diff options
-rw-r--r-- | Makefile.am | 47 | ||||
-rw-r--r-- | NEWS | 3 | ||||
-rwxr-xr-x | acprep | 1 | ||||
-rw-r--r-- | config.cc | 545 | ||||
-rw-r--r-- | config.h | 42 | ||||
-rw-r--r-- | configure.in | 5 | ||||
-rw-r--r-- | derive.cc | 2 | ||||
-rw-r--r-- | emacs.cc | 2 | ||||
-rw-r--r-- | format.cc | 10 | ||||
-rw-r--r-- | journal.cc | 10 | ||||
-rw-r--r-- | ledger.el | 10 | ||||
-rw-r--r-- | ledger.h | 12 | ||||
-rw-r--r-- | main.cc | 295 | ||||
-rw-r--r-- | parser.cc | 138 | ||||
-rw-r--r-- | parser.h | 18 | ||||
-rw-r--r-- | startup.cc | 54 | ||||
-rw-r--r-- | textual.cc | 18 | ||||
-rw-r--r-- | textual.h | 2 | ||||
-rw-r--r-- | timing.h | 2 | ||||
-rw-r--r-- | valexpr.cc | 6 | ||||
-rw-r--r-- | valexpr.h | 6 | ||||
-rw-r--r-- | walk.cc | 93 | ||||
-rw-r--r-- | walk.h | 60 |
23 files changed, 756 insertions, 625 deletions
diff --git a/Makefile.am b/Makefile.am index 09ddbd35..bdc1f868 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,6 +1,6 @@ -lib_LIBRARIES = libledger.a -libledger_a_CXXFLAGS = -libledger_a_SOURCES = \ +lib_LTLIBRARIES = libledger.la +libledger_la_CXXFLAGS = +libledger_la_SOURCES = \ amount.cc \ balance.cc \ binary.cc \ @@ -16,30 +16,32 @@ libledger_a_SOURCES = \ qif.cc \ quotes.cc \ reconcile.cc \ + startup.cc \ textual.cc \ valexpr.cc \ value.cc \ walk.cc if HAVE_EXPAT -libledger_a_CXXFLAGS += -DHAVE_EXPAT=1 -libledger_a_SOURCES += gnucash.cc xml.cc +libledger_la_CXXFLAGS += -DHAVE_EXPAT=1 +libledger_la_SOURCES += gnucash.cc xml.cc endif if HAVE_XMLPARSE -libledger_a_CXXFLAGS += -DHAVE_XMLPARSE=1 -libledger_a_SOURCES += gnucash.cc xml.cc +libledger_la_CXXFLAGS += -DHAVE_XMLPARSE=1 +libledger_la_SOURCES += gnucash.cc xml.cc endif if HAVE_LIBOFX -libledger_a_CXXFLAGS += -DHAVE_LIBOFX=1 -libledger_a_SOURCES += ofx.cc +libledger_la_CXXFLAGS += -DHAVE_LIBOFX=1 +libledger_la_SOURCES += ofx.cc endif if HAVE_BOOST_PYTHON -libledger_a_CXXFLAGS += -DUSE_BOOST_PYTHON=1 -libledger_a_SOURCES += py_eval.cc +libledger_la_CXXFLAGS += -DUSE_BOOST_PYTHON=1 +libledger_la_SOURCES += py_eval.cc endif if DEBUG -libledger_a_CXXFLAGS += -DDEBUG_LEVEL=4 -libledger_a_SOURCES += debug.cc +libledger_la_CXXFLAGS += -DDEBUG_LEVEL=4 +libledger_la_SOURCES += debug.cc endif +libledger_la_LDFLAGS = -version-info 3:0 pkginclude_HEADERS = \ acconf.h \ @@ -79,9 +81,9 @@ ledger_CXXFLAGS = ledger_SOURCES = main.cc if HAVE_BOOST_PYTHON 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 -ledger_LDADD = $(LIBOBJS) libledger.a +ledger_LDADD = $(LIBOBJS) libledger.la endif if HAVE_EXPAT ledger_CXXFLAGS += -DHAVE_EXPAT=1 @@ -98,6 +100,7 @@ endif if DEBUG ledger_CXXFLAGS += -DDEBUG_LEVEL=4 endif +ledger_LDFLAGS = -static # for the sake of command-line speed info_TEXINFOS = ledger.texi @@ -123,15 +126,15 @@ else HAVE_LIBOFX_VALUE = false endif -ledger.so: py_eval.cc libledger.a - CFLAGS="$(CPPFLAGS)" LDFLAGS="$(LDFLAGS) -L." \ +ledger.so: py_eval.cc libledger.la + CFLAGS="$(CPPFLAGS)" LDFLAGS="$(LDFLAGS) -L. -L.libs" \ HAVE_EXPAT="$(HAVE_EXPAT_VALUE)" \ HAVE_XMLPARSE="$(HAVE_XMLPARSE_VALUE)" \ HAVE_LIBOFX="$(HAVE_LIBOFX_VALUE)" \ python setup.py build --build-lib=. install-exec-hook: - CFLAGS="$(CPPFLAGS)" LDFLAGS="$(LDFLAGS) -L." \ + CFLAGS="$(CPPFLAGS)" LDFLAGS="$(LDFLAGS) -L. -L.libs" \ HAVE_EXPAT="$(HAVE_EXPAT_VALUE)" \ HAVE_XMLPARSE="$(HAVE_XMLPARSE_VALUE)" \ HAVE_LIBOFX="$(HAVE_LIBOFX_VALUE)" \ @@ -143,7 +146,7 @@ all-clean: maintainer-clean rm -fr *~ .*~ .\#* *.html *.info *.pdf *.a *.so *.o *.lo *.la \ *.elc *.aux *.cp *.fn *.ky *.log *.pg *.toc *.tp *.vr *.pyc \ .gdb_history gmon.out h out TAGS ledger valexpr .deps \ - build AUTHORS COPYING INSTALL Makefile aclocal.m4 autom4te \ - acconf.h acconf.h.in config.guess config.sub configure \ - depcomp install-sh missing stamp texinfo.tex Makefile.in \ - results.out + .libs build AUTHORS COPYING INSTALL Makefile acconf.h \ + acconf.h.in aclocal.m4 autom4te config.guess config.sub \ + configure depcomp install-sh libtool ltconfig ltmain.sh \ + missing stamp texinfo.tex Makefile.in @@ -2,6 +2,9 @@ * 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: 2004/10/03=2004/09/30 Credit card company @@ -2,6 +2,7 @@ touch AUTHORS COPYING +glibtoolize --automake -f -c aclocal autoheader if [ "$1" = "--dist" ]; then @@ -20,10 +20,11 @@ namespace ledger { -config_t config; std::list<option_t> config_options; -config_t::config_t() +static config_t * config = NULL; + +void config_t::reset() { amount_expr = "a"; total_expr = "O"; @@ -73,12 +74,13 @@ config_t::config_t() cache_dirty = false; } -static void -regexps_to_predicate(config_t& config, 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) +void +config_t::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, + const bool add_account_short_masks, + const bool logical_and) { std::string regexps[2]; @@ -110,12 +112,12 @@ regexps_to_predicate(config_t& config, const std::string& command, if (regexps[i].empty()) continue; - if (! config.predicate.empty()) - config.predicate += "&"; + if (! predicate.empty()) + predicate += logical_and ? "&" : "|"; int add_predicate = 0; // 1 adds /.../, 2 adds ///.../ if (i == 1) { - config.predicate += "!"; + predicate += "!"; } else if (add_account_short_masks) { 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) { - config.show_subtotal = true; + show_subtotal = true; add_predicate = 1; } else { 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 (! config.display_predicate.empty()) - config.display_predicate += "&"; - else if (! config.show_empty) - config.display_predicate += "T&"; + if (! display_predicate.empty()) + display_predicate += "&"; + else if (! show_empty) + display_predicate += "T&"; if (add_predicate == 2) - config.display_predicate += "//"; - config.display_predicate += "/(?:"; - config.display_predicate += regexps[i]; - config.display_predicate += ")/"; + display_predicate += "//"; + display_predicate += "/(?:"; + display_predicate += regexps[i]; + display_predicate += ")/"; } if (! account_regexp) - config.predicate += "/"; - config.predicate += "/(?:"; - config.predicate += regexps[i]; - config.predicate += ")/"; + predicate += "/"; + predicate += "/(?:"; + predicate += regexps[i]; + 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, strings_list::iterator arg, strings_list::iterator args_end) @@ -189,11 +214,11 @@ void config_t::process_options(const std::string& command, break; if (i != arg) - regexps_to_predicate(*this, command, arg, i, true, + regexps_to_predicate(command, arg, i, true, (command == "b" && ! show_subtotal && display_predicate.empty())); 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 @@ -258,83 +283,157 @@ void config_t::process_options(const std::string& command, format_t::date_format = date_format; } -void parse_ledger_data(journal_t * journal, parser_t * cache_parser, - parser_t * text_parser, parser_t * xml_parser) +item_handler<transaction_t> * +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; - - DEBUG_PRINT("ledger.config.cache", "3. use_cache = " << config.use_cache); - - if (! config.init_file.empty() && - access(config.init_file.c_str(), R_OK) != -1) { - if (parse_journal_file(config.init_file, journal) || - journal->auto_entries.size() > 0 || - journal->period_entries.size() > 0) - throw error(std::string("Entries found in initialization file '") + - config.init_file + "'"); + item_handler<transaction_t> * formatter = NULL; + + ptrs.push_back(formatter = base_formatter); + + // format_transactions write each transaction received to the + // output stream. + if (! (command == "b" || command == "E")) { + // truncate_entries cuts off a certain number of _entries_ from + // being displayed. It does not affect calculation. + if (head_entries || tail_entries) + ptrs.push_back(formatter = + new truncate_entries(formatter, + head_entries, tail_entries)); + + // 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)); + } - journal->sources.pop_front(); // remove init file + // 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 && - ! config.cache_file.empty() && - ! config.data_file.empty()) { - DEBUG_PRINT("ledger.config.cache", "using_cache " << config.cache_file); - config.cache_dirty = true; - if (access(config.cache_file.c_str(), R_OK) != -1) { - std::ifstream stream(config.cache_file.c_str()); - if (cache_parser->test(stream)) { - std::string price_db_orig = journal->price_db; - journal->price_db = config.price_db; - entry_count += cache_parser->parse(stream, journal, NULL, - &config.data_file); - if (entry_count > 0) - config.cache_dirty = false; - else - journal->price_db = price_db_orig; - } - } + // 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 (show_subtotal && ! (command == "b" || command == "E")) + ptrs.push_back(formatter = new subtotal_transactions(formatter)); + + if (days_of_the_week) + 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()) { - account_t * account = NULL; - if (! config.account.empty()) - account = journal->find_account(config.account); - - journal->price_db = config.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 " << config.data_file); - if (config.data_file == "-") { - config.use_cache = false; - journal->sources.push_back("<stdin>"); - if (xml_parser && std::cin.peek() == '<') - entry_count += xml_parser->parse(std::cin, journal, account); - else - entry_count += text_parser->parse(std::cin, journal, account); - } - else if (access(config.data_file.c_str(), R_OK) != -1) { - entry_count += parse_journal_file(config.data_file, journal, account); - if (! journal->price_db.empty()) - journal->sources.push_back(journal->price_db); - } + // invert_transactions inverts the value of the transactions it + // receives. + if (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 (show_related) + ptrs.push_back(formatter = + new related_transactions(formatter, + show_all_related)); + + // This filter_transactions will only pass through transactions + // matching the `predicate'. + if (! predicate.empty()) + ptrs.push_back(formatter = new filter_transactions(formatter, 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 (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) - throw error("Please specify ledger file using -f" - " or LEDGER_FILE environment variable."); + if (comm_as_payee) + ptrs.push_back(formatter = new set_comm_as_payee(formatter)); - VALIDATE(journal->valid()); + return formatter; } static void show_version(std::ostream& out) @@ -586,38 +685,42 @@ OPT_BEGIN(version, "v") { } OPT_END(version); OPT_BEGIN(init_file, "i:") { - config.init_file = optarg; + config->init_file = optarg; } OPT_END(init_file); OPT_BEGIN(file, "f:") { if (std::string(optarg) == "-" || access(optarg, R_OK) != -1) - config.data_file = optarg; + config->data_file = optarg; else throw error(std::string("The ledger file '") + optarg + "' does not exist or is not readable"); } OPT_END(file); OPT_BEGIN(cache, ":") { - config.cache_file = optarg; + config->cache_file = optarg; } OPT_END(cache); OPT_BEGIN(no_cache, "") { - config.cache_file = "<none>"; + config->cache_file = "<none>"; } OPT_END(no_cache); OPT_BEGIN(output, "o:") { if (std::string(optarg) != "-") - config.output_file = optarg; + config->output_file = optarg; } OPT_END(output); OPT_BEGIN(account, "a:") { - config.account = optarg; + config->account = optarg; } OPT_END(account); ////////////////////////////////////////////////////////////////////// // // Report filtering +OPT_BEGIN(effective, "") { + transaction_t::use_effective_date = true; +} OPT_END(effective); + OPT_BEGIN(begin, "b:") { char buf[128]; interval_t interval(optarg); @@ -627,11 +730,11 @@ OPT_BEGIN(begin, "b:") { throw error(std::string("Could not determine beginning of period '") + optarg + "'"); - if (! config.predicate.empty()) - config.predicate += "&"; - config.predicate += "d>=["; - config.predicate += buf; - config.predicate += "]"; + if (! config->predicate.empty()) + config->predicate += "&"; + config->predicate += "d>=["; + config->predicate += buf; + config->predicate += "]"; } OPT_END(begin); OPT_BEGIN(end, "e:") { @@ -643,43 +746,43 @@ OPT_BEGIN(end, "e:") { throw error(std::string("Could not determine end of period '") + optarg + "'"); - if (! config.predicate.empty()) - config.predicate += "&"; - config.predicate += "d<["; - config.predicate += buf; - config.predicate += "]"; + if (! config->predicate.empty()) + config->predicate += "&"; + config->predicate += "d<["; + config->predicate += buf; + config->predicate += "]"; terminus = interval.end; } OPT_END(end); OPT_BEGIN(current, "c") { - if (! config.predicate.empty()) - config.predicate += "&"; - config.predicate += "d<=m"; + if (! config->predicate.empty()) + config->predicate += "&"; + config->predicate += "d<=m"; } OPT_END(current); OPT_BEGIN(cleared, "C") { - if (! config.predicate.empty()) - config.predicate += "&"; - config.predicate += "X"; + if (! config->predicate.empty()) + config->predicate += "&"; + config->predicate += "X"; } OPT_END(cleared); OPT_BEGIN(uncleared, "U") { - if (! config.predicate.empty()) - config.predicate += "&"; - config.predicate += "!X"; + if (! config->predicate.empty()) + config->predicate += "&"; + config->predicate += "!X"; } OPT_END(uncleared); OPT_BEGIN(real, "R") { - if (! config.predicate.empty()) - config.predicate += "&"; - config.predicate += "R"; + if (! config->predicate.empty()) + config->predicate += "&"; + config->predicate += "R"; } OPT_END(real); OPT_BEGIN(actual, "L") { - if (! config.predicate.empty()) - config.predicate += "&"; - config.predicate += "L"; + if (! config->predicate.empty()) + config->predicate += "&"; + config->predicate += "L"; } OPT_END(actual); ////////////////////////////////////////////////////////////////////// @@ -687,11 +790,11 @@ OPT_BEGIN(actual, "L") { // Output customization OPT_BEGIN(format, "F:") { - config.format_string = optarg; + config->format_string = optarg; } OPT_END(format); OPT_BEGIN(date_format, "y:") { - config.date_format = optarg; + config->date_format = optarg; } OPT_END(date_format); OPT_BEGIN(input_date_format, ":") { @@ -700,95 +803,91 @@ OPT_BEGIN(input_date_format, ":") { } OPT_END(input_date_format); OPT_BEGIN(balance_format, ":") { - config.balance_format = optarg; + config->balance_format = optarg; } OPT_END(balance_format); OPT_BEGIN(register_format, ":") { - config.register_format = optarg; + config->register_format = optarg; } OPT_END(register_format); OPT_BEGIN(wide_register_format, ":") { - config.wide_register_format = optarg; + config->wide_register_format = optarg; } OPT_END(wide_register_format); OPT_BEGIN(plot_amount_format, ":") { - config.plot_amount_format = optarg; + config->plot_amount_format = optarg; } OPT_END(plot_amount_format); OPT_BEGIN(plot_total_format, ":") { - config.plot_total_format = optarg; - -OPT_BEGIN(effective, "") { - transaction_t::use_effective_date = true; -} OPT_END(effective); + config->plot_total_format = optarg; } OPT_END(plot_total_format); OPT_BEGIN(print_format, ":") { - config.print_format = optarg; + config->print_format = optarg; } OPT_END(print_format); OPT_BEGIN(write_hdr_format, ":") { - config.write_hdr_format = optarg; + config->write_hdr_format = optarg; } OPT_END(write_hdr_format); OPT_BEGIN(write_xact_format, ":") { - config.write_xact_format = optarg; + config->write_xact_format = optarg; } OPT_END(write_xact_format); OPT_BEGIN(equity_format, ":") { - config.equity_format = optarg; + config->equity_format = optarg; } OPT_END(equity_format); OPT_BEGIN(prices_format, ":") { - config.prices_format = optarg; + config->prices_format = optarg; } OPT_END(prices_format); OPT_BEGIN(wide, "w") { - config.register_format = config.wide_register_format; + config->register_format = config->wide_register_format; } OPT_END(wide); OPT_BEGIN(head, ":") { - config.head_entries = std::atoi(optarg); + config->head_entries = std::atoi(optarg); } OPT_END(head); OPT_BEGIN(tail, ":") { - config.tail_entries = std::atoi(optarg); + config->tail_entries = std::atoi(optarg); } OPT_END(tail); OPT_BEGIN(pager, ":") { - config.pager = optarg; + config->pager = optarg; } OPT_END(pager); OPT_BEGIN(empty, "E") { - config.show_empty = true; + config->show_empty = true; } OPT_END(empty); OPT_BEGIN(collapse, "n") { - config.show_collapsed = true; + config->show_collapsed = true; } OPT_END(collapse); OPT_BEGIN(subtotal, "s") { - config.show_subtotal = true; + config->show_subtotal = true; } OPT_END(subtotal); OPT_BEGIN(totals, "") { - config.show_totals = true; + config->show_totals = true; } OPT_END(totals); OPT_BEGIN(sort, "S:") { - config.sort_string = optarg; + config->sort_string = optarg; } OPT_END(sort); OPT_BEGIN(related, "r") { - config.show_related = true; + config->show_related = true; } OPT_END(related); OPT_BEGIN(period, "p:") { - if (config.report_period.empty()) { - config.report_period = optarg; + if (config->report_period.empty()) { + config->report_period = optarg; } else { - config.report_period += " "; - config.report_period += optarg; + config->report_period += " "; + config->report_period += optarg; } // 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. char buf[128]; - interval_t interval(config.report_period); + interval_t interval(config->report_period); + if (interval.begin) { std::strftime(buf, 127, formats[0], std::localtime(&interval.begin)); - if (! config.predicate.empty()) - config.predicate += "&"; - config.predicate += "d>=["; - config.predicate += buf; - config.predicate += "]"; + if (! config->predicate.empty()) + config->predicate += "&"; + config->predicate += "d>=["; + config->predicate += buf; + config->predicate += "]"; } + if (interval.end) { std::strftime(buf, 127, formats[0], std::localtime(&interval.end)); - if (! config.predicate.empty()) - config.predicate += "&"; - config.predicate += "d<["; - config.predicate += buf; - config.predicate += "]"; + if (! config->predicate.empty()) + config->predicate += "&"; + config->predicate += "d<["; + config->predicate += buf; + config->predicate += "]"; terminus = interval.end; } } OPT_END(period); OPT_BEGIN(period_sort, ":") { - config.report_period_sort = optarg; + config->report_period_sort = optarg; } OPT_END(period_sort); OPT_BEGIN(weekly, "W") { - if (config.report_period.empty()) - config.report_period = "weekly"; + if (config->report_period.empty()) + config->report_period = "weekly"; else - config.report_period = std::string("weekly ") + config.report_period; + config->report_period = std::string("weekly ") + config->report_period; } OPT_END(weekly); OPT_BEGIN(monthly, "M") { - if (config.report_period.empty()) - config.report_period = "monthly"; + if (config->report_period.empty()) + config->report_period = "monthly"; else - config.report_period = std::string("monthly ") + config.report_period; + config->report_period = std::string("monthly ") + config->report_period; } OPT_END(monthly); OPT_BEGIN(yearly, "Y") { - if (config.report_period.empty()) - config.report_period = "yearly"; + if (config->report_period.empty()) + config->report_period = "yearly"; else - config.report_period = std::string("yearly ") + config.report_period; + config->report_period = std::string("yearly ") + config->report_period; } OPT_END(yearly); OPT_BEGIN(dow, "") { - config.days_of_the_week = true; + config->days_of_the_week = true; } OPT_END(dow); OPT_BEGIN(by_payee, "P") { - config.by_payee = true; + config->by_payee = true; } OPT_END(by_payee); OPT_BEGIN(comm_as_payee, "x") { - config.comm_as_payee = true; + config->comm_as_payee = true; } OPT_END(comm_as_payee); OPT_BEGIN(budget, "") { - config.budget_flags = BUDGET_BUDGETED; + config->budget_flags = BUDGET_BUDGETED; } OPT_END(budget); OPT_BEGIN(add_budget, "") { - config.budget_flags = BUDGET_BUDGETED | BUDGET_UNBUDGETED; + config->budget_flags = BUDGET_BUDGETED | BUDGET_UNBUDGETED; } OPT_END(add_budget); OPT_BEGIN(unbudgeted, "") { - config.budget_flags = BUDGET_UNBUDGETED; + config->budget_flags = BUDGET_UNBUDGETED; } OPT_END(unbudgeted); OPT_BEGIN(forecast, ":") { - config.forecast_limit = optarg; + config->forecast_limit = optarg; } OPT_END(forecast); OPT_BEGIN(reconcile, ":") { - config.reconcile_balance = optarg; + config->reconcile_balance = optarg; } OPT_END(reconcile); OPT_BEGIN(reconcile_date, ":") { - config.reconcile_date = optarg; + config->reconcile_date = optarg; } OPT_END(reconcile_date); OPT_BEGIN(limit, "l:") { - if (! config.predicate.empty()) - config.predicate += "&"; - config.predicate += "("; - config.predicate += optarg; - config.predicate += ")"; + if (! config->predicate.empty()) + config->predicate += "&"; + config->predicate += "("; + config->predicate += optarg; + config->predicate += ")"; } OPT_END(limit); OPT_BEGIN(display, "d:") { - if (! config.display_predicate.empty()) - config.display_predicate += "&"; - config.display_predicate += "("; - config.display_predicate += optarg; - config.display_predicate += ")"; + if (! config->display_predicate.empty()) + config->display_predicate += "&"; + config->display_predicate += "("; + config->display_predicate += optarg; + config->display_predicate += ")"; } OPT_END(display); OPT_BEGIN(amount, "t:") { - config.amount_expr = optarg; + config->amount_expr = optarg; } OPT_END(amount); OPT_BEGIN(total, "T:") { - config.total_expr = optarg; + config->total_expr = optarg; } OPT_END(total); OPT_BEGIN(amount_data, "j") { - config.format_string = config.plot_amount_format; + config->format_string = config->plot_amount_format; } OPT_END(amount_data); - OPT_BEGIN(total_data, "J") { - config.format_string = config.plot_total_format; + config->format_string = config->plot_total_format; } OPT_END(total_data); - ////////////////////////////////////////////////////////////////////// // // Commodity reporting OPT_BEGIN(price_db, ":") { - config.price_db = optarg; + config->price_db = optarg; } OPT_END(price_db); OPT_BEGIN(price_exp, "Z:") { - config.pricing_leeway = std::atol(optarg) * 60; + config->pricing_leeway = std::atol(optarg) * 60; } OPT_END(price_exp); OPT_BEGIN(download, "Q") { - config.download_quotes = true; + config->download_quotes = true; } OPT_END(download); OPT_BEGIN(quantity, "O") { - config.amount_expr = "a"; - config.total_expr = "O"; + config->amount_expr = "a"; + config->total_expr = "O"; } OPT_END(quantity); OPT_BEGIN(basis, "B") { - config.amount_expr = "b"; - config.total_expr = "B"; + config->amount_expr = "b"; + config->total_expr = "B"; } OPT_END(basis); OPT_BEGIN(market, "V") { - config.show_revalued = true; + config->show_revalued = true; - config.amount_expr = "v"; - config.total_expr = "V"; + config->amount_expr = "v"; + config->total_expr = "V"; } OPT_END(market); OPT_BEGIN(performance, "g") { - config.amount_expr = "P(a,m)-b"; // same as 'g', but priced now - config.total_expr = "P(O,m)-B"; + config->amount_expr = "P(a,m)-b"; // same as 'g', but priced now + config->total_expr = "P(O,m)-B"; } OPT_END(performance); OPT_BEGIN(gain, "G") { - config.show_revalued = - config.show_revalued_only = true; + config->show_revalued = + config->show_revalued_only = true; - config.amount_expr = "a"; - config.total_expr = "G"; + config->amount_expr = "a"; + config->total_expr = "G"; } OPT_END(gain); OPT_BEGIN(average, "A") { - config.total_expr_template = "A#"; + config->total_expr_template = "A#"; } OPT_END(average); OPT_BEGIN(deviation, "D") { - config.total_expr_template = "t-A#"; + config->total_expr_template = "t-A#"; } OPT_END(deviation); OPT_BEGIN(percentage, "%") { - config.total_expr_template = "^#&{100.0%}*(#/^#)"; + config->total_expr_template = "^#&{100.0%}*(#/^#)"; } OPT_END(percentage); #ifdef USE_BOOST_PYTHON @@ -1017,9 +1116,6 @@ void py_add_config_option_handlers() add_other_option_handlers(config_options); } -BOOST_PYTHON_FUNCTION_OVERLOADS(parse_ledger_data_overloads, - parse_ledger_data, 1, 2) - void py_option_help() { option_help(std::cout); @@ -1082,10 +1178,9 @@ void export_config() .def("process_options", py_process_options) ; - scope().attr("config") = ptr(&config); + scope().attr("config") = ptr(config); 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); } @@ -1,12 +1,7 @@ #ifndef _CONFIG_H #define _CONFIG_H -#include "journal.h" -#include "option.h" -#include "valexpr.h" -#include "datetime.h" -#include "format.h" -#include "parser.h" +#include "ledger.h" #include <iostream> #include <memory> @@ -14,8 +9,9 @@ namespace ledger { -struct config_t +class config_t { + public: // These options can all be set used text fields. strings_list price_settings; @@ -70,27 +66,43 @@ struct config_t bool use_cache; bool cache_dirty; - config_t(); + config_t() { + reset(); + } config_t(const config_t&) { 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, strings_list::iterator arg, 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; 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 { declared_option_handler(const std::string& label, const std::string& opt_chars) { diff --git a/configure.in b/configure.in index af2a1962..0059e0ea 100644 --- a/configure.in +++ b/configure.in @@ -10,9 +10,8 @@ AC_CONFIG_HEADER([acconf.h]) # Checks for programs. AC_PROG_CXX AC_PROG_MAKE_SET -AC_PROG_RANLIB -#AC_PROG_LIBTOOL -#AM_PROG_LIBTOOL +AC_PROG_LIBTOOL +AM_PROG_LIBTOOL # check if UNIX pipes are available AC_CACHE_CHECK( @@ -36,7 +36,7 @@ entry_t * derive_new_entry(journal_t& journal, if (i == end) { // If no argument were given but the payee, assume the user wants // to see the same transaction as last time. - added->code = matching->code; + added->code = matching->code; for (transactions_list::iterator j = matching->transactions.begin(); j != matching->transactions.end(); @@ -47,7 +47,7 @@ void format_emacs_transactions::operator()(transaction_t& xact) out << "\n"; } - out << " (\"" << xact.account->fullname() << "\" \"" + out << " (\"" << xact_account(xact)->fullname() << "\" \"" << xact.amount << "\""; switch (xact.state) { @@ -474,7 +474,7 @@ void format_t::format(std::ostream& out_str, const details_t& details) const if (details.xact) { switch (details.xact->state) { case transaction_t::CLEARED: - out << "* "; + out << "* "; break; case transaction_t::PENDING: out << "! "; @@ -547,8 +547,8 @@ void format_t::format(std::ostream& out_str, const details_t& details) const case element_t::ACCOUNT_FULLNAME: if (details.account) { name += (elem->type == element_t::ACCOUNT_FULLNAME ? - details.account->fullname() : - partial_account_name(*details.account)); + details.account->fullname() : + partial_account_name(*details.account)); if (details.xact && details.xact->flags & TRANSACTION_VIRTUAL) { if (elem->max_width > 2) @@ -685,7 +685,9 @@ void print_entry(std::ostream& out, const entry_t& entry) formatter); 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, @@ -728,11 +728,11 @@ EXC_TRANSLATOR(parse_error) void export_journal() { - scope().attr("TRANSACTION_NORMAL") = TRANSACTION_NORMAL; - scope().attr("TRANSACTION_VIRTUAL") = TRANSACTION_VIRTUAL; - scope().attr("TRANSACTION_BALANCE") = TRANSACTION_BALANCE; - scope().attr("TRANSACTION_AUTO") = TRANSACTION_AUTO; - scope().attr("TRANSACTION_BULK_ALLOC") = TRANSACTION_BULK_ALLOC; + scope().attr("TRANSACTION_NORMAL") = TRANSACTION_NORMAL; + scope().attr("TRANSACTION_VIRTUAL") = TRANSACTION_VIRTUAL; + scope().attr("TRANSACTION_BALANCE") = TRANSACTION_BALANCE; + scope().attr("TRANSACTION_AUTO") = TRANSACTION_AUTO; + scope().attr("TRANSACTION_BULK_ALLOC") = TRANSACTION_BULK_ALLOC; class_< transaction_t > ("Transaction") .def(init<account_t *, amount_t, optional<unsigned int, std::string> >()) @@ -487,7 +487,7 @@ dropped." (dolist (item items) (let ((index 1)) (dolist (xact (nthcdr 5 item)) - (let ((beg (point)) + (let ((beg (point)) (where (with-current-buffer buf (cons @@ -502,14 +502,14 @@ dropped." account (cdr (ledger-current-entry-bounds))) (setq i (1+ i)))) (point-marker))))))) - (insert (format "%s %-30s %-25s %15s\n" + (insert (format "%s %-30s %-25s %15s\n" (format-time-string "%m/%d" (nth 2 item)) (nth 4 item) (nth 0 xact) (nth 1 xact))) (if (nth 2 xact) + (set-text-properties beg (1- (point)) + (list 'face 'bold + 'where where)) (set-text-properties beg (1- (point)) - (list 'face 'bold - 'where where)) - (set-text-properties beg (1- (point)) (list 'where where)))) (setq index (1+ index))))) (goto-char (point-min)) @@ -26,7 +26,6 @@ #include <reconcile.h> #include <error.h> #include <option.h> -#include <config.h> #include <parser.h> #include <textual.h> @@ -36,4 +35,15 @@ #include <qif.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 @@ -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 <fstream> #include <sstream> -#include <memory> #include <algorithm> #include <exception> #include <iterator> @@ -27,218 +17,34 @@ using namespace ledger; #include "fdstream.hpp" #endif -#if !defined(DEBUG_LEVEL) || DEBUG_LEVEL <= RELEASE - -#define auto_ptr bogus_auto_ptr - -// 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; - } - } - }; -} - +#include "ledger.h" +#ifdef USE_BOOST_PYTHON +#include "py_eval.h" #endif +#include "timing.h" -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) -{ - item_handler<transaction_t> * formatter = NULL; - - ptrs.push_back(formatter = base_formatter); - - // format_transactions write each transaction received to the - // output stream. - if (! (command == "b" || command == "E")) { - // truncate_entries cuts off a certain number of _entries_ from - // 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)); +using namespace ledger; - return formatter; +namespace { + TIMER_DEF_(setup); + TIMER_DEF_(parse); + TIMER_DEF_(process); + TIMER_DEF_(walk); + TIMER_DEF_(cleanup); } 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); // Parse command-line arguments, and those set in the environment 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()) { 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(); DEBUG_PRINT("ledger.config.cache", "1. use_cache = " << config.use_cache); - process_environment(config_options, envp, "LEDGER_"); + config.process_environment(envp, "LEDGER_"); #if 1 // These are here for backwards compatability, but are deprecated. 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")) - process_option(config_options, "init-file", p); + config.process_option("init-file", p); 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")) - process_option(config_options, "price-exp", p); + config.process_option("price-exp", p); #endif const char * p = std::getenv("HOME"); @@ -311,38 +117,22 @@ int parse_and_report(int argc, char * argv[], char * envp[]) else throw error(std::string("Unrecognized command '") + command + "'"); + TIMER_STOP(setup); + // Parse initialization files, ledger data, price database, etc. - std::auto_ptr<binary_parser_t> bin_parser(new binary_parser_t); -#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); + TIMER_START(parse); - register_parser(bin_parser.get()); -#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) - register_parser(xml_parser.get()); - 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()); + if (parse_ledger_data(journal.get(), config) == 0) + throw error("Please specify ledger file using -f" + " or LEDGER_FILE environment variable."); - parse_ledger_data(journal.get(), bin_parser.get(), text_parser.get() -#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) - , xml_parser.get() -#endif - ); + TIMER_STOP(parse); // process the command word and its following arguments + TIMER_START(process); + std::string first_arg; if (command == "w") { if (arg == args.end()) @@ -461,8 +251,12 @@ def vmax(d, val):\n\ #endif // USE_BOOST_PYTHON + TIMER_STOP(process); + // Walk the entries based on the report type and the options + TIMER_START(walk); + item_handler<transaction_t> * formatter; std::list<item_handler<transaction_t> *> formatter_ptrs; @@ -482,10 +276,11 @@ def vmax(d, val):\n\ formatter = new format_transactions(*out, *format); if (command == "w") { - write_textual_journal(*journal, first_arg, *formatter, *out); + write_textual_journal(*journal, first_arg, *formatter, + config.write_hdr_format, *out); } else { - formatter = chain_xact_handlers(command, formatter, journal.get(), - journal->master, formatter_ptrs); + formatter = config.chain_xact_handlers(command, formatter, journal.get(), + journal->master, formatter_ptrs); if (command == "e") walk_transactions(new_entry->transactions, *formatter); else if (command == "P" || command == "D") @@ -521,8 +316,16 @@ def vmax(d, val):\n\ acct_formatter.flush(); } + TIMER_STOP(walk); + + TIMER_START(cleanup); + #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()) delete out; @@ -554,13 +357,13 @@ def vmax(d, val):\n\ } #endif + TIMER_STOP(cleanup); + return 0; } int main(int argc, char * argv[], char * envp[]) { - std::ios::sync_with_stdio(false); - try { return parse_and_report(argc, argv, envp); } @@ -1,5 +1,6 @@ #include "parser.h" #include "journal.h" +#include "config.h" #include <fstream> #ifdef WIN32 @@ -12,18 +13,31 @@ namespace ledger { 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) { parsers_list::iterator i; - for (i = parsers.begin(); i != parsers.end(); i++) + for (i = parsers->begin(); i != parsers->end(); i++) if (*i == parser) break; - if (i != parsers.end()) + if (i != parsers->end()) return false; - parsers.push_back(parser); + parsers->push_back(parser); return true; } @@ -31,13 +45,13 @@ bool register_parser(parser_t * parser) bool unregister_parser(parser_t * parser) { parsers_list::iterator i; - for (i = parsers.begin(); i != parsers.end(); i++) + for (i = parsers->begin(); i != parsers->end(); i++) if (*i == parser) break; - if (i == parsers.end()) + if (i == parsers->end()) return false; - parsers.erase(i); + parsers->erase(i); return true; } @@ -50,8 +64,8 @@ unsigned int parse_journal(std::istream& in, if (! master) master = journal->master; - for (parsers_list::iterator i = parsers.begin(); - i != parsers.end(); + for (parsers_list::iterator i = parsers->begin(); + i != parsers->end(); i++) if ((*i)->test(in)) 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); } +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 #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, parse_journal_file, 2, 4) +BOOST_PYTHON_FUNCTION_OVERLOADS(parse_ledger_data_overloads, + parse_ledger_data, 1, 2) + void export_parser() { class_< parser_t, parser_wrap, boost::noncopyable > ("Parser") ; @@ -116,6 +233,9 @@ void export_parser() { def("unregister_parser", unregister_parser); def("parse_journal", parse_journal, parse_journal_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 @@ -35,6 +35,24 @@ unsigned int parse_journal_file(const std::string& path, account_t * master = 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 #endif // _PARSER_H diff --git a/startup.cc b/startup.cc new file mode 100644 index 00000000..ad462e36 --- /dev/null +++ b/startup.cc @@ -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(); + } +} @@ -211,7 +211,7 @@ transaction_t * parse_transaction(char * line, account_t * account) if (amount == note_str) amount = NULL; - *note_str++ = '\0'; + *note_str++ = '\0'; note_str = skip_ws(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"); } - xact->note = skip_ws(note_str); - } + xact->note = skip_ws(note_str); + } if (amount) { price = std::strchr(amount, '@'); if (price) { if (price == amount) - throw parse_error(path, linenum, "Cost specified without amount"); + throw parse_error(path, linenum, "Cost specified without amount"); *price++ = '\0'; if (*price == '@') { - per_unit = false; + per_unit = false; price++; - } + } price = skip_ws(price); - } + } } } } @@ -321,7 +321,6 @@ bool parse_transactions(std::istream& in, } namespace { - TIMER_DEF(entry_finish, "finalizing entry"); TIMER_DEF(entry_xacts, "parsing transactions"); TIMER_DEF(entry_details, "parsing entry details"); 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, item_handler<transaction_t>& formatter, + const std::string& write_hdr_format, std::ostream& out) { 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 jump_to; - format_t hdr_fmt(config.write_hdr_format); + format_t hdr_fmt(write_hdr_format); std::ifstream in(found.c_str()); while (! in.eof()) { @@ -2,6 +2,7 @@ #define _TEXTUAL_H #include "parser.h" +#include "format.h" #include "walk.h" 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, item_handler<transaction_t>& formatter, + const std::string& write_hdr_format, std::ostream& out); } // namespace ledger @@ -41,10 +41,12 @@ class timing_t #ifdef DEBUG_ENABLED #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_STOP(sym) sym.stop() #else #define TIMER_DEF(sym, cat) +#define TIMER_DEF_(sym) #define TIMER_START(sym) #define TIMER_STOP(sym) #endif @@ -15,6 +15,12 @@ std::auto_ptr<value_expr_t> total_expr; 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 { switch (kind) { @@ -20,10 +20,7 @@ struct details_t : entry(&_entry), xact(NULL), account(NULL) { DEBUG_PRINT("ledger.memory.ctors", "ctor details_t"); } - 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 transaction_t& _xact); details_t(const account_t& _account) : entry(NULL), xact(NULL), account(&_account) { 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> total_expr; - extern std::time_t terminus; inline void compute_amount(value_t& result, const details_t& details) { @@ -7,12 +7,6 @@ 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 <> bool compare_items<transaction_t>::operator()(const transaction_t * left, 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) { - if (! xact.data) { - transactions_xdata.push_back(transaction_xdata_t()); - xact.data = &transactions_xdata.back(); - transactions_xdata_ptrs.push_back(&xact.data); - } + if (! xact.data) + xact.data = new transaction_xdata_t(); return *((transaction_xdata_t *) xact.data); } @@ -109,7 +100,10 @@ void truncate_entries::flush() 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); xdata.count++; @@ -370,7 +364,7 @@ void subtotal_transactions::operator()(transaction_t& xact) if (! finish || std::difftime(xact.date(), finish) > 0) finish = xact.date(); - account_t * acct = xact.account; + account_t * acct = xact_account(xact); assert(acct); values_map::iterator i = values.find(acct->fullname()); @@ -387,9 +381,9 @@ void subtotal_transactions::operator()(transaction_t& xact) // that contain only virtual transactions. 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)) - 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) @@ -559,10 +553,10 @@ void budget_transactions::report_budget_items(const std::time_t moment) if (std::difftime(begin, moment) < 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 " - << xact.account->fullname()); + << xact_account(xact)->fullname()); DEBUG_PRINT_TIME("ledger.walk.budget", begin); 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); transaction_t& temp = xact_temps.back(); - temp.entry = &entry; - temp.flags |= TRANSACTION_AUTO; + temp.entry = &entry; + temp.flags |= TRANSACTION_AUTO | TRANSACTION_BULK_ALLOC; temp.amount.negate(); - temp.flags |= TRANSACTION_BULK_ALLOC; entry.add_transaction(&temp); 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(); i != pending_xacts.end(); i++) - for (account_t * acct = xact.account; acct; acct = acct->parent) { - if (acct == (*i).second->account) { + for (account_t * acct = xact_account(xact); + acct; + acct = acct->parent) { + if (acct == xact_account(*(*i).second)) { xact_in_budget = true; - // Report the transaction as if it had occurred in the parent - // account. jww (2005-07-13): Note that this assignment will - // irrevocably change the underlying transaction. - if (xact.account != acct) - xact.account = acct; + // account. + if (xact_account(xact) != acct) + transaction_xdata(xact).account = acct; goto handle; } } @@ -705,17 +698,6 @@ void forecast_transactions::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 <> bool compare_items<account_t>::operator()(const account_t * left, 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) { - if (! account.data) { - accounts_xdata.push_back(account_xdata_t()); - account.data = &accounts_xdata.back(); - accounts_xdata_ptrs.push_back(&account.data); - } + if (! account.data) + account.data = new account_xdata_t(); + 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, item_handler<transaction_t>& handler) { @@ -972,7 +940,6 @@ void export_walk() def("transaction_has_xdata", transaction_has_xdata); def("transaction_xdata", transaction_xdata, return_internal_reference<1>()); - def("clear_transactions_xdata", clear_transactions_xdata); def("add_transaction_to", add_transaction_to); class_< xact_handler_t, item_handler_wrap<transaction_t> > @@ -991,6 +958,12 @@ void export_walk() .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> > ("TruncateEntries", init<xact_handler_t *, int, int>() [with_custodian_and_ward<1, 2>()]) @@ -1149,8 +1122,6 @@ void export_walk() def("account_has_xdata", account_has_xdata); 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> > ("AccountHandler") @@ -1162,6 +1133,12 @@ void export_walk() &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("walk_accounts", py_walk_accounts_1); def("walk_accounts", py_walk_accounts_2); @@ -91,18 +91,17 @@ struct transaction_xdata_t unsigned int index; unsigned short dflags; std::time_t date; + account_t * account; 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) { 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) { 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); 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, @@ -136,8 +146,6 @@ inline void walk_entries(entries_list& list, walk_entries(list.begin(), list.end(), handler); } -void clear_transactions_xdata(); - ////////////////////////////////////////////////////////////////////// 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) {} }; +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> { int head_count; @@ -594,9 +613,6 @@ inline bool account_has_xdata(const account_t& account) { 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) { 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); typedef std::deque<account_t *> accounts_deque; @@ -619,18 +646,19 @@ void walk_accounts(account_t& account, item_handler<account_t>& handler, 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, 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 #endif // _WALK_H |