Restructed the code that it can build and be used as a shared library.

The command-line version is still statically bound in the build
process by default (for the sake of speed).
This commit is contained in:
John Wiegley 2005-11-09 07:11:22 +00:00
parent 6f4957c8c3
commit e1d0dbf220
23 changed files with 752 additions and 621 deletions

View file

@ -1,6 +1,6 @@
lib_LIBRARIES = libledger.a lib_LTLIBRARIES = libledger.la
libledger_a_CXXFLAGS = libledger_la_CXXFLAGS =
libledger_a_SOURCES = \ libledger_la_SOURCES = \
amount.cc \ amount.cc \
balance.cc \ balance.cc \
binary.cc \ binary.cc \
@ -16,30 +16,32 @@ libledger_a_SOURCES = \
qif.cc \ qif.cc \
quotes.cc \ quotes.cc \
reconcile.cc \ reconcile.cc \
startup.cc \
textual.cc \ textual.cc \
valexpr.cc \ valexpr.cc \
value.cc \ value.cc \
walk.cc walk.cc
if HAVE_EXPAT if HAVE_EXPAT
libledger_a_CXXFLAGS += -DHAVE_EXPAT=1 libledger_la_CXXFLAGS += -DHAVE_EXPAT=1
libledger_a_SOURCES += gnucash.cc xml.cc libledger_la_SOURCES += gnucash.cc xml.cc
endif endif
if HAVE_XMLPARSE if HAVE_XMLPARSE
libledger_a_CXXFLAGS += -DHAVE_XMLPARSE=1 libledger_la_CXXFLAGS += -DHAVE_XMLPARSE=1
libledger_a_SOURCES += gnucash.cc xml.cc libledger_la_SOURCES += gnucash.cc xml.cc
endif endif
if HAVE_LIBOFX if HAVE_LIBOFX
libledger_a_CXXFLAGS += -DHAVE_LIBOFX=1 libledger_la_CXXFLAGS += -DHAVE_LIBOFX=1
libledger_a_SOURCES += ofx.cc libledger_la_SOURCES += ofx.cc
endif endif
if HAVE_BOOST_PYTHON if HAVE_BOOST_PYTHON
libledger_a_CXXFLAGS += -DUSE_BOOST_PYTHON=1 libledger_la_CXXFLAGS += -DUSE_BOOST_PYTHON=1
libledger_a_SOURCES += py_eval.cc libledger_la_SOURCES += py_eval.cc
endif endif
if DEBUG if DEBUG
libledger_a_CXXFLAGS += -DDEBUG_LEVEL=4 libledger_la_CXXFLAGS += -DDEBUG_LEVEL=4
libledger_a_SOURCES += debug.cc libledger_la_SOURCES += debug.cc
endif endif
libledger_la_LDFLAGS = -version-info 3:0
pkginclude_HEADERS = \ pkginclude_HEADERS = \
acconf.h \ acconf.h \
@ -79,9 +81,9 @@ ledger_CXXFLAGS =
ledger_SOURCES = main.cc ledger_SOURCES = main.cc
if HAVE_BOOST_PYTHON if HAVE_BOOST_PYTHON
ledger_CXXFLAGS += -DUSE_BOOST_PYTHON=1 ledger_CXXFLAGS += -DUSE_BOOST_PYTHON=1
ledger_LDADD = $(LIBOBJS) libledger.a -lboost_python -lpython$(PYTHON_VERSION) ledger_LDADD = $(LIBOBJS) libledger.la -lboost_python -lpython$(PYTHON_VERSION)
else else
ledger_LDADD = $(LIBOBJS) libledger.a ledger_LDADD = $(LIBOBJS) libledger.la
endif endif
if HAVE_EXPAT if HAVE_EXPAT
ledger_CXXFLAGS += -DHAVE_EXPAT=1 ledger_CXXFLAGS += -DHAVE_EXPAT=1
@ -98,6 +100,7 @@ endif
if DEBUG if DEBUG
ledger_CXXFLAGS += -DDEBUG_LEVEL=4 ledger_CXXFLAGS += -DDEBUG_LEVEL=4
endif endif
ledger_LDFLAGS = -static # for the sake of command-line speed
info_TEXINFOS = ledger.texi info_TEXINFOS = ledger.texi
@ -123,15 +126,15 @@ else
HAVE_LIBOFX_VALUE = false HAVE_LIBOFX_VALUE = false
endif endif
ledger.so: py_eval.cc libledger.a ledger.so: py_eval.cc libledger.la
CFLAGS="$(CPPFLAGS)" LDFLAGS="$(LDFLAGS) -L." \ CFLAGS="$(CPPFLAGS)" LDFLAGS="$(LDFLAGS) -L. -L.libs" \
HAVE_EXPAT="$(HAVE_EXPAT_VALUE)" \ HAVE_EXPAT="$(HAVE_EXPAT_VALUE)" \
HAVE_XMLPARSE="$(HAVE_XMLPARSE_VALUE)" \ HAVE_XMLPARSE="$(HAVE_XMLPARSE_VALUE)" \
HAVE_LIBOFX="$(HAVE_LIBOFX_VALUE)" \ HAVE_LIBOFX="$(HAVE_LIBOFX_VALUE)" \
python setup.py build --build-lib=. python setup.py build --build-lib=.
install-exec-hook: install-exec-hook:
CFLAGS="$(CPPFLAGS)" LDFLAGS="$(LDFLAGS) -L." \ CFLAGS="$(CPPFLAGS)" LDFLAGS="$(LDFLAGS) -L. -L.libs" \
HAVE_EXPAT="$(HAVE_EXPAT_VALUE)" \ HAVE_EXPAT="$(HAVE_EXPAT_VALUE)" \
HAVE_XMLPARSE="$(HAVE_XMLPARSE_VALUE)" \ HAVE_XMLPARSE="$(HAVE_XMLPARSE_VALUE)" \
HAVE_LIBOFX="$(HAVE_LIBOFX_VALUE)" \ HAVE_LIBOFX="$(HAVE_LIBOFX_VALUE)" \
@ -143,7 +146,7 @@ all-clean: maintainer-clean
rm -fr *~ .*~ .\#* *.html *.info *.pdf *.a *.so *.o *.lo *.la \ rm -fr *~ .*~ .\#* *.html *.info *.pdf *.a *.so *.o *.lo *.la \
*.elc *.aux *.cp *.fn *.ky *.log *.pg *.toc *.tp *.vr *.pyc \ *.elc *.aux *.cp *.fn *.ky *.log *.pg *.toc *.tp *.vr *.pyc \
.gdb_history gmon.out h out TAGS ledger valexpr .deps \ .gdb_history gmon.out h out TAGS ledger valexpr .deps \
build AUTHORS COPYING INSTALL Makefile aclocal.m4 autom4te \ .libs build AUTHORS COPYING INSTALL Makefile acconf.h \
acconf.h acconf.h.in config.guess config.sub configure \ acconf.h.in aclocal.m4 autom4te config.guess config.sub \
depcomp install-sh missing stamp texinfo.tex Makefile.in \ configure depcomp install-sh libtool ltconfig ltmain.sh \
results.out missing stamp texinfo.tex Makefile.in

3
NEWS
View file

@ -2,6 +2,9 @@
* 2.5 * 2.5
- Much internal restruction to allow the use of libledger.so in a
non-command-line environment.
- Effective dates may now be specified for entries: - Effective dates may now be specified for entries:
2004/10/03=2004/09/30 Credit card company 2004/10/03=2004/09/30 Credit card company

1
acprep
View file

@ -2,6 +2,7 @@
touch AUTHORS COPYING touch AUTHORS COPYING
glibtoolize --automake -f -c
aclocal aclocal
autoheader autoheader
if [ "$1" = "--dist" ]; then if [ "$1" = "--dist" ]; then

537
config.cc
View file

@ -20,10 +20,11 @@
namespace ledger { namespace ledger {
config_t config;
std::list<option_t> config_options; std::list<option_t> config_options;
config_t::config_t() static config_t * config = NULL;
void config_t::reset()
{ {
amount_expr = "a"; amount_expr = "a";
total_expr = "O"; total_expr = "O";
@ -73,12 +74,13 @@ config_t::config_t()
cache_dirty = false; cache_dirty = false;
} }
static void void
regexps_to_predicate(config_t& config, const std::string& command, config_t::regexps_to_predicate(const std::string& command,
std::list<std::string>::const_iterator begin, std::list<std::string>::const_iterator begin,
std::list<std::string>::const_iterator end, std::list<std::string>::const_iterator end,
const bool account_regexp = false, const bool account_regexp,
const bool add_account_short_masks = false) const bool add_account_short_masks,
const bool logical_and)
{ {
std::string regexps[2]; std::string regexps[2];
@ -110,12 +112,12 @@ regexps_to_predicate(config_t& config, const std::string& command,
if (regexps[i].empty()) if (regexps[i].empty())
continue; continue;
if (! config.predicate.empty()) if (! predicate.empty())
config.predicate += "&"; predicate += logical_and ? "&" : "|";
int add_predicate = 0; // 1 adds /.../, 2 adds ///.../ int add_predicate = 0; // 1 adds /.../, 2 adds ///.../
if (i == 1) { if (i == 1) {
config.predicate += "!"; predicate += "!";
} }
else if (add_account_short_masks) { else if (add_account_short_masks) {
if (regexps[i].find(':') != std::string::npos || if (regexps[i].find(':') != std::string::npos ||
@ -124,7 +126,7 @@ regexps_to_predicate(config_t& config, const std::string& command,
regexps[i].find('+') != std::string::npos || regexps[i].find('+') != std::string::npos ||
regexps[i].find('[') != std::string::npos || regexps[i].find('[') != std::string::npos ||
regexps[i].find('(') != std::string::npos) { regexps[i].find('(') != std::string::npos) {
config.show_subtotal = true; show_subtotal = true;
add_predicate = 1; add_predicate = 1;
} else { } else {
add_predicate = 2; add_predicate = 2;
@ -135,26 +137,49 @@ regexps_to_predicate(config_t& config, const std::string& command,
} }
if (i != 1 && command == "b" && account_regexp) { if (i != 1 && command == "b" && account_regexp) {
if (! config.display_predicate.empty()) if (! display_predicate.empty())
config.display_predicate += "&"; display_predicate += "&";
else if (! config.show_empty) else if (! show_empty)
config.display_predicate += "T&"; display_predicate += "T&";
if (add_predicate == 2) if (add_predicate == 2)
config.display_predicate += "//"; display_predicate += "//";
config.display_predicate += "/(?:"; display_predicate += "/(?:";
config.display_predicate += regexps[i]; display_predicate += regexps[i];
config.display_predicate += ")/"; display_predicate += ")/";
} }
if (! account_regexp) if (! account_regexp)
config.predicate += "/"; predicate += "/";
config.predicate += "/(?:"; predicate += "/(?:";
config.predicate += regexps[i]; predicate += regexps[i];
config.predicate += ")/"; predicate += ")/";
} }
} }
bool config_t::process_option(const std::string& opt, const char * arg)
{
config = this;
bool result = ::process_option(config_options, opt, arg);
config = NULL;
return result;
}
void config_t::process_arguments(int argc, char ** argv, const bool anywhere,
std::list<std::string>& args)
{
config = this;
::process_arguments(config_options, argc, argv, anywhere, args);
config = NULL;
}
void config_t::process_environment(char ** envp, const std::string& tag)
{
config = this;
::process_environment(config_options, envp, tag);
config = NULL;
}
void config_t::process_options(const std::string& command, void config_t::process_options(const std::string& command,
strings_list::iterator arg, strings_list::iterator arg,
strings_list::iterator args_end) strings_list::iterator args_end)
@ -189,11 +214,11 @@ void config_t::process_options(const std::string& command,
break; break;
if (i != arg) if (i != arg)
regexps_to_predicate(*this, command, arg, i, true, regexps_to_predicate(command, arg, i, true,
(command == "b" && ! show_subtotal && (command == "b" && ! show_subtotal &&
display_predicate.empty())); display_predicate.empty()));
if (i != args_end && ++i != args_end) if (i != args_end && ++i != args_end)
regexps_to_predicate(*this, command, i, args_end); regexps_to_predicate(command, i, args_end);
} }
// Setup the default value for the display predicate // Setup the default value for the display predicate
@ -258,83 +283,157 @@ void config_t::process_options(const std::string& command,
format_t::date_format = date_format; format_t::date_format = date_format;
} }
void parse_ledger_data(journal_t * journal, parser_t * cache_parser, item_handler<transaction_t> *
parser_t * text_parser, parser_t * xml_parser) config_t::chain_xact_handlers(const std::string& command,
item_handler<transaction_t> * base_formatter,
journal_t * journal,
account_t * master,
std::list<item_handler<transaction_t> *>& ptrs)
{ {
int entry_count = 0; item_handler<transaction_t> * formatter = NULL;
DEBUG_PRINT("ledger.config.cache", "3. use_cache = " << config.use_cache); ptrs.push_back(formatter = base_formatter);
if (! config.init_file.empty() && // format_transactions write each transaction received to the
access(config.init_file.c_str(), R_OK) != -1) { // output stream.
if (parse_journal_file(config.init_file, journal) || if (! (command == "b" || command == "E")) {
journal->auto_entries.size() > 0 || // truncate_entries cuts off a certain number of _entries_ from
journal->period_entries.size() > 0) // being displayed. It does not affect calculation.
throw error(std::string("Entries found in initialization file '") + if (head_entries || tail_entries)
config.init_file + "'"); ptrs.push_back(formatter =
new truncate_entries(formatter,
head_entries, tail_entries));
journal->sources.pop_front(); // remove init file // filter_transactions will only pass through transactions
// matching the `display_predicate'.
if (! display_predicate.empty())
ptrs.push_back(formatter =
new filter_transactions(formatter,
display_predicate));
// calc_transactions computes the running total. When this
// appears will determine, for example, whether filtered
// transactions are included or excluded from the running total.
ptrs.push_back(formatter = new calc_transactions(formatter));
// reconcile_transactions will pass through only those
// transactions which can be reconciled to a given balance
// (calculated against the transactions which it receives).
if (! reconcile_balance.empty()) {
value_t target_balance(reconcile_balance);
time_t cutoff = now;
if (! reconcile_date.empty())
parse_date(reconcile_date.c_str(), &cutoff);
ptrs.push_back(formatter =
new reconcile_transactions(formatter, target_balance,
cutoff));
}
// sort_transactions will sort all the transactions it sees, based
// on the `sort_order' value expression.
if (! sort_string.empty())
ptrs.push_back(formatter =
new sort_transactions(formatter, sort_string));
// changed_value_transactions adds virtual transactions to the
// list to account for changes in market value of commodities,
// which otherwise would affect the running total unpredictably.
if (show_revalued)
ptrs.push_back(formatter =
new changed_value_transactions(formatter,
show_revalued_only));
// collapse_transactions causes entries with multiple transactions
// to appear as entries with a subtotaled transaction for each
// commodity used.
if (show_collapsed)
ptrs.push_back(formatter = new collapse_transactions(formatter));
} }
if (cache_parser && config.use_cache && // subtotal_transactions combines all the transactions it receives
! config.cache_file.empty() && // into one subtotal entry, which has one transaction for each
! config.data_file.empty()) { // commodity in each account.
DEBUG_PRINT("ledger.config.cache", "using_cache " << config.cache_file); //
config.cache_dirty = true; // period_transactions is like subtotal_transactions, but it
if (access(config.cache_file.c_str(), R_OK) != -1) { // subtotals according to time periods rather than totalling
std::ifstream stream(config.cache_file.c_str()); // everything.
if (cache_parser->test(stream)) { //
std::string price_db_orig = journal->price_db; // dow_transactions is like period_transactions, except that it
journal->price_db = config.price_db; // reports all the transactions that fall on each subsequent day
entry_count += cache_parser->parse(stream, journal, NULL, // of the week.
&config.data_file); if (show_subtotal && ! (command == "b" || command == "E"))
if (entry_count > 0) ptrs.push_back(formatter = new subtotal_transactions(formatter));
config.cache_dirty = false;
else if (days_of_the_week)
journal->price_db = price_db_orig; ptrs.push_back(formatter = new dow_transactions(formatter));
} else if (by_payee)
} ptrs.push_back(formatter = new by_payee_transactions(formatter));
if (! report_period.empty()) {
ptrs.push_back(formatter =
new interval_transactions(formatter,
report_period,
report_period_sort));
ptrs.push_back(formatter = new sort_transactions(formatter, "d"));
} }
if (entry_count == 0 && ! config.data_file.empty()) { // invert_transactions inverts the value of the transactions it
account_t * account = NULL; // receives.
if (! config.account.empty()) if (show_inverted)
account = journal->find_account(config.account); ptrs.push_back(formatter = new invert_transactions(formatter));
journal->price_db = config.price_db; // related_transactions will pass along all transactions related
if (! journal->price_db.empty() && // to the transaction received. If `show_all_related' is true,
access(journal->price_db.c_str(), R_OK) != -1) { // then all the entry's transactions are passed; meaning that if
if (parse_journal_file(journal->price_db, journal)) { // one transaction of an entry is to be printed, all the
throw error("Entries not allowed in price history file"); // transaction for that entry will be printed.
} else { if (show_related)
DEBUG_PRINT("ledger.config.cache", ptrs.push_back(formatter =
"read price database " << journal->price_db); new related_transactions(formatter,
journal->sources.pop_back(); show_all_related));
}
}
DEBUG_PRINT("ledger.config.cache", // This filter_transactions will only pass through transactions
"rejected cache, parsing " << config.data_file); // matching the `predicate'.
if (config.data_file == "-") { if (! predicate.empty())
config.use_cache = false; ptrs.push_back(formatter = new filter_transactions(formatter, predicate));
journal->sources.push_back("<stdin>");
if (xml_parser && std::cin.peek() == '<') // budget_transactions takes a set of transactions from a data
entry_count += xml_parser->parse(std::cin, journal, account); // file and uses them to generate "budget transactions" which
else // balance against the reported transactions.
entry_count += text_parser->parse(std::cin, journal, account); //
} // forecast_transactions is a lot like budget_transactions, except
else if (access(config.data_file.c_str(), R_OK) != -1) { // that it adds entries only for the future, and does not balance
entry_count += parse_journal_file(config.data_file, journal, account); // them against anything but the future balance.
if (! journal->price_db.empty())
journal->sources.push_back(journal->price_db); if (budget_flags) {
} budget_transactions * handler
= new budget_transactions(formatter, budget_flags);
handler->add_period_entries(journal->period_entries);
ptrs.push_back(formatter = handler);
// Apply this before the budget handler, so that only matching
// transactions are calculated toward the budget. The use of
// filter_transactions above will further clean the results so
// that no automated transactions that don't match the filter get
// reported.
if (! predicate.empty())
ptrs.push_back(formatter = new filter_transactions(formatter, predicate));
}
else if (! forecast_limit.empty()) {
forecast_transactions * handler
= new forecast_transactions(formatter, forecast_limit);
handler->add_period_entries(journal->period_entries);
ptrs.push_back(formatter = handler);
// See above, under budget_transactions.
if (! predicate.empty())
ptrs.push_back(formatter = new filter_transactions(formatter, predicate));
} }
if (entry_count == 0) if (comm_as_payee)
throw error("Please specify ledger file using -f" ptrs.push_back(formatter = new set_comm_as_payee(formatter));
" or LEDGER_FILE environment variable.");
VALIDATE(journal->valid()); return formatter;
} }
static void show_version(std::ostream& out) static void show_version(std::ostream& out)
@ -586,38 +685,42 @@ OPT_BEGIN(version, "v") {
} OPT_END(version); } OPT_END(version);
OPT_BEGIN(init_file, "i:") { OPT_BEGIN(init_file, "i:") {
config.init_file = optarg; config->init_file = optarg;
} OPT_END(init_file); } OPT_END(init_file);
OPT_BEGIN(file, "f:") { OPT_BEGIN(file, "f:") {
if (std::string(optarg) == "-" || access(optarg, R_OK) != -1) if (std::string(optarg) == "-" || access(optarg, R_OK) != -1)
config.data_file = optarg; config->data_file = optarg;
else else
throw error(std::string("The ledger file '") + optarg + throw error(std::string("The ledger file '") + optarg +
"' does not exist or is not readable"); "' does not exist or is not readable");
} OPT_END(file); } OPT_END(file);
OPT_BEGIN(cache, ":") { OPT_BEGIN(cache, ":") {
config.cache_file = optarg; config->cache_file = optarg;
} OPT_END(cache); } OPT_END(cache);
OPT_BEGIN(no_cache, "") { OPT_BEGIN(no_cache, "") {
config.cache_file = "<none>"; config->cache_file = "<none>";
} OPT_END(no_cache); } OPT_END(no_cache);
OPT_BEGIN(output, "o:") { OPT_BEGIN(output, "o:") {
if (std::string(optarg) != "-") if (std::string(optarg) != "-")
config.output_file = optarg; config->output_file = optarg;
} OPT_END(output); } OPT_END(output);
OPT_BEGIN(account, "a:") { OPT_BEGIN(account, "a:") {
config.account = optarg; config->account = optarg;
} OPT_END(account); } OPT_END(account);
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// //
// Report filtering // Report filtering
OPT_BEGIN(effective, "") {
transaction_t::use_effective_date = true;
} OPT_END(effective);
OPT_BEGIN(begin, "b:") { OPT_BEGIN(begin, "b:") {
char buf[128]; char buf[128];
interval_t interval(optarg); interval_t interval(optarg);
@ -627,11 +730,11 @@ OPT_BEGIN(begin, "b:") {
throw error(std::string("Could not determine beginning of period '") + throw error(std::string("Could not determine beginning of period '") +
optarg + "'"); optarg + "'");
if (! config.predicate.empty()) if (! config->predicate.empty())
config.predicate += "&"; config->predicate += "&";
config.predicate += "d>=["; config->predicate += "d>=[";
config.predicate += buf; config->predicate += buf;
config.predicate += "]"; config->predicate += "]";
} OPT_END(begin); } OPT_END(begin);
OPT_BEGIN(end, "e:") { OPT_BEGIN(end, "e:") {
@ -643,43 +746,43 @@ OPT_BEGIN(end, "e:") {
throw error(std::string("Could not determine end of period '") + throw error(std::string("Could not determine end of period '") +
optarg + "'"); optarg + "'");
if (! config.predicate.empty()) if (! config->predicate.empty())
config.predicate += "&"; config->predicate += "&";
config.predicate += "d<["; config->predicate += "d<[";
config.predicate += buf; config->predicate += buf;
config.predicate += "]"; config->predicate += "]";
terminus = interval.end; terminus = interval.end;
} OPT_END(end); } OPT_END(end);
OPT_BEGIN(current, "c") { OPT_BEGIN(current, "c") {
if (! config.predicate.empty()) if (! config->predicate.empty())
config.predicate += "&"; config->predicate += "&";
config.predicate += "d<=m"; config->predicate += "d<=m";
} OPT_END(current); } OPT_END(current);
OPT_BEGIN(cleared, "C") { OPT_BEGIN(cleared, "C") {
if (! config.predicate.empty()) if (! config->predicate.empty())
config.predicate += "&"; config->predicate += "&";
config.predicate += "X"; config->predicate += "X";
} OPT_END(cleared); } OPT_END(cleared);
OPT_BEGIN(uncleared, "U") { OPT_BEGIN(uncleared, "U") {
if (! config.predicate.empty()) if (! config->predicate.empty())
config.predicate += "&"; config->predicate += "&";
config.predicate += "!X"; config->predicate += "!X";
} OPT_END(uncleared); } OPT_END(uncleared);
OPT_BEGIN(real, "R") { OPT_BEGIN(real, "R") {
if (! config.predicate.empty()) if (! config->predicate.empty())
config.predicate += "&"; config->predicate += "&";
config.predicate += "R"; config->predicate += "R";
} OPT_END(real); } OPT_END(real);
OPT_BEGIN(actual, "L") { OPT_BEGIN(actual, "L") {
if (! config.predicate.empty()) if (! config->predicate.empty())
config.predicate += "&"; config->predicate += "&";
config.predicate += "L"; config->predicate += "L";
} OPT_END(actual); } OPT_END(actual);
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@ -687,11 +790,11 @@ OPT_BEGIN(actual, "L") {
// Output customization // Output customization
OPT_BEGIN(format, "F:") { OPT_BEGIN(format, "F:") {
config.format_string = optarg; config->format_string = optarg;
} OPT_END(format); } OPT_END(format);
OPT_BEGIN(date_format, "y:") { OPT_BEGIN(date_format, "y:") {
config.date_format = optarg; config->date_format = optarg;
} OPT_END(date_format); } OPT_END(date_format);
OPT_BEGIN(input_date_format, ":") { OPT_BEGIN(input_date_format, ":") {
@ -700,95 +803,91 @@ OPT_BEGIN(input_date_format, ":") {
} OPT_END(input_date_format); } OPT_END(input_date_format);
OPT_BEGIN(balance_format, ":") { OPT_BEGIN(balance_format, ":") {
config.balance_format = optarg; config->balance_format = optarg;
} OPT_END(balance_format); } OPT_END(balance_format);
OPT_BEGIN(register_format, ":") { OPT_BEGIN(register_format, ":") {
config.register_format = optarg; config->register_format = optarg;
} OPT_END(register_format); } OPT_END(register_format);
OPT_BEGIN(wide_register_format, ":") { OPT_BEGIN(wide_register_format, ":") {
config.wide_register_format = optarg; config->wide_register_format = optarg;
} OPT_END(wide_register_format); } OPT_END(wide_register_format);
OPT_BEGIN(plot_amount_format, ":") { OPT_BEGIN(plot_amount_format, ":") {
config.plot_amount_format = optarg; config->plot_amount_format = optarg;
} OPT_END(plot_amount_format); } OPT_END(plot_amount_format);
OPT_BEGIN(plot_total_format, ":") { OPT_BEGIN(plot_total_format, ":") {
config.plot_total_format = optarg; config->plot_total_format = optarg;
OPT_BEGIN(effective, "") {
transaction_t::use_effective_date = true;
} OPT_END(effective);
} OPT_END(plot_total_format); } OPT_END(plot_total_format);
OPT_BEGIN(print_format, ":") { OPT_BEGIN(print_format, ":") {
config.print_format = optarg; config->print_format = optarg;
} OPT_END(print_format); } OPT_END(print_format);
OPT_BEGIN(write_hdr_format, ":") { OPT_BEGIN(write_hdr_format, ":") {
config.write_hdr_format = optarg; config->write_hdr_format = optarg;
} OPT_END(write_hdr_format); } OPT_END(write_hdr_format);
OPT_BEGIN(write_xact_format, ":") { OPT_BEGIN(write_xact_format, ":") {
config.write_xact_format = optarg; config->write_xact_format = optarg;
} OPT_END(write_xact_format); } OPT_END(write_xact_format);
OPT_BEGIN(equity_format, ":") { OPT_BEGIN(equity_format, ":") {
config.equity_format = optarg; config->equity_format = optarg;
} OPT_END(equity_format); } OPT_END(equity_format);
OPT_BEGIN(prices_format, ":") { OPT_BEGIN(prices_format, ":") {
config.prices_format = optarg; config->prices_format = optarg;
} OPT_END(prices_format); } OPT_END(prices_format);
OPT_BEGIN(wide, "w") { OPT_BEGIN(wide, "w") {
config.register_format = config.wide_register_format; config->register_format = config->wide_register_format;
} OPT_END(wide); } OPT_END(wide);
OPT_BEGIN(head, ":") { OPT_BEGIN(head, ":") {
config.head_entries = std::atoi(optarg); config->head_entries = std::atoi(optarg);
} OPT_END(head); } OPT_END(head);
OPT_BEGIN(tail, ":") { OPT_BEGIN(tail, ":") {
config.tail_entries = std::atoi(optarg); config->tail_entries = std::atoi(optarg);
} OPT_END(tail); } OPT_END(tail);
OPT_BEGIN(pager, ":") { OPT_BEGIN(pager, ":") {
config.pager = optarg; config->pager = optarg;
} OPT_END(pager); } OPT_END(pager);
OPT_BEGIN(empty, "E") { OPT_BEGIN(empty, "E") {
config.show_empty = true; config->show_empty = true;
} OPT_END(empty); } OPT_END(empty);
OPT_BEGIN(collapse, "n") { OPT_BEGIN(collapse, "n") {
config.show_collapsed = true; config->show_collapsed = true;
} OPT_END(collapse); } OPT_END(collapse);
OPT_BEGIN(subtotal, "s") { OPT_BEGIN(subtotal, "s") {
config.show_subtotal = true; config->show_subtotal = true;
} OPT_END(subtotal); } OPT_END(subtotal);
OPT_BEGIN(totals, "") { OPT_BEGIN(totals, "") {
config.show_totals = true; config->show_totals = true;
} OPT_END(totals); } OPT_END(totals);
OPT_BEGIN(sort, "S:") { OPT_BEGIN(sort, "S:") {
config.sort_string = optarg; config->sort_string = optarg;
} OPT_END(sort); } OPT_END(sort);
OPT_BEGIN(related, "r") { OPT_BEGIN(related, "r") {
config.show_related = true; config->show_related = true;
} OPT_END(related); } OPT_END(related);
OPT_BEGIN(period, "p:") { OPT_BEGIN(period, "p:") {
if (config.report_period.empty()) { if (config->report_period.empty()) {
config.report_period = optarg; config->report_period = optarg;
} else { } else {
config.report_period += " "; config->report_period += " ";
config.report_period += optarg; config->report_period += optarg;
} }
// If the period gives a beginning and/or ending date, make sure to // If the period gives a beginning and/or ending date, make sure to
@ -796,180 +895,180 @@ OPT_BEGIN(period, "p:") {
// options) to take this into account. // options) to take this into account.
char buf[128]; char buf[128];
interval_t interval(config.report_period); interval_t interval(config->report_period);
if (interval.begin) { if (interval.begin) {
std::strftime(buf, 127, formats[0], std::localtime(&interval.begin)); std::strftime(buf, 127, formats[0], std::localtime(&interval.begin));
if (! config.predicate.empty()) if (! config->predicate.empty())
config.predicate += "&"; config->predicate += "&";
config.predicate += "d>=["; config->predicate += "d>=[";
config.predicate += buf; config->predicate += buf;
config.predicate += "]"; config->predicate += "]";
} }
if (interval.end) { if (interval.end) {
std::strftime(buf, 127, formats[0], std::localtime(&interval.end)); std::strftime(buf, 127, formats[0], std::localtime(&interval.end));
if (! config.predicate.empty()) if (! config->predicate.empty())
config.predicate += "&"; config->predicate += "&";
config.predicate += "d<["; config->predicate += "d<[";
config.predicate += buf; config->predicate += buf;
config.predicate += "]"; config->predicate += "]";
terminus = interval.end; terminus = interval.end;
} }
} OPT_END(period); } OPT_END(period);
OPT_BEGIN(period_sort, ":") { OPT_BEGIN(period_sort, ":") {
config.report_period_sort = optarg; config->report_period_sort = optarg;
} OPT_END(period_sort); } OPT_END(period_sort);
OPT_BEGIN(weekly, "W") { OPT_BEGIN(weekly, "W") {
if (config.report_period.empty()) if (config->report_period.empty())
config.report_period = "weekly"; config->report_period = "weekly";
else else
config.report_period = std::string("weekly ") + config.report_period; config->report_period = std::string("weekly ") + config->report_period;
} OPT_END(weekly); } OPT_END(weekly);
OPT_BEGIN(monthly, "M") { OPT_BEGIN(monthly, "M") {
if (config.report_period.empty()) if (config->report_period.empty())
config.report_period = "monthly"; config->report_period = "monthly";
else else
config.report_period = std::string("monthly ") + config.report_period; config->report_period = std::string("monthly ") + config->report_period;
} OPT_END(monthly); } OPT_END(monthly);
OPT_BEGIN(yearly, "Y") { OPT_BEGIN(yearly, "Y") {
if (config.report_period.empty()) if (config->report_period.empty())
config.report_period = "yearly"; config->report_period = "yearly";
else else
config.report_period = std::string("yearly ") + config.report_period; config->report_period = std::string("yearly ") + config->report_period;
} OPT_END(yearly); } OPT_END(yearly);
OPT_BEGIN(dow, "") { OPT_BEGIN(dow, "") {
config.days_of_the_week = true; config->days_of_the_week = true;
} OPT_END(dow); } OPT_END(dow);
OPT_BEGIN(by_payee, "P") { OPT_BEGIN(by_payee, "P") {
config.by_payee = true; config->by_payee = true;
} OPT_END(by_payee); } OPT_END(by_payee);
OPT_BEGIN(comm_as_payee, "x") { OPT_BEGIN(comm_as_payee, "x") {
config.comm_as_payee = true; config->comm_as_payee = true;
} OPT_END(comm_as_payee); } OPT_END(comm_as_payee);
OPT_BEGIN(budget, "") { OPT_BEGIN(budget, "") {
config.budget_flags = BUDGET_BUDGETED; config->budget_flags = BUDGET_BUDGETED;
} OPT_END(budget); } OPT_END(budget);
OPT_BEGIN(add_budget, "") { OPT_BEGIN(add_budget, "") {
config.budget_flags = BUDGET_BUDGETED | BUDGET_UNBUDGETED; config->budget_flags = BUDGET_BUDGETED | BUDGET_UNBUDGETED;
} OPT_END(add_budget); } OPT_END(add_budget);
OPT_BEGIN(unbudgeted, "") { OPT_BEGIN(unbudgeted, "") {
config.budget_flags = BUDGET_UNBUDGETED; config->budget_flags = BUDGET_UNBUDGETED;
} OPT_END(unbudgeted); } OPT_END(unbudgeted);
OPT_BEGIN(forecast, ":") { OPT_BEGIN(forecast, ":") {
config.forecast_limit = optarg; config->forecast_limit = optarg;
} OPT_END(forecast); } OPT_END(forecast);
OPT_BEGIN(reconcile, ":") { OPT_BEGIN(reconcile, ":") {
config.reconcile_balance = optarg; config->reconcile_balance = optarg;
} OPT_END(reconcile); } OPT_END(reconcile);
OPT_BEGIN(reconcile_date, ":") { OPT_BEGIN(reconcile_date, ":") {
config.reconcile_date = optarg; config->reconcile_date = optarg;
} OPT_END(reconcile_date); } OPT_END(reconcile_date);
OPT_BEGIN(limit, "l:") { OPT_BEGIN(limit, "l:") {
if (! config.predicate.empty()) if (! config->predicate.empty())
config.predicate += "&"; config->predicate += "&";
config.predicate += "("; config->predicate += "(";
config.predicate += optarg; config->predicate += optarg;
config.predicate += ")"; config->predicate += ")";
} OPT_END(limit); } OPT_END(limit);
OPT_BEGIN(display, "d:") { OPT_BEGIN(display, "d:") {
if (! config.display_predicate.empty()) if (! config->display_predicate.empty())
config.display_predicate += "&"; config->display_predicate += "&";
config.display_predicate += "("; config->display_predicate += "(";
config.display_predicate += optarg; config->display_predicate += optarg;
config.display_predicate += ")"; config->display_predicate += ")";
} OPT_END(display); } OPT_END(display);
OPT_BEGIN(amount, "t:") { OPT_BEGIN(amount, "t:") {
config.amount_expr = optarg; config->amount_expr = optarg;
} OPT_END(amount); } OPT_END(amount);
OPT_BEGIN(total, "T:") { OPT_BEGIN(total, "T:") {
config.total_expr = optarg; config->total_expr = optarg;
} OPT_END(total); } OPT_END(total);
OPT_BEGIN(amount_data, "j") { OPT_BEGIN(amount_data, "j") {
config.format_string = config.plot_amount_format; config->format_string = config->plot_amount_format;
} OPT_END(amount_data); } OPT_END(amount_data);
OPT_BEGIN(total_data, "J") { OPT_BEGIN(total_data, "J") {
config.format_string = config.plot_total_format; config->format_string = config->plot_total_format;
} OPT_END(total_data); } OPT_END(total_data);
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// //
// Commodity reporting // Commodity reporting
OPT_BEGIN(price_db, ":") { OPT_BEGIN(price_db, ":") {
config.price_db = optarg; config->price_db = optarg;
} OPT_END(price_db); } OPT_END(price_db);
OPT_BEGIN(price_exp, "Z:") { OPT_BEGIN(price_exp, "Z:") {
config.pricing_leeway = std::atol(optarg) * 60; config->pricing_leeway = std::atol(optarg) * 60;
} OPT_END(price_exp); } OPT_END(price_exp);
OPT_BEGIN(download, "Q") { OPT_BEGIN(download, "Q") {
config.download_quotes = true; config->download_quotes = true;
} OPT_END(download); } OPT_END(download);
OPT_BEGIN(quantity, "O") { OPT_BEGIN(quantity, "O") {
config.amount_expr = "a"; config->amount_expr = "a";
config.total_expr = "O"; config->total_expr = "O";
} OPT_END(quantity); } OPT_END(quantity);
OPT_BEGIN(basis, "B") { OPT_BEGIN(basis, "B") {
config.amount_expr = "b"; config->amount_expr = "b";
config.total_expr = "B"; config->total_expr = "B";
} OPT_END(basis); } OPT_END(basis);
OPT_BEGIN(market, "V") { OPT_BEGIN(market, "V") {
config.show_revalued = true; config->show_revalued = true;
config.amount_expr = "v"; config->amount_expr = "v";
config.total_expr = "V"; config->total_expr = "V";
} OPT_END(market); } OPT_END(market);
OPT_BEGIN(performance, "g") { OPT_BEGIN(performance, "g") {
config.amount_expr = "P(a,m)-b"; // same as 'g', but priced now config->amount_expr = "P(a,m)-b"; // same as 'g', but priced now
config.total_expr = "P(O,m)-B"; config->total_expr = "P(O,m)-B";
} OPT_END(performance); } OPT_END(performance);
OPT_BEGIN(gain, "G") { OPT_BEGIN(gain, "G") {
config.show_revalued = config->show_revalued =
config.show_revalued_only = true; config->show_revalued_only = true;
config.amount_expr = "a"; config->amount_expr = "a";
config.total_expr = "G"; config->total_expr = "G";
} OPT_END(gain); } OPT_END(gain);
OPT_BEGIN(average, "A") { OPT_BEGIN(average, "A") {
config.total_expr_template = "A#"; config->total_expr_template = "A#";
} OPT_END(average); } OPT_END(average);
OPT_BEGIN(deviation, "D") { OPT_BEGIN(deviation, "D") {
config.total_expr_template = "t-A#"; config->total_expr_template = "t-A#";
} OPT_END(deviation); } OPT_END(deviation);
OPT_BEGIN(percentage, "%") { OPT_BEGIN(percentage, "%") {
config.total_expr_template = "^#&{100.0%}*(#/^#)"; config->total_expr_template = "^#&{100.0%}*(#/^#)";
} OPT_END(percentage); } OPT_END(percentage);
#ifdef USE_BOOST_PYTHON #ifdef USE_BOOST_PYTHON
@ -1017,9 +1116,6 @@ void py_add_config_option_handlers()
add_other_option_handlers(config_options); add_other_option_handlers(config_options);
} }
BOOST_PYTHON_FUNCTION_OVERLOADS(parse_ledger_data_overloads,
parse_ledger_data, 1, 2)
void py_option_help() void py_option_help()
{ {
option_help(std::cout); option_help(std::cout);
@ -1082,10 +1178,9 @@ void export_config()
.def("process_options", py_process_options) .def("process_options", py_process_options)
; ;
scope().attr("config") = ptr(&config); scope().attr("config") = ptr(config);
def("option_help", py_option_help); def("option_help", py_option_help);
def("parse_ledger_data", parse_ledger_data, parse_ledger_data_overloads());
def("add_config_option_handlers", py_add_config_option_handlers); def("add_config_option_handlers", py_add_config_option_handlers);
} }

View file

@ -1,12 +1,7 @@
#ifndef _CONFIG_H #ifndef _CONFIG_H
#define _CONFIG_H #define _CONFIG_H
#include "journal.h" #include "ledger.h"
#include "option.h"
#include "valexpr.h"
#include "datetime.h"
#include "format.h"
#include "parser.h"
#include <iostream> #include <iostream>
#include <memory> #include <memory>
@ -14,8 +9,9 @@
namespace ledger { namespace ledger {
struct config_t class config_t
{ {
public:
// These options can all be set used text fields. // These options can all be set used text fields.
strings_list price_settings; strings_list price_settings;
@ -70,27 +66,43 @@ struct config_t
bool use_cache; bool use_cache;
bool cache_dirty; bool cache_dirty;
config_t(); config_t() {
reset();
}
config_t(const config_t&) { config_t(const config_t&) {
assert(0); assert(0);
} }
void reset();
void regexps_to_predicate(const std::string& command,
std::list<std::string>::const_iterator begin,
std::list<std::string>::const_iterator end,
const bool account_regexp = false,
const bool add_account_short_masks = false,
const bool logical_and = true);
bool process_option(const std::string& opt, const char * arg = NULL);
void process_arguments(int argc, char ** argv, const bool anywhere,
std::list<std::string>& args);
void process_environment(char ** envp, const std::string& tag);
void process_options(const std::string& command, void process_options(const std::string& command,
strings_list::iterator arg, strings_list::iterator arg,
strings_list::iterator args_end); strings_list::iterator args_end);
item_handler<transaction_t> *
chain_xact_handlers(const std::string& command,
item_handler<transaction_t> * base_formatter,
journal_t * journal,
account_t * master,
std::list<item_handler<transaction_t> *>& ptrs);
}; };
extern config_t config;
extern std::list<option_t> config_options; extern std::list<option_t> config_options;
void option_help(std::ostream& out); void option_help(std::ostream& out);
// Parse what ledger data can be determined from the config settings
void parse_ledger_data(journal_t * journal,
parser_t * cache_parser = NULL,
parser_t * text_parser = NULL,
parser_t * xml_parser = NULL);
struct declared_option_handler : public option_handler { struct declared_option_handler : public option_handler {
declared_option_handler(const std::string& label, declared_option_handler(const std::string& label,
const std::string& opt_chars) { const std::string& opt_chars) {

View file

@ -10,9 +10,8 @@ AC_CONFIG_HEADER([acconf.h])
# Checks for programs. # Checks for programs.
AC_PROG_CXX AC_PROG_CXX
AC_PROG_MAKE_SET AC_PROG_MAKE_SET
AC_PROG_RANLIB AC_PROG_LIBTOOL
#AC_PROG_LIBTOOL AM_PROG_LIBTOOL
#AM_PROG_LIBTOOL
# check if UNIX pipes are available # check if UNIX pipes are available
AC_CACHE_CHECK( AC_CACHE_CHECK(

View file

@ -36,7 +36,7 @@ entry_t * derive_new_entry(journal_t& journal,
if (i == end) { if (i == end) {
// If no argument were given but the payee, assume the user wants // If no argument were given but the payee, assume the user wants
// to see the same transaction as last time. // to see the same transaction as last time.
added->code = matching->code; added->code = matching->code;
for (transactions_list::iterator j = matching->transactions.begin(); for (transactions_list::iterator j = matching->transactions.begin();
j != matching->transactions.end(); j != matching->transactions.end();

View file

@ -47,7 +47,7 @@ void format_emacs_transactions::operator()(transaction_t& xact)
out << "\n"; out << "\n";
} }
out << " (\"" << xact.account->fullname() << "\" \"" out << " (\"" << xact_account(xact)->fullname() << "\" \""
<< xact.amount << "\""; << xact.amount << "\"";
switch (xact.state) { switch (xact.state) {

View file

@ -474,7 +474,7 @@ void format_t::format(std::ostream& out_str, const details_t& details) const
if (details.xact) { if (details.xact) {
switch (details.xact->state) { switch (details.xact->state) {
case transaction_t::CLEARED: case transaction_t::CLEARED:
out << "* "; out << "* ";
break; break;
case transaction_t::PENDING: case transaction_t::PENDING:
out << "! "; out << "! ";
@ -547,8 +547,8 @@ void format_t::format(std::ostream& out_str, const details_t& details) const
case element_t::ACCOUNT_FULLNAME: case element_t::ACCOUNT_FULLNAME:
if (details.account) { if (details.account) {
name += (elem->type == element_t::ACCOUNT_FULLNAME ? name += (elem->type == element_t::ACCOUNT_FULLNAME ?
details.account->fullname() : details.account->fullname() :
partial_account_name(*details.account)); partial_account_name(*details.account));
if (details.xact && details.xact->flags & TRANSACTION_VIRTUAL) { if (details.xact && details.xact->flags & TRANSACTION_VIRTUAL) {
if (elem->max_width > 2) if (elem->max_width > 2)
@ -685,7 +685,9 @@ void print_entry(std::ostream& out, const entry_t& entry)
formatter); formatter);
formatter.flush(); formatter.flush();
clear_all_xdata(); clear_transaction_xdata cleaner;
walk_transactions(const_cast<transactions_list&>(entry.transactions),
cleaner);
} }
bool disp_subaccounts_p(const account_t& account, bool disp_subaccounts_p(const account_t& account,

View file

@ -728,11 +728,11 @@ EXC_TRANSLATOR(parse_error)
void export_journal() void export_journal()
{ {
scope().attr("TRANSACTION_NORMAL") = TRANSACTION_NORMAL; scope().attr("TRANSACTION_NORMAL") = TRANSACTION_NORMAL;
scope().attr("TRANSACTION_VIRTUAL") = TRANSACTION_VIRTUAL; scope().attr("TRANSACTION_VIRTUAL") = TRANSACTION_VIRTUAL;
scope().attr("TRANSACTION_BALANCE") = TRANSACTION_BALANCE; scope().attr("TRANSACTION_BALANCE") = TRANSACTION_BALANCE;
scope().attr("TRANSACTION_AUTO") = TRANSACTION_AUTO; scope().attr("TRANSACTION_AUTO") = TRANSACTION_AUTO;
scope().attr("TRANSACTION_BULK_ALLOC") = TRANSACTION_BULK_ALLOC; scope().attr("TRANSACTION_BULK_ALLOC") = TRANSACTION_BULK_ALLOC;
class_< transaction_t > ("Transaction") class_< transaction_t > ("Transaction")
.def(init<account_t *, amount_t, optional<unsigned int, std::string> >()) .def(init<account_t *, amount_t, optional<unsigned int, std::string> >())

View file

@ -487,7 +487,7 @@ dropped."
(dolist (item items) (dolist (item items)
(let ((index 1)) (let ((index 1))
(dolist (xact (nthcdr 5 item)) (dolist (xact (nthcdr 5 item))
(let ((beg (point)) (let ((beg (point))
(where (where
(with-current-buffer buf (with-current-buffer buf
(cons (cons
@ -502,14 +502,14 @@ dropped."
account (cdr (ledger-current-entry-bounds))) account (cdr (ledger-current-entry-bounds)))
(setq i (1+ i)))) (setq i (1+ i))))
(point-marker))))))) (point-marker)))))))
(insert (format "%s %-30s %-25s %15s\n" (insert (format "%s %-30s %-25s %15s\n"
(format-time-string "%m/%d" (nth 2 item)) (format-time-string "%m/%d" (nth 2 item))
(nth 4 item) (nth 0 xact) (nth 1 xact))) (nth 4 item) (nth 0 xact) (nth 1 xact)))
(if (nth 2 xact) (if (nth 2 xact)
(set-text-properties beg (1- (point))
(list 'face 'bold
'where where))
(set-text-properties beg (1- (point)) (set-text-properties beg (1- (point))
(list 'face 'bold
'where where))
(set-text-properties beg (1- (point))
(list 'where where)))) (list 'where where))))
(setq index (1+ index))))) (setq index (1+ index)))))
(goto-char (point-min)) (goto-char (point-min))

View file

@ -26,7 +26,6 @@
#include <reconcile.h> #include <reconcile.h>
#include <error.h> #include <error.h>
#include <option.h> #include <option.h>
#include <config.h>
#include <parser.h> #include <parser.h>
#include <textual.h> #include <textual.h>
@ -36,4 +35,15 @@
#include <qif.h> #include <qif.h>
#include <ofx.h> #include <ofx.h>
namespace ledger {
extern parser_t * binary_parser_ptr;
extern parser_t * xml_parser_ptr;
extern parser_t * gnucash_parser_ptr;
extern parser_t * ofx_parser_ptr;
extern parser_t * qif_parser_ptr;
extern parser_t * textual_parser_ptr;
}
#include <config.h>
#endif // _LEDGER_H #endif // _LEDGER_H

295
main.cc
View file

@ -1,16 +1,6 @@
#include <ledger.h>
#include "acconf.h"
#include "debug.h"
#ifdef USE_BOOST_PYTHON
#include "py_eval.h"
#endif
using namespace ledger;
#include <iostream> #include <iostream>
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <memory>
#include <algorithm> #include <algorithm>
#include <exception> #include <exception>
#include <iterator> #include <iterator>
@ -27,218 +17,34 @@ using namespace ledger;
#include "fdstream.hpp" #include "fdstream.hpp"
#endif #endif
#if !defined(DEBUG_LEVEL) || DEBUG_LEVEL <= RELEASE #include "ledger.h"
#ifdef USE_BOOST_PYTHON
#define auto_ptr bogus_auto_ptr #include "py_eval.h"
// This version of auto_ptr does not delete on deconstruction.
namespace std {
template <typename T>
struct bogus_auto_ptr {
T * ptr;
bogus_auto_ptr() : ptr(NULL) {}
explicit bogus_auto_ptr(T * _ptr) : ptr(_ptr) {}
T& operator*() const throw() {
return *ptr;
}
T * operator->() const throw() {
return ptr;
}
T * get() const throw() { return ptr; }
T * release() throw() {
T * tmp = ptr;
ptr = 0;
return tmp;
}
void reset(T * p = 0) throw() {
if (p != ptr) {
delete ptr;
ptr = p;
}
}
};
}
#endif #endif
#include "timing.h"
item_handler<transaction_t> * using namespace ledger;
chain_xact_handlers(const std::string& command,
item_handler<transaction_t> * base_formatter,
journal_t * journal,
account_t * master,
std::list<item_handler<transaction_t> *>& ptrs)
{
item_handler<transaction_t> * formatter = NULL;
ptrs.push_back(formatter = base_formatter); namespace {
TIMER_DEF_(setup);
// format_transactions write each transaction received to the TIMER_DEF_(parse);
// output stream. TIMER_DEF_(process);
if (! (command == "b" || command == "E")) { TIMER_DEF_(walk);
// truncate_entries cuts off a certain number of _entries_ from TIMER_DEF_(cleanup);
// being displayed. It does not affect calculation.
if (config.head_entries || config.tail_entries)
ptrs.push_back(formatter =
new truncate_entries(formatter,
config.head_entries,
config.tail_entries));
// filter_transactions will only pass through transactions
// matching the `display_predicate'.
if (! config.display_predicate.empty())
ptrs.push_back(formatter =
new filter_transactions(formatter,
config.display_predicate));
// calc_transactions computes the running total. When this
// appears will determine, for example, whether filtered
// transactions are included or excluded from the running total.
ptrs.push_back(formatter = new calc_transactions(formatter));
// reconcile_transactions will pass through only those
// transactions which can be reconciled to a given balance
// (calculated against the transactions which it receives).
if (! config.reconcile_balance.empty()) {
value_t target_balance(config.reconcile_balance);
time_t cutoff = now;
if (! config.reconcile_date.empty())
parse_date(config.reconcile_date.c_str(), &cutoff);
ptrs.push_back(formatter =
new reconcile_transactions(formatter, target_balance,
cutoff));
}
// sort_transactions will sort all the transactions it sees, based
// on the `sort_order' value expression.
if (! config.sort_string.empty())
ptrs.push_back(formatter =
new sort_transactions(formatter, config.sort_string));
// changed_value_transactions adds virtual transactions to the
// list to account for changes in market value of commodities,
// which otherwise would affect the running total unpredictably.
if (config.show_revalued)
ptrs.push_back(formatter =
new changed_value_transactions(formatter,
config.show_revalued_only));
// collapse_transactions causes entries with multiple transactions
// to appear as entries with a subtotaled transaction for each
// commodity used.
if (config.show_collapsed)
ptrs.push_back(formatter = new collapse_transactions(formatter));
}
// subtotal_transactions combines all the transactions it receives
// into one subtotal entry, which has one transaction for each
// commodity in each account.
//
// period_transactions is like subtotal_transactions, but it
// subtotals according to time periods rather than totalling
// everything.
//
// dow_transactions is like period_transactions, except that it
// reports all the transactions that fall on each subsequent day
// of the week.
if (config.show_subtotal && ! (command == "b" || command == "E"))
ptrs.push_back(formatter = new subtotal_transactions(formatter));
if (config.days_of_the_week)
ptrs.push_back(formatter = new dow_transactions(formatter));
else if (config.by_payee)
ptrs.push_back(formatter = new by_payee_transactions(formatter));
if (! config.report_period.empty()) {
ptrs.push_back(formatter =
new interval_transactions(formatter,
config.report_period,
config.report_period_sort));
ptrs.push_back(formatter = new sort_transactions(formatter, "d"));
}
// invert_transactions inverts the value of the transactions it
// receives.
if (config.show_inverted)
ptrs.push_back(formatter = new invert_transactions(formatter));
// related_transactions will pass along all transactions related
// to the transaction received. If `show_all_related' is true,
// then all the entry's transactions are passed; meaning that if
// one transaction of an entry is to be printed, all the
// transaction for that entry will be printed.
if (config.show_related)
ptrs.push_back(formatter =
new related_transactions(formatter,
config.show_all_related));
// This filter_transactions will only pass through transactions
// matching the `predicate'.
if (! config.predicate.empty())
ptrs.push_back(formatter = new filter_transactions(formatter,
config.predicate));
// budget_transactions takes a set of transactions from a data
// file and uses them to generate "budget transactions" which
// balance against the reported transactions.
//
// forecast_transactions is a lot like budget_transactions, except
// that it adds entries only for the future, and does not balance
// them against anything but the future balance.
if (config.budget_flags) {
// Don't generate a cache file after calculating a budget report,
// since certain intermediary accounts may get created which
// aren't intended to be saved. For example, the user might have
// an "Expenses" budget, to catch all other expenses. This will
// result in an "Expenses" account being created in the journal --
// to reflect the calculated totals -- even though no such account
// was ever actually used. Because budgeting and forecasting
// might create such "ghost" accounts for internal purposes, we
// don't want to change the cache.
config.use_cache = false;
budget_transactions * handler
= new budget_transactions(formatter, config.budget_flags);
handler->add_period_entries(journal->period_entries);
ptrs.push_back(formatter = handler);
// Apply this before the budget handler, so that only matching
// transactions are calculated toward the budget. The use of
// filter_transactions above will further clean the results so
// that no automated transactions that don't match the filter get
// reported.
if (! config.predicate.empty())
ptrs.push_back(formatter = new filter_transactions(formatter,
config.predicate));
}
else if (! config.forecast_limit.empty()) {
config.use_cache = false; // see note above
forecast_transactions * handler
= new forecast_transactions(formatter, config.forecast_limit);
handler->add_period_entries(journal->period_entries);
ptrs.push_back(formatter = handler);
// See above, under budget_transactions.
if (! config.predicate.empty())
ptrs.push_back(formatter = new filter_transactions(formatter,
config.predicate));
}
if (config.comm_as_payee)
ptrs.push_back(formatter = new set_comm_as_payee(formatter));
return formatter;
} }
int parse_and_report(int argc, char * argv[], char * envp[]) int parse_and_report(int argc, char * argv[], char * envp[])
{ {
TIMER_START(setup);
config_t config;
std::auto_ptr<journal_t> journal(new journal_t); std::auto_ptr<journal_t> journal(new journal_t);
// Parse command-line arguments, and those set in the environment // Parse command-line arguments, and those set in the environment
std::list<std::string> args; std::list<std::string> args;
process_arguments(config_options, argc - 1, argv + 1, false, args); config.process_arguments(argc - 1, argv + 1, false, args);
if (args.empty()) { if (args.empty()) {
option_help(std::cerr); option_help(std::cerr);
@ -252,19 +58,19 @@ int parse_and_report(int argc, char * argv[], char * envp[])
config.use_cache = config.data_file.empty() && config.price_db.empty(); config.use_cache = config.data_file.empty() && config.price_db.empty();
DEBUG_PRINT("ledger.config.cache", "1. use_cache = " << config.use_cache); DEBUG_PRINT("ledger.config.cache", "1. use_cache = " << config.use_cache);
process_environment(config_options, envp, "LEDGER_"); config.process_environment(envp, "LEDGER_");
#if 1 #if 1
// These are here for backwards compatability, but are deprecated. // These are here for backwards compatability, but are deprecated.
if (const char * p = std::getenv("LEDGER")) if (const char * p = std::getenv("LEDGER"))
process_option(config_options, "file", p); config.process_option("file", p);
if (const char * p = std::getenv("LEDGER_INIT")) if (const char * p = std::getenv("LEDGER_INIT"))
process_option(config_options, "init-file", p); config.process_option("init-file", p);
if (const char * p = std::getenv("PRICE_HIST")) if (const char * p = std::getenv("PRICE_HIST"))
process_option(config_options, "price-db", p); config.process_option("price-db", p);
if (const char * p = std::getenv("PRICE_EXP")) if (const char * p = std::getenv("PRICE_EXP"))
process_option(config_options, "price-exp", p); config.process_option("price-exp", p);
#endif #endif
const char * p = std::getenv("HOME"); const char * p = std::getenv("HOME");
@ -311,38 +117,22 @@ int parse_and_report(int argc, char * argv[], char * envp[])
else else
throw error(std::string("Unrecognized command '") + command + "'"); throw error(std::string("Unrecognized command '") + command + "'");
TIMER_STOP(setup);
// Parse initialization files, ledger data, price database, etc. // Parse initialization files, ledger data, price database, etc.
std::auto_ptr<binary_parser_t> bin_parser(new binary_parser_t); TIMER_START(parse);
#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
std::auto_ptr<xml_parser_t> xml_parser(new xml_parser_t);
std::auto_ptr<gnucash_parser_t> gnucash_parser(new gnucash_parser_t);
#endif
#ifdef HAVE_LIBOFX
std::auto_ptr<ofx_parser_t> ofx_parser(new ofx_parser_t);
#endif
std::auto_ptr<qif_parser_t> qif_parser(new qif_parser_t);
std::auto_ptr<textual_parser_t> text_parser(new textual_parser_t);
register_parser(bin_parser.get()); if (parse_ledger_data(journal.get(), config) == 0)
#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) throw error("Please specify ledger file using -f"
register_parser(xml_parser.get()); " or LEDGER_FILE environment variable.");
register_parser(gnucash_parser.get());
#endif
#ifdef HAVE_LIBOFX
register_parser(ofx_parser.get());
#endif
register_parser(qif_parser.get());
register_parser(text_parser.get());
parse_ledger_data(journal.get(), bin_parser.get(), text_parser.get() TIMER_STOP(parse);
#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
, xml_parser.get()
#endif
);
// process the command word and its following arguments // process the command word and its following arguments
TIMER_START(process);
std::string first_arg; std::string first_arg;
if (command == "w") { if (command == "w") {
if (arg == args.end()) if (arg == args.end())
@ -461,8 +251,12 @@ def vmax(d, val):\n\
#endif // USE_BOOST_PYTHON #endif // USE_BOOST_PYTHON
TIMER_STOP(process);
// Walk the entries based on the report type and the options // Walk the entries based on the report type and the options
TIMER_START(walk);
item_handler<transaction_t> * formatter; item_handler<transaction_t> * formatter;
std::list<item_handler<transaction_t> *> formatter_ptrs; std::list<item_handler<transaction_t> *> formatter_ptrs;
@ -482,10 +276,11 @@ def vmax(d, val):\n\
formatter = new format_transactions(*out, *format); formatter = new format_transactions(*out, *format);
if (command == "w") { if (command == "w") {
write_textual_journal(*journal, first_arg, *formatter, *out); write_textual_journal(*journal, first_arg, *formatter,
config.write_hdr_format, *out);
} else { } else {
formatter = chain_xact_handlers(command, formatter, journal.get(), formatter = config.chain_xact_handlers(command, formatter, journal.get(),
journal->master, formatter_ptrs); journal->master, formatter_ptrs);
if (command == "e") if (command == "e")
walk_transactions(new_entry->transactions, *formatter); walk_transactions(new_entry->transactions, *formatter);
else if (command == "P" || command == "D") else if (command == "P" || command == "D")
@ -521,8 +316,16 @@ def vmax(d, val):\n\
acct_formatter.flush(); acct_formatter.flush();
} }
TIMER_STOP(walk);
TIMER_START(cleanup);
#if DEBUG_LEVEL >= BETA #if DEBUG_LEVEL >= BETA
clear_all_xdata(); clear_transaction_xdata xact_cleaner;
walk_entries(journal->entries, xact_cleaner);
clear_account_xdata acct_cleaner;
walk_accounts(*journal->master, acct_cleaner);
if (! config.output_file.empty()) if (! config.output_file.empty())
delete out; delete out;
@ -554,13 +357,13 @@ def vmax(d, val):\n\
} }
#endif #endif
TIMER_STOP(cleanup);
return 0; return 0;
} }
int main(int argc, char * argv[], char * envp[]) int main(int argc, char * argv[], char * envp[])
{ {
std::ios::sync_with_stdio(false);
try { try {
return parse_and_report(argc, argv, envp); return parse_and_report(argc, argv, envp);
} }

138
parser.cc
View file

@ -1,5 +1,6 @@
#include "parser.h" #include "parser.h"
#include "journal.h" #include "journal.h"
#include "config.h"
#include <fstream> #include <fstream>
#ifdef WIN32 #ifdef WIN32
@ -12,18 +13,31 @@ namespace ledger {
typedef std::list<parser_t *> parsers_list; typedef std::list<parser_t *> parsers_list;
static parsers_list parsers; static parsers_list * parsers = NULL;
void initialize_parser_support()
{
parsers = new parsers_list;
}
void shutdown_parser_support()
{
if (parsers) {
delete parsers;
parsers = NULL;
}
}
bool register_parser(parser_t * parser) bool register_parser(parser_t * parser)
{ {
parsers_list::iterator i; parsers_list::iterator i;
for (i = parsers.begin(); i != parsers.end(); i++) for (i = parsers->begin(); i != parsers->end(); i++)
if (*i == parser) if (*i == parser)
break; break;
if (i != parsers.end()) if (i != parsers->end())
return false; return false;
parsers.push_back(parser); parsers->push_back(parser);
return true; return true;
} }
@ -31,13 +45,13 @@ bool register_parser(parser_t * parser)
bool unregister_parser(parser_t * parser) bool unregister_parser(parser_t * parser)
{ {
parsers_list::iterator i; parsers_list::iterator i;
for (i = parsers.begin(); i != parsers.end(); i++) for (i = parsers->begin(); i != parsers->end(); i++)
if (*i == parser) if (*i == parser)
break; break;
if (i == parsers.end()) if (i == parsers->end())
return false; return false;
parsers.erase(i); parsers->erase(i);
return true; return true;
} }
@ -50,8 +64,8 @@ unsigned int parse_journal(std::istream& in,
if (! master) if (! master)
master = journal->master; master = journal->master;
for (parsers_list::iterator i = parsers.begin(); for (parsers_list::iterator i = parsers->begin();
i != parsers.end(); i != parsers->end();
i++) i++)
if ((*i)->test(in)) if ((*i)->test(in))
return (*i)->parse(in, journal, master, original_file); return (*i)->parse(in, journal, master, original_file);
@ -76,6 +90,106 @@ unsigned int parse_journal_file(const std::string& path,
return parse_journal(stream, journal, master, original_file); return parse_journal(stream, journal, master, original_file);
} }
unsigned int parse_ledger_data(journal_t * journal,
const std::string& data_file,
const std::string& init_file,
const std::string& price_db,
bool use_cache,
const std::string& cache_file,
bool * cache_dirty,
parser_t * cache_parser,
parser_t * xml_parser,
parser_t * stdin_parser,
const std::string& default_account)
{
unsigned int entry_count = 0;
DEBUG_PRINT("ledger.config.cache", "3. use_cache = " << use_cache);
if (! init_file.empty() && access(init_file.c_str(), R_OK) != -1) {
if (parse_journal_file(init_file, journal) ||
journal->auto_entries.size() > 0 ||
journal->period_entries.size() > 0)
throw error(std::string("Entries found in initialization file '") +
init_file + "'");
journal->sources.pop_front(); // remove init file
}
if (use_cache && ! cache_file.empty() && ! data_file.empty()) {
DEBUG_PRINT("ledger.config.cache", "using_cache " << cache_file);
if (cache_dirty)
*cache_dirty = true;
if (access(cache_file.c_str(), R_OK) != -1) {
std::ifstream stream(cache_file.c_str());
if (cache_parser && cache_parser->test(stream)) {
std::string price_db_orig = journal->price_db;
journal->price_db = price_db;
entry_count += cache_parser->parse(stream, journal, NULL, &data_file);
if (entry_count > 0) {
if (cache_dirty)
*cache_dirty = false;
} else {
journal->price_db = price_db_orig;
}
}
}
}
if (entry_count == 0 && ! data_file.empty()) {
account_t * acct = NULL;
if (! default_account.empty())
acct = journal->find_account(default_account);
journal->price_db = price_db;
if (! journal->price_db.empty() &&
access(journal->price_db.c_str(), R_OK) != -1) {
if (parse_journal_file(journal->price_db, journal)) {
throw error("Entries not allowed in price history file");
} else {
DEBUG_PRINT("ledger.config.cache",
"read price database " << journal->price_db);
journal->sources.pop_back();
}
}
DEBUG_PRINT("ledger.config.cache",
"rejected cache, parsing " << data_file);
if (data_file == "-") {
use_cache = false;
journal->sources.push_back("<stdin>");
#if 0
if (xml_parser && std::cin.peek() == '<')
entry_count += xml_parser->parse(std::cin, journal, acct);
else if (stdin_parser)
#endif
entry_count += stdin_parser->parse(std::cin, journal, acct);
}
else if (access(data_file.c_str(), R_OK) != -1) {
entry_count += parse_journal_file(data_file, journal, acct);
if (! journal->price_db.empty())
journal->sources.push_back(journal->price_db);
}
}
VALIDATE(journal->valid());
return entry_count;
}
extern parser_t * binary_parser_ptr;
extern parser_t * xml_parser_ptr;
extern parser_t * textual_parser_ptr;
unsigned int parse_ledger_data(journal_t * journal, config_t& config)
{
return parse_ledger_data(journal, config.data_file, config.init_file,
config.price_db, config.use_cache,
config.cache_file, &config.cache_dirty,
binary_parser_ptr, xml_parser_ptr,
textual_parser_ptr, config.account);
}
} // namespace ledger } // namespace ledger
#ifdef USE_BOOST_PYTHON #ifdef USE_BOOST_PYTHON
@ -108,6 +222,9 @@ BOOST_PYTHON_FUNCTION_OVERLOADS(parse_journal_overloads, parse_journal, 2, 4)
BOOST_PYTHON_FUNCTION_OVERLOADS(parse_journal_file_overloads, BOOST_PYTHON_FUNCTION_OVERLOADS(parse_journal_file_overloads,
parse_journal_file, 2, 4) parse_journal_file, 2, 4)
BOOST_PYTHON_FUNCTION_OVERLOADS(parse_ledger_data_overloads,
parse_ledger_data, 1, 2)
void export_parser() { void export_parser() {
class_< parser_t, parser_wrap, boost::noncopyable > ("Parser") class_< parser_t, parser_wrap, boost::noncopyable > ("Parser")
; ;
@ -116,6 +233,9 @@ void export_parser() {
def("unregister_parser", unregister_parser); def("unregister_parser", unregister_parser);
def("parse_journal", parse_journal, parse_journal_overloads()); def("parse_journal", parse_journal, parse_journal_overloads());
def("parse_journal_file", parse_journal_file, parse_journal_file_overloads()); def("parse_journal_file", parse_journal_file, parse_journal_file_overloads());
#if 0
def("parse_ledger_data", parse_ledger_data, parse_ledger_data_overloads());
#endif
} }
#endif // USE_BOOST_PYTHON #endif // USE_BOOST_PYTHON

View file

@ -35,6 +35,24 @@ unsigned int parse_journal_file(const std::string& path,
account_t * master = NULL, account_t * master = NULL,
const std::string * original_file = NULL); const std::string * original_file = NULL);
unsigned int parse_ledger_data(journal_t * journal,
const std::string& data_file,
const std::string& init_file = "",
const std::string& price_db = "",
bool use_cache = false,
const std::string& cache_file = "",
bool * cache_dirty = NULL,
parser_t * cache_parser = NULL,
parser_t * xml_parser = NULL,
parser_t * stdin_parser = NULL,
const std::string& default_account = "");
class config_t;
unsigned int parse_ledger_data(journal_t * journal, config_t& config);
void initialize_parser_support();
void shutdown_parser_support();
} // namespace ledger } // namespace ledger
#endif // _PARSER_H #endif // _PARSER_H

54
startup.cc Normal file
View file

@ -0,0 +1,54 @@
#include "ledger.h"
using namespace ledger;
namespace ledger {
parser_t * binary_parser_ptr = NULL;
parser_t * xml_parser_ptr = NULL;
parser_t * gnucash_parser_ptr = NULL;
parser_t * ofx_parser_ptr = NULL;
parser_t * qif_parser_ptr = NULL;
parser_t * textual_parser_ptr = NULL;
}
namespace {
binary_parser_t binary_parser;
#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
xml_parser_t xml_parser;
gnucash_parser_t gnucash_parser;
#endif
#ifdef HAVE_LIBOFX
ofx_parser_t ofx_parser;
#endif
qif_parser_t qif_parser;
textual_parser_t textual_parser;
static class startup {
public:
startup();
~startup();
} _startup;
startup::startup()
{
std::ios::sync_with_stdio(false);
initialize_parser_support();
register_parser(&binary_parser); binary_parser_ptr = &binary_parser;
#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
register_parser(&xml_parser); xml_parser_ptr = &xml_parser;
register_parser(&gnucash_parser); gnucash_parser_ptr = &gnucash_parser;
#endif
#ifdef HAVE_LIBOFX
register_parser(&ofx_parser); ofx_parser_ptr = &ofx_parser;
#endif
register_parser(&qif_parser); qif_parser_ptr = &qif_parser;
register_parser(&textual_parser); textual_parser_ptr = &textual_parser;
}
startup::~startup()
{
shutdown_parser_support();
}
}

View file

@ -211,7 +211,7 @@ transaction_t * parse_transaction(char * line, account_t * account)
if (amount == note_str) if (amount == note_str)
amount = NULL; amount = NULL;
*note_str++ = '\0'; *note_str++ = '\0';
note_str = skip_ws(note_str); note_str = skip_ws(note_str);
if (char * b = std::strchr(note_str, '[')) if (char * b = std::strchr(note_str, '['))
@ -231,22 +231,22 @@ transaction_t * parse_transaction(char * line, account_t * account)
throw parse_error(path, linenum, "Failed to parse date"); throw parse_error(path, linenum, "Failed to parse date");
} }
xact->note = skip_ws(note_str); xact->note = skip_ws(note_str);
} }
if (amount) { if (amount) {
price = std::strchr(amount, '@'); price = std::strchr(amount, '@');
if (price) { if (price) {
if (price == amount) if (price == amount)
throw parse_error(path, linenum, "Cost specified without amount"); throw parse_error(path, linenum, "Cost specified without amount");
*price++ = '\0'; *price++ = '\0';
if (*price == '@') { if (*price == '@') {
per_unit = false; per_unit = false;
price++; price++;
} }
price = skip_ws(price); price = skip_ws(price);
} }
} }
} }
} }
@ -321,7 +321,6 @@ bool parse_transactions(std::istream& in,
} }
namespace { namespace {
TIMER_DEF(entry_finish, "finalizing entry");
TIMER_DEF(entry_xacts, "parsing transactions"); TIMER_DEF(entry_xacts, "parsing transactions");
TIMER_DEF(entry_details, "parsing entry details"); TIMER_DEF(entry_details, "parsing entry details");
TIMER_DEF(entry_date, "parsing entry date"); TIMER_DEF(entry_date, "parsing entry date");
@ -817,6 +816,7 @@ unsigned int textual_parser_t::parse(std::istream& in,
void write_textual_journal(journal_t& journal, std::string path, void write_textual_journal(journal_t& journal, std::string path,
item_handler<transaction_t>& formatter, item_handler<transaction_t>& formatter,
const std::string& write_hdr_format,
std::ostream& out) std::ostream& out)
{ {
unsigned long index = 0; unsigned long index = 0;
@ -859,7 +859,7 @@ void write_textual_journal(journal_t& journal, std::string path,
istream_pos_type pos = 0; istream_pos_type pos = 0;
istream_pos_type jump_to; istream_pos_type jump_to;
format_t hdr_fmt(config.write_hdr_format); format_t hdr_fmt(write_hdr_format);
std::ifstream in(found.c_str()); std::ifstream in(found.c_str());
while (! in.eof()) { while (! in.eof()) {

View file

@ -2,6 +2,7 @@
#define _TEXTUAL_H #define _TEXTUAL_H
#include "parser.h" #include "parser.h"
#include "format.h"
#include "walk.h" #include "walk.h"
namespace ledger { namespace ledger {
@ -22,6 +23,7 @@ transaction_t * parse_transaction(std::istream& in, account_t * account);
void write_textual_journal(journal_t& journal, std::string path, void write_textual_journal(journal_t& journal, std::string path,
item_handler<transaction_t>& formatter, item_handler<transaction_t>& formatter,
const std::string& write_hdr_format,
std::ostream& out); std::ostream& out);
} // namespace ledger } // namespace ledger

View file

@ -41,10 +41,12 @@ class timing_t
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED
#define TIMER_DEF(sym, cat) static timing_t sym(#sym, cat) #define TIMER_DEF(sym, cat) static timing_t sym(#sym, cat)
#define TIMER_DEF_(sym) static timing_t sym(#sym, #sym)
#define TIMER_START(sym) sym.start(__FILE__, __LINE__) #define TIMER_START(sym) sym.start(__FILE__, __LINE__)
#define TIMER_STOP(sym) sym.stop() #define TIMER_STOP(sym) sym.stop()
#else #else
#define TIMER_DEF(sym, cat) #define TIMER_DEF(sym, cat)
#define TIMER_DEF_(sym)
#define TIMER_START(sym) #define TIMER_START(sym)
#define TIMER_STOP(sym) #define TIMER_STOP(sym)
#endif #endif

View file

@ -15,6 +15,12 @@ std::auto_ptr<value_expr_t> total_expr;
std::time_t terminus = now; std::time_t terminus = now;
details_t::details_t(const transaction_t& _xact)
: entry(_xact.entry), xact(&_xact), account(xact_account(_xact))
{
DEBUG_PRINT("ledger.memory.ctors", "ctor details_t");
}
void value_expr_t::compute(value_t& result, const details_t& details) const void value_expr_t::compute(value_t& result, const details_t& details) const
{ {
switch (kind) { switch (kind) {

View file

@ -20,10 +20,7 @@ struct details_t
: entry(&_entry), xact(NULL), account(NULL) { : entry(&_entry), xact(NULL), account(NULL) {
DEBUG_PRINT("ledger.memory.ctors", "ctor details_t"); DEBUG_PRINT("ledger.memory.ctors", "ctor details_t");
} }
details_t(const transaction_t& _xact) details_t(const transaction_t& _xact);
: entry(_xact.entry), xact(&_xact), account(_xact.account) {
DEBUG_PRINT("ledger.memory.ctors", "ctor details_t");
}
details_t(const account_t& _account) details_t(const account_t& _account)
: entry(NULL), xact(NULL), account(&_account) { : entry(NULL), xact(NULL), account(&_account) {
DEBUG_PRINT("ledger.memory.ctors", "ctor details_t"); DEBUG_PRINT("ledger.memory.ctors", "ctor details_t");
@ -127,7 +124,6 @@ struct value_expr_t
extern std::auto_ptr<value_expr_t> amount_expr; extern std::auto_ptr<value_expr_t> amount_expr;
extern std::auto_ptr<value_expr_t> total_expr; extern std::auto_ptr<value_expr_t> total_expr;
extern std::time_t terminus; extern std::time_t terminus;
inline void compute_amount(value_t& result, const details_t& details) { inline void compute_amount(value_t& result, const details_t& details) {

93
walk.cc
View file

@ -7,12 +7,6 @@
namespace ledger { namespace ledger {
std::list<transaction_xdata_t> transactions_xdata;
std::list<void **> transactions_xdata_ptrs;
std::list<account_xdata_t> accounts_xdata;
std::list<void **> accounts_xdata_ptrs;
template <> template <>
bool compare_items<transaction_t>::operator()(const transaction_t * left, bool compare_items<transaction_t>::operator()(const transaction_t * left,
const transaction_t * right) const transaction_t * right)
@ -37,11 +31,8 @@ bool compare_items<transaction_t>::operator()(const transaction_t * left,
transaction_xdata_t& transaction_xdata(const transaction_t& xact) transaction_xdata_t& transaction_xdata(const transaction_t& xact)
{ {
if (! xact.data) { if (! xact.data)
transactions_xdata.push_back(transaction_xdata_t()); xact.data = new transaction_xdata_t();
xact.data = &transactions_xdata.back();
transactions_xdata_ptrs.push_back(&xact.data);
}
return *((transaction_xdata_t *) xact.data); return *((transaction_xdata_t *) xact.data);
} }
@ -109,7 +100,10 @@ void truncate_entries::flush()
void set_account_value::operator()(transaction_t& xact) void set_account_value::operator()(transaction_t& xact)
{ {
account_xdata_t& xdata = account_xdata(*xact.account); account_t * acct = xact_account(xact);
assert(acct);
account_xdata_t& xdata = account_xdata(*acct);
add_transaction_to(xact, xdata.value); add_transaction_to(xact, xdata.value);
xdata.count++; xdata.count++;
@ -370,7 +364,7 @@ void subtotal_transactions::operator()(transaction_t& xact)
if (! finish || std::difftime(xact.date(), finish) > 0) if (! finish || std::difftime(xact.date(), finish) > 0)
finish = xact.date(); finish = xact.date();
account_t * acct = xact.account; account_t * acct = xact_account(xact);
assert(acct); assert(acct);
values_map::iterator i = values.find(acct->fullname()); values_map::iterator i = values.find(acct->fullname());
@ -387,9 +381,9 @@ void subtotal_transactions::operator()(transaction_t& xact)
// that contain only virtual transactions. // that contain only virtual transactions.
if (! (xact.flags & TRANSACTION_VIRTUAL)) if (! (xact.flags & TRANSACTION_VIRTUAL))
account_xdata(*xact.account).dflags |= ACCOUNT_HAS_NON_VIRTUALS; account_xdata(*xact_account(xact)).dflags |= ACCOUNT_HAS_NON_VIRTUALS;
else if (! (xact.flags & TRANSACTION_BALANCE)) else if (! (xact.flags & TRANSACTION_BALANCE))
account_xdata(*xact.account).dflags |= ACCOUNT_HAS_UNB_VIRTUALS; account_xdata(*xact_account(xact)).dflags |= ACCOUNT_HAS_UNB_VIRTUALS;
} }
void interval_transactions::report_subtotal(const std::time_t moment) void interval_transactions::report_subtotal(const std::time_t moment)
@ -559,10 +553,10 @@ void budget_transactions::report_budget_items(const std::time_t moment)
if (std::difftime(begin, moment) < 0 && if (std::difftime(begin, moment) < 0 &&
(! (*i).first.end || std::difftime(begin, (*i).first.end) < 0)) { (! (*i).first.end || std::difftime(begin, (*i).first.end) < 0)) {
transaction_t& xact = *(*i).second; transaction_t& xact = *(*i).second;
DEBUG_PRINT("ledger.walk.budget", "Reporting budget for " DEBUG_PRINT("ledger.walk.budget", "Reporting budget for "
<< xact.account->fullname()); << xact_account(xact)->fullname());
DEBUG_PRINT_TIME("ledger.walk.budget", begin); DEBUG_PRINT_TIME("ledger.walk.budget", begin);
DEBUG_PRINT_TIME("ledger.walk.budget", moment); DEBUG_PRINT_TIME("ledger.walk.budget", moment);
@ -573,10 +567,9 @@ void budget_transactions::report_budget_items(const std::time_t moment)
xact_temps.push_back(xact); xact_temps.push_back(xact);
transaction_t& temp = xact_temps.back(); transaction_t& temp = xact_temps.back();
temp.entry = &entry; temp.entry = &entry;
temp.flags |= TRANSACTION_AUTO; temp.flags |= TRANSACTION_AUTO | TRANSACTION_BULK_ALLOC;
temp.amount.negate(); temp.amount.negate();
temp.flags |= TRANSACTION_BULK_ALLOC;
entry.add_transaction(&temp); entry.add_transaction(&temp);
begin = (*i).first.increment(begin); begin = (*i).first.increment(begin);
@ -596,15 +589,15 @@ void budget_transactions::operator()(transaction_t& xact)
for (pending_xacts_list::iterator i = pending_xacts.begin(); for (pending_xacts_list::iterator i = pending_xacts.begin();
i != pending_xacts.end(); i != pending_xacts.end();
i++) i++)
for (account_t * acct = xact.account; acct; acct = acct->parent) { for (account_t * acct = xact_account(xact);
if (acct == (*i).second->account) { acct;
acct = acct->parent) {
if (acct == xact_account(*(*i).second)) {
xact_in_budget = true; xact_in_budget = true;
// Report the transaction as if it had occurred in the parent // Report the transaction as if it had occurred in the parent
// account. jww (2005-07-13): Note that this assignment will // account.
// irrevocably change the underlying transaction. if (xact_account(xact) != acct)
if (xact.account != acct) transaction_xdata(xact).account = acct;
xact.account = acct;
goto handle; goto handle;
} }
} }
@ -705,17 +698,6 @@ void forecast_transactions::flush()
item_handler<transaction_t>::flush(); item_handler<transaction_t>::flush();
} }
void clear_transactions_xdata()
{
transactions_xdata.clear();
for (std::list<void **>::iterator i = transactions_xdata_ptrs.begin();
i != transactions_xdata_ptrs.end();
i++)
**i = NULL;
transactions_xdata_ptrs.clear();
}
template <> template <>
bool compare_items<account_t>::operator()(const account_t * left, bool compare_items<account_t>::operator()(const account_t * left,
const account_t * right) const account_t * right)
@ -740,11 +722,9 @@ bool compare_items<account_t>::operator()(const account_t * left,
account_xdata_t& account_xdata(const account_t& account) account_xdata_t& account_xdata(const account_t& account)
{ {
if (! account.data) { if (! account.data)
accounts_xdata.push_back(account_xdata_t()); account.data = new account_xdata_t();
account.data = &accounts_xdata.back();
accounts_xdata_ptrs.push_back(&account.data);
}
return *((account_xdata_t *) account.data); return *((account_xdata_t *) account.data);
} }
@ -824,18 +804,6 @@ void walk_accounts(account_t& account,
} }
} }
void clear_accounts_xdata()
{
accounts_xdata.clear();
for (std::list<void **>::iterator i = accounts_xdata_ptrs.begin();
i != accounts_xdata_ptrs.end();
i++)
**i = NULL;
accounts_xdata_ptrs.clear();
}
void walk_commodities(commodities_map& commodities, void walk_commodities(commodities_map& commodities,
item_handler<transaction_t>& handler) item_handler<transaction_t>& handler)
{ {
@ -972,7 +940,6 @@ void export_walk()
def("transaction_has_xdata", transaction_has_xdata); def("transaction_has_xdata", transaction_has_xdata);
def("transaction_xdata", transaction_xdata, return_internal_reference<1>()); def("transaction_xdata", transaction_xdata, return_internal_reference<1>());
def("clear_transactions_xdata", clear_transactions_xdata);
def("add_transaction_to", add_transaction_to); def("add_transaction_to", add_transaction_to);
class_< xact_handler_t, item_handler_wrap<transaction_t> > class_< xact_handler_t, item_handler_wrap<transaction_t> >
@ -991,6 +958,12 @@ void export_walk()
.def("__call__", &ignore_transactions::operator()); .def("__call__", &ignore_transactions::operator());
; ;
class_< clear_transaction_xdata, bases<xact_handler_t> >
("ClearTransactionXData")
.def("flush", &xact_handler_t::flush)
.def("__call__", &clear_transaction_xdata::operator());
;
class_< truncate_entries, bases<xact_handler_t> > class_< truncate_entries, bases<xact_handler_t> >
("TruncateEntries", init<xact_handler_t *, int, int>() ("TruncateEntries", init<xact_handler_t *, int, int>()
[with_custodian_and_ward<1, 2>()]) [with_custodian_and_ward<1, 2>()])
@ -1149,8 +1122,6 @@ void export_walk()
def("account_has_xdata", account_has_xdata); def("account_has_xdata", account_has_xdata);
def("account_xdata", account_xdata, return_internal_reference<1>()); def("account_xdata", account_xdata, return_internal_reference<1>());
def("clear_accounts_xdata", clear_accounts_xdata);
def("clear_all_xdata", clear_all_xdata);
class_< account_handler_t, item_handler_wrap<account_t> > class_< account_handler_t, item_handler_wrap<account_t> >
("AccountHandler") ("AccountHandler")
@ -1162,6 +1133,12 @@ void export_walk()
&item_handler_wrap<account_t>::default_call) &item_handler_wrap<account_t>::default_call)
; ;
class_< clear_account_xdata, bases<account_handler_t> >
("ClearAccountXData")
.def("flush", &account_handler_t::flush)
.def("__call__", &clear_account_xdata::operator());
;
def("sum_accounts", sum_accounts); def("sum_accounts", sum_accounts);
def("walk_accounts", py_walk_accounts_1); def("walk_accounts", py_walk_accounts_1);
def("walk_accounts", py_walk_accounts_2); def("walk_accounts", py_walk_accounts_2);

60
walk.h
View file

@ -91,18 +91,17 @@ struct transaction_xdata_t
unsigned int index; unsigned int index;
unsigned short dflags; unsigned short dflags;
std::time_t date; std::time_t date;
account_t * account;
void * ptr; void * ptr;
transaction_xdata_t() : index(0), dflags(0), date(0), ptr(0) {} transaction_xdata_t()
: index(0), dflags(0), date(0), account(0), ptr(0) {}
}; };
inline bool transaction_has_xdata(const transaction_t& xact) { inline bool transaction_has_xdata(const transaction_t& xact) {
return xact.data != NULL; return xact.data != NULL;
} }
extern std::list<transaction_xdata_t> transactions_xdata;
extern std::list<void **> transactions_xdata_ptrs;
inline transaction_xdata_t& transaction_xdata_(const transaction_t& xact) { inline transaction_xdata_t& transaction_xdata_(const transaction_t& xact) {
return *((transaction_xdata_t *) xact.data); return *((transaction_xdata_t *) xact.data);
} }
@ -110,6 +109,17 @@ inline transaction_xdata_t& transaction_xdata_(const transaction_t& xact) {
transaction_xdata_t& transaction_xdata(const transaction_t& xact); transaction_xdata_t& transaction_xdata(const transaction_t& xact);
void add_transaction_to(const transaction_t& xact, value_t& value); void add_transaction_to(const transaction_t& xact, value_t& value);
inline account_t * xact_account(transaction_t& xact) {
account_t * account = transaction_xdata(xact).account;
if (account)
return account;
return xact.account;
}
inline const account_t * xact_account(const transaction_t& xact) {
return xact_account(const_cast<transaction_t&>(xact));
}
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
inline void walk_transactions(transactions_list::iterator begin, inline void walk_transactions(transactions_list::iterator begin,
@ -136,8 +146,6 @@ inline void walk_entries(entries_list& list,
walk_entries(list.begin(), list.end(), handler); walk_entries(list.begin(), list.end(), handler);
} }
void clear_transactions_xdata();
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
class ignore_transactions : public item_handler<transaction_t> class ignore_transactions : public item_handler<transaction_t>
@ -146,6 +154,17 @@ class ignore_transactions : public item_handler<transaction_t>
virtual void operator()(transaction_t& xact) {} virtual void operator()(transaction_t& xact) {}
}; };
class clear_transaction_xdata : public item_handler<transaction_t>
{
public:
virtual void operator()(transaction_t& xact) {
if (xact.data) {
delete (transaction_xdata_t *) xact.data;
xact.data = NULL;
}
}
};
class truncate_entries : public item_handler<transaction_t> class truncate_entries : public item_handler<transaction_t>
{ {
int head_count; int head_count;
@ -594,9 +613,6 @@ inline bool account_has_xdata(const account_t& account) {
return account.data != NULL; return account.data != NULL;
} }
extern std::list<account_xdata_t> accounts_xdata;
extern std::list<void **> accounts_xdata_ptrs;
inline account_xdata_t& account_xdata_(const account_t& account) { inline account_xdata_t& account_xdata_(const account_t& account) {
return *((account_xdata_t *) account.data); return *((account_xdata_t *) account.data);
} }
@ -605,6 +621,17 @@ account_xdata_t& account_xdata(const account_t& account);
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
class clear_account_xdata : public item_handler<account_t>
{
public:
virtual void operator()(account_t& acct) {
if (acct.data) {
delete (account_xdata_t *) acct.data;
acct.data = NULL;
}
}
};
void sum_accounts(account_t& account); void sum_accounts(account_t& account);
typedef std::deque<account_t *> accounts_deque; typedef std::deque<account_t *> accounts_deque;
@ -619,18 +646,19 @@ void walk_accounts(account_t& account,
item_handler<account_t>& handler, item_handler<account_t>& handler,
const std::string& sort_string); const std::string& sort_string);
void clear_accounts_xdata();
inline void clear_all_xdata() {
clear_transactions_xdata();
clear_accounts_xdata();
}
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
void walk_commodities(commodities_map& commodities, void walk_commodities(commodities_map& commodities,
item_handler<transaction_t>& handler); item_handler<transaction_t>& handler);
inline void clear_journal_xdata(journal_t * journal) {
clear_transaction_xdata xact_cleaner;
walk_entries(journal->entries, xact_cleaner);
clear_account_xdata acct_cleaner;
walk_accounts(*journal->master, acct_cleaner);
}
} // namespace ledger } // namespace ledger
#endif // _WALK_H #endif // _WALK_H