diff options
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | NEWS | 9 | ||||
-rw-r--r-- | amount.cc | 13 | ||||
-rw-r--r-- | balance.cc | 10 | ||||
-rw-r--r-- | balance.h | 24 | ||||
-rw-r--r-- | binary.cc | 56 | ||||
-rw-r--r-- | config.cc | 1368 | ||||
-rw-r--r-- | config.h | 134 | ||||
-rw-r--r-- | journal.cc | 8 | ||||
-rw-r--r-- | ledger.h | 1 | ||||
-rw-r--r-- | main.cc | 108 | ||||
-rw-r--r-- | option.cc | 874 | ||||
-rw-r--r-- | option.h | 20 | ||||
-rw-r--r-- | parser.cc | 1 | ||||
-rw-r--r-- | textual.cc | 15 | ||||
-rw-r--r-- | valexpr.cc | 3 | ||||
-rw-r--r-- | valexpr.h | 7 | ||||
-rw-r--r-- | value.cc | 19 | ||||
-rw-r--r-- | value.h | 8 | ||||
-rw-r--r-- | walk.cc | 2 |
20 files changed, 1128 insertions, 1554 deletions
diff --git a/Makefile.am b/Makefile.am index 68dd5f48..63f53e5c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -28,6 +28,7 @@ libledger_la_SOURCES = \ qif.cc \ quotes.cc \ reconcile.cc \ + report.cc \ startup.cc \ textual.cc \ valexpr.cc \ @@ -74,6 +75,7 @@ pkginclude_HEADERS = \ qif.h \ quotes.h \ reconcile.h \ + report.h \ textual.h \ timing.h \ valexpr.h \ @@ -111,6 +111,15 @@ expression which is evaluated as a boolean to locate the desired reported transaction. +- Added a "dump" command for creating binary files, which load much + faster than their textual originals. For example: + + ledger -f huge.dat -o huge.cache dump + ledger -f huge.cache bal + + The second command will load significantly faster (usually about six + times on my machine). + - There have a few changes to value expression syntax. The most significant incompatibilities being: @@ -217,6 +217,8 @@ amount_t::amount_t(const double value) void amount_t::_release() { + DEBUG_PRINT("amounts.refs", + quantity << " ref--, now " << (quantity->ref - 1)); if (--quantity->ref == 0) { if (! (quantity->flags & BIGINT_BULK_ALLOC)) delete quantity; @@ -257,6 +259,8 @@ void amount_t::_copy(const amount_t& amt) quantity = new bigint_t(*amt.quantity); } else { quantity = amt.quantity; + DEBUG_PRINT("amounts.refs", + quantity << " ref++, now " << (quantity->ref + 1)); quantity->ref++; } } @@ -506,8 +510,7 @@ int amount_t::compare(const amount_t& amt) const if (! amt.quantity) return sign(); - if (commodity() && amt.commodity() && - commodity() != amt.commodity()) + if (commodity() && amt.commodity() && commodity() != amt.commodity()) throw new amount_error (std::string("Cannot compare amounts with different commodities: ") + commodity().symbol() + " and " + amt.commodity().symbol()); @@ -619,7 +622,7 @@ amount_t amount_t::round(unsigned int prec) const amount_t temp = *this; if (! quantity || quantity->prec <= prec) { - if (quantity->flags & BIGINT_KEEP_PREC) { + if (quantity && quantity->flags & BIGINT_KEEP_PREC) { temp._dup(); temp.quantity->flags &= ~BIGINT_KEEP_PREC; } @@ -1276,6 +1279,8 @@ void amount_t::read_quantity(char *& data) data += sizeof(unsigned int); quantity = (bigint_t *) (bigints + (index - 1) * sizeof(bigint_t)); + DEBUG_PRINT("amounts.refs", + quantity << " ref++, now " << (quantity->ref + 1)); quantity->ref++; } } @@ -1625,7 +1630,7 @@ amount_t commodity_base_t::value(const std::time_t moment) } } - if (updater) + if (updater && ! (flags & COMMODITY_STYLE_NOMARKET)) (*updater)(*this, moment, age, (history && history->prices.size() > 0 ? (*history->prices.rbegin()).first : 0), price); @@ -197,9 +197,8 @@ balance_t& balance_t::operator*=(const amount_t& amt) i++) (*i).second *= amt; } - else if (amounts.size() == 1 && - (*amounts.begin()).first == &amt.commodity()) { - (*amounts.begin()).second *= amt; + else if (amounts.size() == 1) { + *this = (*amounts.begin()).second * amt; } else { amounts_map::iterator i = amounts.find(&amt.commodity()); @@ -208,9 +207,8 @@ balance_t& balance_t::operator*=(const amount_t& amt) } else { // Try stripping annotations before giving an error. balance_t temp(strip_annotations()); - if (temp.amounts.size() == 1 && - (*temp.amounts.begin()).first == &amt.commodity()) { - return *this = temp * amt; + if (temp.amounts.size() == 1) { + return *this = (*temp.amounts.begin()).second * amt; } else { i = temp.amounts.find(&amt.commodity()); if (i != temp.amounts.end()) @@ -449,6 +449,19 @@ class balance_t (*i).second.abs(); } + void reduce() { + for (amounts_map::iterator i = amounts.begin(); + i != amounts.end(); + i++) + (*i).second.reduce(); + } + + balance_t reduced() const { + balance_t temp(*this); + temp.reduce(); + return temp; + } + void round() { for (amounts_map::iterator i = amounts.begin(); i != amounts.end(); @@ -893,6 +906,17 @@ class balance_pair_t return quantity.valid() && (! cost || cost->valid()); } + void reduce() { + quantity.reduce(); + if (cost) cost->reduce(); + } + + balance_pair_t reduced() const { + balance_pair_t temp(*this); + temp.reduce(); + return temp; + } + void round() { quantity.round(); if (cost) cost->round(); @@ -456,7 +456,7 @@ inline void read_binary_commodity_base_extra(char *& data, // Upon insertion, amt will be copied, which will cause the amount // to be duplicated (and thus not lost when the journal's - // item_pool is deleted. + // item_pool is deleted). if (! commodity->history) commodity->history = new commodity_base_t::history_t; commodity->history->prices.insert(history_pair(when, amt)); @@ -509,7 +509,14 @@ inline commodity_t * read_binary_commodity_annotated(char *& data) commodity->ptr = commodities[read_binary_long<commodity_t::ident_t>(data) - 1]; - read_binary_amount(data, commodity->price); + + // This read-and-then-assign causes a new amount to be allocated + // which does not live within the bulk allocation pool, since that + // pool will be deleted *before* the commodities are destroyed. + amount_t amt; + read_binary_amount(data, amt); + commodity->price = amt; + read_binary_long(data, commodity->date); read_binary_string(data, commodity->tag); @@ -540,7 +547,7 @@ account_t * read_binary_account(char *& data, journal_t * journal, // account, throw away what we've learned about the recorded // journal's own master account. - if (master) { + if (master && acct != master) { delete acct; acct = master; } @@ -551,6 +558,7 @@ account_t * read_binary_account(char *& data, journal_t * journal, i++) { account_t * child = read_binary_account(data, journal); child->parent = acct; + assert(acct != child); acct->add_account(child); } @@ -575,9 +583,6 @@ unsigned int read_binary_journal(std::istream& in, i < count; i++) { std::string path = read_binary_string(in); - if (i == 0 && path != file) - return 0; - std::time_t old_mtime; read_binary_long(in, old_mtime); struct stat info; @@ -609,7 +614,8 @@ unsigned int read_binary_journal(std::istream& in, account_t::ident_t a_count = read_binary_long<account_t::ident_t>(data); accounts = accounts_next = new account_t *[a_count]; - assert(journal->master); delete journal->master; + assert(journal->master); + delete journal->master; journal->master = read_binary_account(data, journal, master); if (read_binary_number<bool>(data)) @@ -650,9 +656,6 @@ unsigned int read_binary_journal(std::istream& in, for (commodity_base_t::ident_t i = 0; i < bc_count; i++) { commodity_base_t * commodity = read_binary_commodity_base(data); - if (commodity->flags & COMMODITY_STYLE_BUILTIN) - commodity_base_t::commodities.erase(commodity->symbol); - std::pair<base_commodities_map::iterator, bool> result = commodity_base_t::commodities.insert (base_commodities_pair(commodity->symbol, commodity)); @@ -663,8 +666,7 @@ unsigned int read_binary_journal(std::istream& in, // It's possible the user might have used a commodity in a value // expression passed to an option, we'll just override the // flags, but keep the commodity pointer intact. - if (c == commodity_base_t::commodities.end() || - (*c).second->smaller || (*c).second->larger) + if (c == commodity_base_t::commodities.end()) throw new error(std::string("Failed to read base commodity from cache: ") + commodity->symbol); @@ -672,10 +674,15 @@ unsigned int read_binary_journal(std::istream& in, (*c).second->note = commodity->note; (*c).second->precision = commodity->precision; (*c).second->flags = commodity->flags; + if ((*c).second->smaller) + delete (*c).second->smaller; (*c).second->smaller = commodity->smaller; + if ((*c).second->larger) + delete (*c).second->larger; (*c).second->larger = commodity->larger; *(base_commodities_next - 1) = (*c).second; + delete commodity; } } @@ -694,20 +701,19 @@ unsigned int read_binary_journal(std::istream& in, commodity = read_binary_commodity_annotated(data); } - if (commodity->flags() & COMMODITY_STYLE_BUILTIN) { - commodity_t::commodities.erase(mapping_key); - if (commodity->symbol() == "") { - delete commodity_t::null_commodity; - commodity_t::null_commodity = commodity; - } - } - std::pair<commodities_map::iterator, bool> result = - commodity_t::commodities.insert(commodities_pair(mapping_key, - commodity)); - if (! result.second && commodity->annotated) - throw new error(std::string("Failed to read commodity from cache: ") + - commodity->base->symbol); + commodity_t::commodities.insert(commodities_pair + (mapping_key, commodity)); + if (! result.second) { + commodities_map::iterator c = + commodity_t::commodities.find(mapping_key); + if (c == commodity_t::commodities.end()) + throw new error(std::string("Failed to read commodity from cache: ") + + commodity->symbol()); + + *(commodities_next - 1) = (*c).second; + delete commodity; + } } for (commodity_base_t::ident_t i = 0; i < bc_count; i++) @@ -25,40 +25,6 @@ extern "C" char *realpath(const char *, char resolved_path[]); namespace ledger { -namespace { - config_t * config = NULL; - - void xact_amount(value_t& result, const details_t& details, value_expr_t *) - { - if (transaction_has_xdata(*details.xact) && - transaction_xdata_(*details.xact).dflags & TRANSACTION_COMPOUND) - result = transaction_xdata_(*details.xact).value; - else - result = details.xact->amount; - } - - void xact_running_total(value_t& result, const details_t& details, - value_expr_t *) - { - result = transaction_xdata_(*details.xact).total; - } - - void account_amount(value_t& result, const details_t& details, - value_expr_t *) - { - if (account_has_xdata(*details.account)) - result = account_xdata(*details.account).value; - } - - void account_total(value_t& result, const details_t& details, - value_expr_t *) - { - if (account_has_xdata(*details.account)) - result = account_xdata(*details.account).total; - } -} - - std::string expand_path(const std::string& path) { if (path.length() == 0 || path[0] != '~') @@ -113,1335 +79,31 @@ std::string resolve_path(const std::string& path) return path; } -void config_t::reset() +config_t::config_t() { - ledger::amount_expr = "@a"; - ledger::total_expr = "@O"; - - pricing_leeway = 24 * 3600; - budget_flags = BUDGET_NO_BUDGET; - balance_format = "%20T %2_%-a\n"; - register_format = ("%D %-.20P %-.22A %12.67t %!12.80T\n%/" - "%32|%-.22A %12.67t %!12.80T\n"); + balance_format = "%20T %2_%-a\n"; + register_format = ("%D %-.20P %-.22A %12.67t %!12.80T\n%/" + "%32|%-.22A %12.67t %!12.80T\n"); wide_register_format = ("%D %-.35P %-.38A %22.108t %!22.132T\n%/" "%48|%-.38A %22.108t %!22.132T\n"); - csv_register_format = "\"%D\",\"%P\",\"%A\",\"%t\",\"%T\"\n"; - plot_amount_format = "%D %(@S(@t))\n"; - plot_total_format = "%D %(@S(@T))\n"; - print_format = "\n%d %Y%C%P\n %-34W %12o%n\n%/ %-34W %12o%n\n"; - write_hdr_format = "%d %Y%C%P\n"; - write_xact_format = " %-34W %12o%n\n"; - equity_format = "\n%D %Y%C%P\n%/ %-34W %12t\n"; - prices_format = "%[%Y/%m/%d %H:%M:%S %Z] %-10A %12t %12T\n"; - pricesdb_format = "P %[%Y/%m/%d %H:%M:%S] %A %t\n"; - - predicate = ""; - secondary_predicate = ""; - display_predicate = ""; + csv_register_format = "\"%D\",\"%P\",\"%A\",\"%t\",\"%T\"\n"; + plot_amount_format = "%D %(@S(@t))\n"; + plot_total_format = "%D %(@S(@T))\n"; + print_format = "\n%d %Y%C%P\n %-34W %12o%n\n%/ %-34W %12o%n\n"; + write_hdr_format = "%d %Y%C%P\n"; + write_xact_format = " %-34W %12o%n\n"; + equity_format = "\n%D %Y%C%P\n%/ %-34W %12t\n"; + prices_format = "%[%Y/%m/%d %H:%M:%S %Z] %-10A %12t %12T\n"; + pricesdb_format = "P %[%Y/%m/%d %H:%M:%S] %A %t\n"; - descend_expr = ""; - - head_entries = 0; - tail_entries = 0; - - show_collapsed = false; - show_subtotal = false; - show_totals = false; - show_related = false; - show_all_related = false; - show_inverted = false; - show_empty = false; - days_of_the_week = false; - by_payee = false; - comm_as_payee = false; - code_as_payee = false; - show_revalued = false; - show_revalued_only = false; download_quotes = false; + use_cache = false; + cache_dirty = false; debug_mode = false; verbose_mode = false; trace_mode = false; - keep_price = false; - keep_date = false; - keep_tag = false; - entry_sort = false; - sort_all = false; - - use_cache = false; - cache_dirty = 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]; - - assert(begin != end); - - // Treat the remaining command-line arguments as regular - // expressions, used for refining report results. - - for (std::list<std::string>::const_iterator i = begin; - i != end; - i++) - if ((*i)[0] == '-') { - if (! regexps[1].empty()) - regexps[1] += "|"; - regexps[1] += (*i).substr(1); - } - else if ((*i)[0] == '+') { - if (! regexps[0].empty()) - regexps[0] += "|"; - regexps[0] += (*i).substr(1); - } - else { - if (! regexps[0].empty()) - regexps[0] += "|"; - regexps[0] += *i; - } - - for (int i = 0; i < 2; i++) { - if (regexps[i].empty()) - continue; - - if (! predicate.empty()) - predicate += logical_and ? "&" : "|"; - - int add_predicate = 0; // 1 adds /.../, 2 adds ///.../ - if (i == 1) { - predicate += "!"; - } - else if (add_account_short_masks) { - if (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) { - show_subtotal = true; - add_predicate = 1; - } else { - add_predicate = 2; - } - } - else { - add_predicate = 1; - } - - if (i != 1 && command == "b" && account_regexp) { - if (! show_related && ! show_all_related) { - if (! display_predicate.empty()) - display_predicate += "&"; - if (! show_empty) - display_predicate += "T&"; - - if (add_predicate == 2) - display_predicate += "//"; - display_predicate += "/(?:"; - display_predicate += regexps[i]; - display_predicate += ")/"; - } - else if (! show_empty) { - if (! display_predicate.empty()) - display_predicate += "&"; - display_predicate += "T"; - } - } - - if (! account_regexp) - 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; -} - -static std::string expand_value_expr(const std::string& tmpl, - const std::string& expr) -{ - std::string xp = tmpl; - for (std::string::size_type i = xp.find('#'); - i != std::string::npos; - i = xp.find('#')) - xp = (std::string(xp, 0, i) + "(" + expr + ")" + - std::string(xp, i + 1)); - return xp; -} - -void config_t::process_options(const std::string& command, - strings_list::iterator arg, - strings_list::iterator args_end) -{ - // Configure some other options depending on report type - - if (command == "p" || command == "e" || command == "w") { - show_related = - show_all_related = true; - } - else if (command == "E") { - show_subtotal = true; - } - else if (show_related) { - if (command == "r") { - show_inverted = true; - } else { - show_subtotal = true; - show_all_related = true; - } - } - - if (command != "b" && command != "r") - amount_t::keep_base = true; - - // Process remaining command-line arguments - - if (command != "e") { - // Treat the remaining command-line arguments as regular - // expressions, used for refining report results. - - std::list<std::string>::iterator i = arg; - for (; i != args_end; i++) - if (*i == "--") - break; - - if (i != arg) - regexps_to_predicate(command, arg, i, true, - (command == "b" && ! show_subtotal && - display_predicate.empty())); - if (i != args_end && ++i != args_end) - regexps_to_predicate(command, i, args_end); - } - - // Setup the default value for the display predicate - - if (display_predicate.empty()) { - if (command == "b") { - if (! show_empty) - display_predicate = "T"; - if (! show_subtotal) { - if (! display_predicate.empty()) - display_predicate += "&"; - display_predicate += "l<=1"; - } - } - else if (command == "E") { - display_predicate = "t"; - } - else if (command == "r" && ! show_empty) { - display_predicate = "a"; - } - } - - DEBUG_PRINT("ledger.config.predicates", "Predicate: " << predicate); - DEBUG_PRINT("ledger.config.predicates", "Display P: " << display_predicate); - - // Setup the values of %t and %T, used in format strings - - if (! amount_expr.empty()) - ledger::amount_expr = amount_expr; - if (! total_expr.empty()) - ledger::total_expr = total_expr; - - // If downloading is to be supported, configure the updater - - if (! commodity_base_t::updater && download_quotes) - commodity_base_t::updater = - new quotes_by_script(price_db, pricing_leeway, cache_dirty); - - // Now setup the various formatting strings - - if (! date_format.empty()) - datetime_t::date_format = date_format; - - amount_t::keep_price = keep_price; - amount_t::keep_date = keep_date; - amount_t::keep_tag = keep_tag; - - if (! report_period.empty() && ! sort_all) - entry_sort = true; } -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) -{ - bool remember_components = false; - - 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)); - - // component_transactions looks for reported transaction that - // match the given `descend_expr', and then reports the - // transactions which made up the total for that reported - // transaction. - if (! descend_expr.empty()) { - std::list<std::string> descend_exprs; - - std::string::size_type beg = 0; - for (std::string::size_type pos = descend_expr.find(';'); - pos != std::string::npos; - beg = pos + 1, pos = descend_expr.find(';', beg)) - descend_exprs.push_back(std::string(descend_expr, beg, pos)); - descend_exprs.push_back(std::string(descend_expr, beg)); - - for (std::list<std::string>::reverse_iterator i = - descend_exprs.rbegin(); - i != descend_exprs.rend(); - i++) - ptrs.push_back(formatter = - new component_transactions(formatter, *i)); - - remember_components = true; - } - - // 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()) { - std::time_t cutoff = now; - if (! reconcile_date.empty()) - parse_date(reconcile_date.c_str(), &cutoff); - ptrs.push_back(formatter = - new reconcile_transactions - (formatter, value_t(reconcile_balance), cutoff)); - } - - // filter_transactions will only pass through transactions - // matching the `secondary_predicate'. - if (! secondary_predicate.empty()) - ptrs.push_back(formatter = - new filter_transactions(formatter, - secondary_predicate)); - - // sort_transactions will sort all the transactions it sees, based - // on the `sort_order' value expression. - if (! sort_string.empty()) { - if (entry_sort) - ptrs.push_back(formatter = - new sort_entries(formatter, sort_string)); - else - 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)); - - // 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) - ptrs.push_back(formatter = - new subtotal_transactions(formatter, remember_components)); - - if (days_of_the_week) - ptrs.push_back(formatter = - new dow_transactions(formatter, remember_components)); - else if (by_payee) - ptrs.push_back(formatter = - new by_payee_transactions(formatter, remember_components)); - - // interval_transactions groups transactions together based on a - // time period, such as weekly or monthly. - if (! report_period.empty()) { - ptrs.push_back(formatter = - new interval_transactions(formatter, report_period, - remember_components)); - ptrs.push_back(formatter = new sort_transactions(formatter, "d")); - } - } - - // 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 (comm_as_payee) - ptrs.push_back(formatter = new set_comm_as_payee(formatter)); - else if (code_as_payee) - ptrs.push_back(formatter = new set_code_as_payee(formatter)); - - return formatter; -} - -static void show_version(std::ostream& out) -{ - out << "Ledger " << ledger::version << ", the command-line accounting tool"; - out << "\n\nCopyright (c) 2003-2006, John Wiegley. All rights reserved.\n\n\ -This program is made available under the terms of the BSD Public License.\n\ -See LICENSE file included with the distribution for details and disclaimer.\n"; - out << "\n(modules: gmp, pcre"; -#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) - out << ", xml"; -#endif -#ifdef HAVE_LIBOFX - out << ", ofx"; -#endif - out << ")\n"; -} - -void option_full_help(std::ostream& out) -{ - out << "usage: ledger [options] COMMAND [ACCT REGEX]... [-- [PAYEE REGEX]...]\n\n\ -Basic options:\n\ - -H, --full-help display this help text\n\ - -h, --help display summarized help text\n\ - -v, --version show version information\n\ - -f, --file FILE read ledger data from FILE\n\ - -o, --output FILE write output to FILE\n\ - -i, --init-file FILE initialize ledger using FILE (default: ~/.ledgerrc)\n\ - --cache FILE use FILE as a binary cache when --file is not used\n\ - --no-cache don't use a cache, even if it would be appropriate\n\ - -a, --account NAME use NAME for the default account (useful with QIF)\n\n\ -Report filtering:\n\ - -c, --current show only current and past entries (not future)\n\ - -b, --begin DATE set report begin date\n\ - -e, --end DATE set report end date\n\ - -p, --period STR report using the given period\n\ - --period-sort EXPR sort each report period's entries by EXPR\n\ - -C, --cleared consider only cleared transactions\n\ - -U, --uncleared consider only uncleared transactions\n\ - -R, --real consider only real (non-virtual) transactions\n\ - -L, --actual consider only actual (non-automated) transactions\n\ - -r, --related calculate report using related transactions\n\ - --budget generate budget entries based on FILE\n\ - --add-budget show all transactions plus the budget\n\ - --unbudgeted show only unbudgeted transactions\n\ - --forecast EXPR generate forecast entries while EXPR is true\n\ - -l, --limit EXPR calculate only transactions matching EXPR\n\ - -t, --amount EXPR use EXPR to calculate the displayed amount\n\ - -T, --total EXPR use EXPR to calculate the displayed total\n\n\ -Output customization:\n\ - -n, --collapse register: collapse entries; balance: no grand total\n\ - -s, --subtotal balance: show sub-accounts; other: show subtotals\n\ - -P, --by-payee show summarized totals by payee\n\ - -x, --comm-as-payee set commodity name as the payee, for reporting\n\ - -E, --empty balance: show accounts with zero balance\n\ - -W, --weekly show weekly sub-totals\n\ - -M, --monthly show monthly sub-totals\n\ - -Y, --yearly show yearly sub-totals\n\ - --dow show a days-of-the-week report\n\ - -S, --sort EXPR sort report according to the value expression EXPR\n\ - -w, --wide for the default register report, use 132 columns\n\ - --head COUNT show only the first COUNT entries (negative inverts)\n\ - --tail COUNT show only the last COUNT entries (negative inverts)\n\ - --pager PAGER send all output through the given PAGER program\n\ - -A, --average report average transaction amount\n\ - -D, --deviation report deviation from the average\n\ - -%, --percentage report balance totals as a percentile of the parent\n\ - --totals in the \"xml\" report, include running total\n\ - -j, --amount-data print only raw amount data (useful for scripting)\n\ - -J, --total-data print only raw total data\n\ - -d, --display EXPR display only transactions matching EXPR\n\ - -y, --date-format STR use STR as the date format (default: %Y/%m/%d)\n\ - -F, --format STR use STR as the format; for each report type, use:\n\ - --balance-format --register-format --print-format\n\ - --plot-amount-format --plot-total-format --equity-format\n\ - --prices-format --wide-register-format\n\n\ -Commodity reporting:\n\ - --price-db FILE sets the price database to FILE (def: ~/.pricedb)\n\ - -L, --price-exp MINS download quotes only if newer than MINS (def: 1440)\n\ - -Q, --download download price information when needed\n\ - -O, --quantity report commodity totals (this is the default)\n\ - -B, --basis report cost basis of commodities\n\ - -V, --market report last known market value\n\ - -g, --performance report gain/loss for each displayed transaction\n\ - -G, --gain report net gain/loss\n\n"; - out - << "Commands:\n\ - balance [REGEXP]... show balance totals for matching accounts\n\ - register [REGEXP]... show register of matching transactions\n\ - print [REGEXP]... print all matching entries\n\ - xml [REGEXP]... print matching entries in XML format\n\ - equity [REGEXP]... output equity entries for matching accounts\n\ - prices [REGEXP]... display price history for matching commodities\n\ - entry DATE PAYEE AMT output a derived entry, based on the arguments\n"; -} - -void option_help(std::ostream& out) -{ - out << "usage: ledger [options] COMMAND [ACCT REGEX]... [-- [PAYEE REGEX]...]\n\n\ -Use -H to see all the help text on one page, or:\n\ - --help-calc calculation options\n\ - --help-disp display options\n\ - --help-comm commodity options\n"; - out << "\nBasic options:\n\ - -h, --help display this help text\n\ - -v, --version show version information\n\ - -f, --file FILE read ledger data from FILE\n\ - -o, --output FILE write output to FILE\n\ - -i, --init-file FILE initialize ledger using FILE (default: ~/.ledgerrc)\n\ - --cache FILE use FILE as a binary cache when --file is not used\n\ - --no-cache don't use a cache, even if it would be appropriate\n\ - -a, --account NAME use NAME for the default account (useful with QIF)\n\n\ -Commands:\n\ - balance [REGEXP]... show balance totals for matching accounts\n\ - register [REGEXP]... show register of matching transactions\n\ - print [REGEXP]... print all matching entries\n\ - xml [REGEXP]... print matching entries in XML format\n\ - equity [REGEXP]... output equity entries for matching accounts\n\ - prices [REGEXP]... display price history for matching commodities\n\ - entry DATE PAYEE AMT output a derived entry, based on the arguments\n"; -} - -void option_calc_help(std::ostream& out) -{ - out << "Options to control how a report is calculated:\n\ - -c, --current show only current and past entries (not future)\n\ - -b, --begin DATE set report begin date\n\ - -e, --end DATE set report end date\n\ - -p, --period STR report using the given period\n\ - --period-sort EXPR sort each report period's entries by EXPR\n\ - -C, --cleared consider only cleared transactions\n\ - -U, --uncleared consider only uncleared transactions\n\ - -R, --real consider only real (non-virtual) transactions\n\ - -L, --actual consider only actual (non-automated) transactions\n\ - -r, --related calculate report using related transactions\n\ - --budget generate budget entries based on FILE\n\ - --add-budget show all transactions plus the budget\n\ - --unbudgeted show only unbudgeted transactions\n\ - --forecast EXPR generate forecast entries while EXPR is true\n\ - -l, --limit EXPR calculate only transactions matching EXPR\n\ - -t, --amount EXPR use EXPR to calculate the displayed amount\n\ - -T, --total EXPR use EXPR to calculate the displayed total\n"; -} - -void option_disp_help(std::ostream& out) -{ - out << "Output to control how report results are displayed:\n\ - -n, --collapse register: collapse entries; balance: no grand total\n\ - -s, --subtotal balance: show sub-accounts; other: show subtotals\n\ - -P, --by-payee show summarized totals by payee\n\ - -x, --comm-as-payee set commodity name as the payee, for reporting\n\ - -E, --empty balance: show accounts with zero balance\n\ - -W, --weekly show weekly sub-totals\n\ - -M, --monthly show monthly sub-totals\n\ - -Y, --yearly show yearly sub-totals\n\ - --dow show a days-of-the-week report\n\ - -S, --sort EXPR sort report according to the value expression EXPR\n\ - -w, --wide for the default register report, use 132 columns\n\ - --head COUNT show only the first COUNT entries (negative inverts)\n\ - --tail COUNT show only the last COUNT entries (negative inverts)\n\ - --pager PAGER send all output through the given PAGER program\n\ - -A, --average report average transaction amount\n\ - -D, --deviation report deviation from the average\n\ - -%, --percentage report balance totals as a percentile of the parent\n\ - --totals in the \"xml\" report, include running total\n\ - -j, --amount-data print only raw amount data (useful for scripting)\n\ - -J, --total-data print only raw total data\n\ - -d, --display EXPR display only transactions matching EXPR\n\ - -y, --date-format STR use STR as the date format (default: %Y/%m/%d)\n\ - -F, --format STR use STR as the format; for each report type, use:\n\ - --balance-format --register-format --print-format\n\ - --plot-amount-format --plot-total-format --equity-format\n\ - --prices-format --wide-register-format\n"; -} - -void option_comm_help(std::ostream& out) -{ - out << "Options to control how commodity values are determined:\n\ - --price-db FILE sets the price database to FILE (def: ~/.pricedb)\n\ - -Z, --price-exp MINS download quotes only if newer than MINS (def: 1440)\n\ - -Q, --download download price information when needed\n\ - -O, --quantity report commodity totals (this is the default)\n\ - -B, --basis report cost basis of commodities\n\ - -V, --market report last known market value\n\ - -g, --performance report gain/loss for each displayed transaction\n\ - -G, --gain report net gain/loss\n"; -} - -////////////////////////////////////////////////////////////////////// -// -// Basic options - -OPT_BEGIN(full_help, "H") { - option_full_help(std::cout); - throw 0; -} OPT_END(full_help); - -OPT_BEGIN(help, "h") { - option_help(std::cout); - throw 0; -} OPT_END(help); - -OPT_BEGIN(help_calc, "") { - option_calc_help(std::cout); - throw 0; -} OPT_END(help_calc); - -OPT_BEGIN(help_disp, "") { - option_disp_help(std::cout); - throw 0; -} OPT_END(help_disp); - -OPT_BEGIN(help_comm, "") { - option_comm_help(std::cout); - throw 0; -} OPT_END(help_comm); - -OPT_BEGIN(version, "v") { - show_version(std::cout); - throw 0; -} OPT_END(version); - -OPT_BEGIN(init_file, "i:") { - std::string path = resolve_path(optarg); - if (access(path.c_str(), R_OK) != -1) - config->init_file = path; - else - throw new error(std::string("The init file '") + path + - "' does not exist or is not readable"); -} OPT_END(init_file); - -OPT_BEGIN(file, "f:") { - if (std::string(optarg) == "-") { - config->data_file = optarg; - } else { - std::string path = resolve_path(optarg); - if (access(path.c_str(), R_OK) != -1) - config->data_file = path; - else - throw new error(std::string("The ledger file '") + path + - "' does not exist or is not readable"); - } -} OPT_END(file); - -OPT_BEGIN(cache, ":") { - config->cache_file = resolve_path(optarg); -} OPT_END(cache); - -OPT_BEGIN(no_cache, "") { - config->cache_file = "<none>"; -} OPT_END(no_cache); - -OPT_BEGIN(output, "o:") { - if (std::string(optarg) != "-") { - std::string path = resolve_path(optarg); - if (access(path.c_str(), W_OK) != -1) - config->output_file = path; - else - throw new error(std::string("The output file '") + path + - "' is not writable"); - } -} OPT_END(output); - -OPT_BEGIN(account, "a:") { - config->account = optarg; -} OPT_END(account); - -OPT_BEGIN(debug, ":") { - config->debug_mode = true; - ::setenv("DEBUG_CLASS", optarg, 1); -} OPT_END(debug); - -OPT_BEGIN(verbose, "") { - config->verbose_mode = true; -} OPT_END(verbose); - -OPT_BEGIN(trace, "") { - config->trace_mode = true; -} OPT_END(trace); - -////////////////////////////////////////////////////////////////////// -// -// 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); - if (interval.begin) - std::strftime(buf, 127, formats[0], std::localtime(&interval.begin)); - else - throw new error(std::string("Could not determine beginning of period '") + - optarg + "'"); - - if (! config->predicate.empty()) - config->predicate += "&"; - config->predicate += "d>=["; - config->predicate += buf; - config->predicate += "]"; -} OPT_END(begin); - -OPT_BEGIN(end, "e:") { - char buf[128]; - interval_t interval(optarg); - if (interval.end) - std::strftime(buf, 127, formats[0], std::localtime(&interval.end)); - else - throw new error(std::string("Could not determine end of period '") + - optarg + "'"); - - 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"; -} OPT_END(current); - -OPT_BEGIN(cleared, "C") { - 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"; -} OPT_END(uncleared); - -OPT_BEGIN(real, "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"; -} OPT_END(actual); - -OPT_BEGIN(lots, "") { - config->keep_price = - config->keep_date = - config->keep_tag = true; -} OPT_END(lots); - -OPT_BEGIN(lot_prices, "") { - config->keep_price = true; -} OPT_END(lots_prices); - -OPT_BEGIN(lot_dates, "") { - config->keep_date = true; -} OPT_END(lots_dates); - -OPT_BEGIN(lot_tags, "") { - config->keep_tag = true; -} OPT_END(lots_tags); - -////////////////////////////////////////////////////////////////////// -// -// Output customization - -OPT_BEGIN(format, "F:") { - config->format_string = optarg; -} OPT_END(format); - -OPT_BEGIN(date_format, "y:") { - config->date_format = optarg; -} OPT_END(date_format); - -OPT_BEGIN(input_date_format, ":") { - std::strcpy(input_format, optarg); - formats[0] = input_format; -} OPT_END(input_date_format); - -OPT_BEGIN(balance_format, ":") { - config->balance_format = optarg; -} OPT_END(balance_format); - -OPT_BEGIN(register_format, ":") { - config->register_format = optarg; -} OPT_END(register_format); - -OPT_BEGIN(wide_register_format, ":") { - config->wide_register_format = optarg; -} OPT_END(wide_register_format); - -OPT_BEGIN(csv_register_format, ":") { - config->csv_register_format = optarg; -} OPT_END(csv_register_format); - -OPT_BEGIN(plot_amount_format, ":") { - config->plot_amount_format = optarg; -} OPT_END(plot_amount_format); - -OPT_BEGIN(plot_total_format, ":") { - config->plot_total_format = optarg; -} OPT_END(plot_total_format); - -OPT_BEGIN(print_format, ":") { - config->print_format = optarg; -} OPT_END(print_format); - -OPT_BEGIN(write_hdr_format, ":") { - config->write_hdr_format = optarg; -} OPT_END(write_hdr_format); - -OPT_BEGIN(write_xact_format, ":") { - config->write_xact_format = optarg; -} OPT_END(write_xact_format); - -OPT_BEGIN(equity_format, ":") { - config->equity_format = optarg; -} OPT_END(equity_format); - -OPT_BEGIN(prices_format, ":") { - config->prices_format = optarg; -} OPT_END(prices_format); - -OPT_BEGIN(wide, "w") { - config->register_format = config->wide_register_format; -} OPT_END(wide); - -OPT_BEGIN(head, ":") { - config->head_entries = std::atoi(optarg); -} OPT_END(head); - -OPT_BEGIN(tail, ":") { - config->tail_entries = std::atoi(optarg); -} OPT_END(tail); - -OPT_BEGIN(pager, ":") { - config->pager = optarg; -} OPT_END(pager); - -OPT_BEGIN(empty, "E") { - config->show_empty = true; -} OPT_END(empty); - -OPT_BEGIN(collapse, "n") { - config->show_collapsed = true; -} OPT_END(collapse); - -OPT_BEGIN(subtotal, "s") { - config->show_subtotal = true; -} OPT_END(subtotal); - -OPT_BEGIN(totals, "") { - config->show_totals = true; -} OPT_END(totals); - -OPT_BEGIN(sort, "S:") { - config->sort_string = optarg; -} OPT_END(sort); - -OPT_BEGIN(sort_entries, "") { - config->sort_string = optarg; - config->entry_sort = true; -} OPT_END(sort_entries); - -OPT_BEGIN(sort_all, "") { - config->sort_string = optarg; - config->entry_sort = false; - config->sort_all = true; -} OPT_END(sort_all); - -OPT_BEGIN(period_sort, ":") { - config->sort_string = optarg; - config->entry_sort = true; -} OPT_END(period_sort); - -OPT_BEGIN(related, "r") { - config->show_related = true; -} OPT_END(related); - -OPT_BEGIN(descend, "") { - std::string arg(optarg); - std::string::size_type beg = 0; - config->descend_expr = ""; - for (std::string::size_type pos = arg.find(';'); - pos != std::string::npos; - beg = pos + 1, pos = arg.find(';', beg)) - config->descend_expr += (std::string("t=={") + - std::string(arg, beg, pos) + "};"); - config->descend_expr += (std::string("t=={") + - std::string(arg, beg) + "}"); -} OPT_END(descend); - -OPT_BEGIN(descend_if, "") { - config->descend_expr = optarg; -} OPT_END(descend_if); - -OPT_BEGIN(period, "p:") { - if (config->report_period.empty()) { - config->report_period = optarg; - } else { - config->report_period += " "; - config->report_period += optarg; - } - - // If the period gives a beginning and/or ending date, make sure to - // modify the calculation predicate (via the --begin and --end - // options) to take this into account. - - char buf[128]; - 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 (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 += "]"; - - terminus = interval.end; - } -} OPT_END(period); - -OPT_BEGIN(daily, "") { - if (config->report_period.empty()) - config->report_period = "daily"; - else - config->report_period = std::string("daily ") + config->report_period; -} OPT_END(daily); - -OPT_BEGIN(weekly, "W") { - if (config->report_period.empty()) - config->report_period = "weekly"; - else - 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"; - else - 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"; - else - config->report_period = std::string("yearly ") + config->report_period; -} OPT_END(yearly); - -OPT_BEGIN(dow, "") { - config->days_of_the_week = true; -} OPT_END(dow); - -OPT_BEGIN(by_payee, "P") { - config->by_payee = true; -} OPT_END(by_payee); - -OPT_BEGIN(comm_as_payee, "x") { - config->comm_as_payee = true; -} OPT_END(comm_as_payee); - -OPT_BEGIN(code_as_payee, "") { - config->code_as_payee = true; -} OPT_END(code_as_payee); - -OPT_BEGIN(budget, "") { - config->budget_flags = BUDGET_BUDGETED; -} OPT_END(budget); - -OPT_BEGIN(add_budget, "") { - config->budget_flags = BUDGET_BUDGETED | BUDGET_UNBUDGETED; -} OPT_END(add_budget); - -OPT_BEGIN(unbudgeted, "") { - config->budget_flags = BUDGET_UNBUDGETED; -} OPT_END(unbudgeted); - -OPT_BEGIN(forecast, ":") { - config->forecast_limit = optarg; -} OPT_END(forecast); - -OPT_BEGIN(reconcile, ":") { - config->reconcile_balance = optarg; -} OPT_END(reconcile); - -OPT_BEGIN(reconcile_date, ":") { - 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 += ")"; -} OPT_END(limit); - -OPT_BEGIN(only, ":") { - if (! config->secondary_predicate.empty()) - config->secondary_predicate += "&"; - config->secondary_predicate += "("; - config->secondary_predicate += optarg; - config->secondary_predicate += ")"; -} OPT_END(only); - -OPT_BEGIN(display, "d:") { - 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:") { - ledger::amount_expr = optarg; -} OPT_END(amount); - -OPT_BEGIN(total, "T:") { - ledger::total_expr = optarg; -} OPT_END(total); - -OPT_BEGIN(amount_data, "j") { - config->format_string = config->plot_amount_format; -} OPT_END(amount_data); - -OPT_BEGIN(total_data, "J") { - config->format_string = config->plot_total_format; -} OPT_END(total_data); - -OPT_BEGIN(ansi, "") { - format_t::ansi_codes = true; - format_t::ansi_invert = false; -} OPT_END(ansi); - -OPT_BEGIN(ansi_invert, "") { - format_t::ansi_codes = - format_t::ansi_invert = true; -} OPT_END(ansi); - -////////////////////////////////////////////////////////////////////// -// -// Commodity reporting - -OPT_BEGIN(base, ":") { - amount_t::keep_base = true; -} OPT_END(base); - -OPT_BEGIN(price_db, ":") { - config->price_db = optarg; -} OPT_END(price_db); - -OPT_BEGIN(price_exp, "Z:") { - config->pricing_leeway = std::atol(optarg) * 60; -} OPT_END(price_exp); - -OPT_BEGIN(download, "Q") { - config->download_quotes = true; -} OPT_END(download); - -OPT_BEGIN(quantity, "O") { - ledger::amount_expr = "@a"; - ledger::total_expr = "@O"; -} OPT_END(quantity); - -OPT_BEGIN(basis, "B") { - ledger::amount_expr = "@b"; - ledger::total_expr = "@B"; -} OPT_END(basis); - -OPT_BEGIN(price, "I") { - ledger::amount_expr = "@i"; - ledger::total_expr = "@I"; -} OPT_END(price); - -OPT_BEGIN(market, "V") { - config->show_revalued = true; - - ledger::amount_expr = "@v"; - ledger::total_expr = "@V"; -} OPT_END(market); - -namespace { - void parse_price_setting(const char * optarg) - { - char * equals = std::strchr(optarg, '='); - if (! equals) - return; - - while (std::isspace(*optarg)) - optarg++; - while (equals > optarg && std::isspace(*(equals - 1))) - equals--; - - std::string symbol(optarg, 0, equals - optarg); - amount_t price(equals + 1); - - if (commodity_t * commodity = commodity_t::find_or_create(symbol)) { - commodity->add_price(now, price); - commodity->history()->bogus_time = now; - } - } -} - -OPT_BEGIN(set_price, ":") { - std::string arg(optarg); - std::string::size_type beg = 0; - for (std::string::size_type pos = arg.find(';'); - pos != std::string::npos; - beg = pos + 1, pos = arg.find(';', beg)) - parse_price_setting(std::string(arg, beg, pos).c_str()); - parse_price_setting(std::string(arg, beg).c_str()); -} OPT_END(set_price); - -OPT_BEGIN(performance, "g") { - ledger::amount_expr = "@P(@a,@m)-@b"; - ledger::total_expr = "@P(@O,@m)-@B"; -} OPT_END(performance); - -OPT_BEGIN(gain, "G") { - config->show_revalued = - config->show_revalued_only = true; - - ledger::amount_expr = "@a"; - ledger::total_expr = "@G"; -} OPT_END(gain); - -OPT_BEGIN(average, "A") { - ledger::total_expr = expand_value_expr("@A(#)", ledger::total_expr.expr); -} OPT_END(average); - -OPT_BEGIN(deviation, "D") { - ledger::total_expr = expand_value_expr("@t-@A(#)", ledger::total_expr.expr); -} OPT_END(deviation); - -OPT_BEGIN(percentage, "%") { - ledger::total_expr = expand_value_expr("^#&{100.0%}*(#/^#)", - ledger::total_expr.expr); -} OPT_END(percentage); - -////////////////////////////////////////////////////////////////////// - -option_t config_options[CONFIG_OPTIONS_SIZE] = { - { "account", 'a', true, opt_account, false }, - { "actual", 'L', false, opt_actual, false }, - { "add-budget", '\0', false, opt_add_budget, false }, - { "amount", 't', true, opt_amount, false }, - { "amount-data", 'j', false, opt_amount_data, false }, - { "ansi", '\0', false, opt_ansi, false }, - { "ansi-invert", '\0', false, opt_ansi_invert, false }, - { "average", 'A', false, opt_average, false }, - { "balance-format", '\0', true, opt_balance_format, false }, - { "base", '\0', false, opt_base, false }, - { "basis", 'B', false, opt_basis, false }, - { "begin", 'b', true, opt_begin, false }, - { "budget", '\0', false, opt_budget, false }, - { "by-payee", 'P', false, opt_by_payee, false }, - { "cache", '\0', true, opt_cache, false }, - { "cleared", 'C', false, opt_cleared, false }, - { "code-as-payee", '\0', false, opt_code_as_payee, false }, - { "collapse", 'n', false, opt_collapse, false }, - { "comm-as-payee", 'x', false, opt_comm_as_payee, false }, - { "csv-register-format", '\0', true, opt_csv_register_format, false }, - { "current", 'c', false, opt_current, false }, - { "daily", '\0', false, opt_daily, false }, - { "date-format", 'y', true, opt_date_format, false }, - { "debug", '\0', true, opt_debug, false }, - { "descend", '\0', true, opt_descend, false }, - { "descend-if", '\0', true, opt_descend_if, false }, - { "deviation", 'D', false, opt_deviation, false }, - { "display", 'd', true, opt_display, false }, - { "dow", '\0', false, opt_dow, false }, - { "download", 'Q', false, opt_download, false }, - { "effective", '\0', false, opt_effective, false }, - { "empty", 'E', false, opt_empty, false }, - { "end", 'e', true, opt_end, false }, - { "equity-format", '\0', true, opt_equity_format, false }, - { "file", 'f', true, opt_file, false }, - { "forecast", '\0', true, opt_forecast, false }, - { "format", 'F', true, opt_format, false }, - { "full-help", 'H', false, opt_full_help, false }, - { "gain", 'G', false, opt_gain, false }, - { "head", '\0', true, opt_head, false }, - { "help", 'h', false, opt_help, false }, - { "help-calc", '\0', false, opt_help_calc, false }, - { "help-comm", '\0', false, opt_help_comm, false }, - { "help-disp", '\0', false, opt_help_disp, false }, - { "init-file", 'i', true, opt_init_file, false }, - { "input-date-format", '\0', true, opt_input_date_format, false }, - { "limit", 'l', true, opt_limit, false }, - { "lot-dates", '\0', false, opt_lot_dates, false }, - { "lot-prices", '\0', false, opt_lot_prices, false }, - { "lot-tags", '\0', false, opt_lot_tags, false }, - { "lots", '\0', false, opt_lots, false }, - { "market", 'V', false, opt_market, false }, - { "monthly", 'M', false, opt_monthly, false }, - { "no-cache", '\0', false, opt_no_cache, false }, - { "only", '\0', true, opt_only, false }, - { "output", 'o', true, opt_output, false }, - { "pager", '\0', true, opt_pager, false }, - { "percentage", '%', false, opt_percentage, false }, - { "performance", 'g', false, opt_performance, false }, - { "period", 'p', true, opt_period, false }, - { "period-sort", '\0', true, opt_period_sort, false }, - { "plot-amount-format", '\0', true, opt_plot_amount_format, false }, - { "plot-total-format", '\0', true, opt_plot_total_format, false }, - { "price", 'I', false, opt_price, false }, - { "price-db", '\0', true, opt_price_db, false }, - { "price-exp", 'Z', true, opt_price_exp, false }, - { "prices-format", '\0', true, opt_prices_format, false }, - { "print-format", '\0', true, opt_print_format, false }, - { "quantity", 'O', false, opt_quantity, false }, - { "real", 'R', false, opt_real, false }, - { "reconcile", '\0', true, opt_reconcile, false }, - { "reconcile-date", '\0', true, opt_reconcile_date, false }, - { "register-format", '\0', true, opt_register_format, false }, - { "related", 'r', false, opt_related, false }, - { "set-price", '\0', true, opt_set_price, false }, - { "sort", 'S', true, opt_sort, false }, - { "sort-all", '\0', true, opt_sort_all, false }, - { "sort-entries", '\0', true, opt_sort_entries, false }, - { "subtotal", 's', false, opt_subtotal, false }, - { "tail", '\0', true, opt_tail, false }, - { "total", 'T', true, opt_total, false }, - { "total-data", 'J', false, opt_total_data, false }, - { "totals", '\0', false, opt_totals, false }, - { "trace", '\0', false, opt_trace, false }, - { "unbudgeted", '\0', false, opt_unbudgeted, false }, - { "uncleared", 'U', false, opt_uncleared, false }, - { "verbose", '\0', false, opt_verbose, false }, - { "version", 'v', false, opt_version, false }, - { "weekly", 'W', false, opt_weekly, false }, - { "wide", 'w', false, opt_wide, false }, - { "wide-register-format", '\0', true, opt_wide_register_format, false }, - { "write-hdr-format", '\0', true, opt_write_hdr_format, false }, - { "write-xact-format", '\0', true, opt_write_xact_format, false }, - { "yearly", 'Y', false, opt_yearly, false }, -}; - ////////////////////////////////////////////////////////////////////// void trace(const std::string& cat, const std::string& str) @@ -13,112 +13,38 @@ namespace ledger { class config_t { public: - // These options can all be set used text fields. - - strings_list price_settings; - std::string init_file; - std::string data_file; - std::string cache_file; - std::string price_db; - std::string output_file; - std::string account; - std::string predicate; - std::string secondary_predicate; - std::string display_predicate; - std::string report_period; - std::string report_period_sort; - std::string format_string; - std::string balance_format; - std::string register_format; - std::string wide_register_format; - std::string csv_register_format; - std::string plot_amount_format; - std::string plot_total_format; - std::string print_format; - std::string write_hdr_format; - std::string write_xact_format; - std::string equity_format; - std::string prices_format; - std::string pricesdb_format; - std::string date_format; - std::string sort_string; - std::string amount_expr; - std::string total_expr; - std::string descend_expr; - std::string forecast_limit; - std::string reconcile_balance; - std::string reconcile_date; - std::string pager; - unsigned long budget_flags; - unsigned long pricing_leeway; - int head_entries; - int tail_entries; - bool show_collapsed; - bool show_subtotal; - bool show_totals; - bool show_related; - bool show_all_related; - bool show_inverted; - bool show_empty; - bool days_of_the_week; - bool by_payee; - bool comm_as_payee; - bool code_as_payee; - bool show_revalued; - bool show_revalued_only; - bool download_quotes; - bool use_cache; - bool cache_dirty; - bool debug_mode; - bool verbose_mode; - bool trace_mode; - bool keep_price; - bool keep_date; - bool keep_tag; - bool entry_sort; - bool sort_all; - - 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); + std::string init_file; + std::string data_file; + std::string cache_file; + std::string price_db; + + std::string balance_format; + std::string register_format; + std::string wide_register_format; + std::string csv_register_format; + std::string plot_amount_format; + std::string plot_total_format; + std::string print_format; + std::string write_hdr_format; + std::string write_xact_format; + std::string equity_format; + std::string prices_format; + std::string pricesdb_format; + + std::string account; + std::string pager; + + bool download_quotes; + bool use_cache; + bool cache_dirty; + bool debug_mode; + bool verbose_mode; + bool trace_mode; + + config_t(); }; -#define CONFIG_OPTIONS_SIZE 94 -extern option_t config_options[CONFIG_OPTIONS_SIZE]; - -void option_help(std::ostream& out); - -#define OPT_BEGIN(tag, chars) \ - void opt_ ## tag(const char * optarg) - -#define OPT_END(tag) +////////////////////////////////////////////////////////////////////// std::string resolve_path(const std::string& path); @@ -468,11 +468,17 @@ bool account_t::valid() const for (accounts_map::const_iterator i = accounts.begin(); i != accounts.end(); - i++) + i++) { + if (this == (*i).second) { + DEBUG_PRINT("ledger.validate", "account_t: parent refers to itself!"); + return false; + } + if (! (*i).second->valid()) { DEBUG_PRINT("ledger.validate", "account_t: child not valid"); return false; } + } return true; } @@ -45,5 +45,6 @@ namespace ledger { } #include <config.h> +#include <report.h> #endif // _LEDGER_H @@ -23,7 +23,8 @@ using namespace ledger; -int parse_and_report(config_t& config, int argc, char * argv[], char * envp[]) +int parse_and_report(config_t& config, report_t& report, + int argc, char * argv[], char * envp[]) { // Configure the terminus for value expressions @@ -32,7 +33,7 @@ int parse_and_report(config_t& config, int argc, char * argv[], char * envp[]) // Parse command-line arguments, and those set in the environment std::list<std::string> args; - config.process_arguments(argc - 1, argv + 1, false, args); + process_arguments(ledger::config_options, argc - 1, argv + 1, false, args); if (args.empty()) { option_help(std::cerr); @@ -48,19 +49,19 @@ int parse_and_report(config_t& config, int argc, char * argv[], char * envp[]) TRACE(main, "Processing options and environment variables"); - config.process_environment(envp, "LEDGER_"); + process_environment(ledger::config_options, envp, "LEDGER_"); #if 1 // These are here for backwards compatability, but are deprecated. if (const char * p = std::getenv("LEDGER")) - config.process_option("file", p); + process_option(ledger::config_options, "file", p); if (const char * p = std::getenv("LEDGER_INIT")) - config.process_option("init-file", p); + process_option(ledger::config_options, "init-file", p); if (const char * p = std::getenv("PRICE_HIST")) - config.process_option("price-db", p); + process_option(ledger::config_options, "price-db", p); if (const char * p = std::getenv("PRICE_EXP")) - config.process_option("price-exp", p); + process_option(ledger::config_options, "price-exp", p); #endif const char * p = std::getenv("HOME"); @@ -100,6 +101,8 @@ int parse_and_report(config_t& config, int argc, char * argv[], char * envp[]) command = "p"; else if (command == "output") command = "w"; + else if (command == "dump") + command = "W"; else if (command == "emacs" || command == "lisp") command = "x"; else if (command == "xml") @@ -118,6 +121,7 @@ int parse_and_report(config_t& config, int argc, char * argv[], char * envp[]) } else if (command == "parse") { value_expr expr(ledger::parse_value_expr(*arg)); + if (config.verbose_mode) { std::cout << "Value expression tree:" << std::endl; ledger::dump_value_expr(std::cout, expr.get()); @@ -127,20 +131,9 @@ int parse_and_report(config_t& config, int argc, char * argv[], char * envp[]) std::cout << std::endl << std::endl; std::cout << "Result of computation: "; } - value_t result = guarded_compute(expr.get()); - if (! config.keep_price || ! config.keep_date || ! config.keep_tag) { - switch (result.type) { - case value_t::AMOUNT: - case value_t::BALANCE: - case value_t::BALANCE_PAIR: - result = result.strip_annotations(); - break; - default: - break; - } - } - std::cout << result << std::endl; + value_t result = guarded_compute(expr.get()); + std::cout << result.strip_annotations() << std::endl; return 0; } @@ -171,11 +164,23 @@ int parse_and_report(config_t& config, int argc, char * argv[], char * envp[]) throw new error("The 'output' command requires a file argument"); first_arg = *arg++; } + else if (command == "W") { + if (report.output_file.empty()) + throw new error("The 'dump' command requires use of the --output option"); + } TRACE(options, std::string("Post-processing options ") + "for command \"" + command + "\""); - config.process_options(command, arg, args.end()); + report.process_options(command, arg, args.end()); + + // If downloading is to be supported, configure the updater + + // jww (2006-03-23): Should the pricing_leeway be in config_t? + // Should download_quotes be in report_t? + if (! commodity_base_t::updater && config.download_quotes) + commodity_base_t::updater = + new quotes_by_script(config.price_db, report.pricing_leeway, config.cache_dirty); std::auto_ptr<entry_t> new_entry; if (command == "e") { @@ -191,8 +196,8 @@ int parse_and_report(config_t& config, int argc, char * argv[], char * envp[]) #endif std::ostream * out = &std::cout; - if (! config.output_file.empty()) { - out = new std::ofstream(config.output_file.c_str()); + if (! report.output_file.empty()) { + out = new std::ofstream(report.output_file.c_str()); } #ifdef HAVE_UNIX_PIPES else if (! config.pager.empty()) { @@ -242,6 +247,7 @@ int parse_and_report(config_t& config, int argc, char * argv[], char * envp[]) if (command == "expr") { value_expr expr(ledger::parse_value_expr(*arg)); + if (config.verbose_mode) { std::cout << "Value expression tree:" << std::endl; ledger::dump_value_expr(std::cout, expr.get()); @@ -251,20 +257,9 @@ int parse_and_report(config_t& config, int argc, char * argv[], char * envp[]) std::cout << std::endl << std::endl; std::cout << "Result of computation: "; } - value_t result = guarded_compute(expr.get()); - if (! config.keep_price || ! config.keep_date || ! config.keep_tag) { - switch (result.type) { - case value_t::AMOUNT: - case value_t::BALANCE: - case value_t::BALANCE_PAIR: - result = result.strip_annotations(); - break; - default: - break; - } - } - std::cout << result << std::endl; + value_t result = guarded_compute(expr.get()); + std::cout << result.strip_annotations() << std::endl; return 0; } @@ -273,8 +268,8 @@ int parse_and_report(config_t& config, int argc, char * argv[], char * envp[]) const std::string * format; - if (! config.format_string.empty()) - format = &config.format_string; + if (! report.format_string.empty()) + format = &report.format_string; else if (command == "b") format = &config.balance_format; else if (command == "r") @@ -301,13 +296,14 @@ int parse_and_report(config_t& config, int argc, char * argv[], char * envp[]) formatter = new format_entries(*out, *format); else if (command == "x") formatter = new format_emacs_transactions(*out); - else if (command == "X") { #if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) - formatter = new format_xml_entries(*out, config.show_totals); + else if (command == "X") + formatter = new format_xml_entries(*out, report.show_totals); #else + else if (command == "X") throw new error("XML support was not compiled into this copy of Ledger"); #endif - } else + else formatter = new format_transactions(*out, *format); if (command == "w") { @@ -315,10 +311,17 @@ int parse_and_report(config_t& config, int argc, char * argv[], char * envp[]) write_textual_journal(*journal, first_arg, *formatter, config.write_hdr_format, *out); TRACE_POP(text_writer, "Finished writing"); - } else { + } + else if (command == "W") { + TRACE_PUSH(binary_writer, "Writing binary file"); + std::ofstream stream(report.output_file.c_str()); + write_binary_journal(stream, journal.get()); + TRACE_POP(binary_writer, "Finished writing"); + } + else { TRACE_PUSH(main, "Walking journal entries"); - formatter = config.chain_xact_handlers(command, formatter, journal.get(), + formatter = report.chain_xact_handlers(command, formatter, journal.get(), journal->master, formatter_ptrs); if (command == "e") walk_transactions(new_entry->transactions, *formatter); @@ -338,14 +341,14 @@ int parse_and_report(config_t& config, int argc, char * argv[], char * envp[]) if (command == "b") { TRACE_PUSH(main, "Walking journal accounts"); - format_account acct_formatter(*out, *format, config.display_predicate); + format_account acct_formatter(*out, *format, report.display_predicate); sum_accounts(*journal->master); - walk_accounts(*journal->master, acct_formatter, config.sort_string); + walk_accounts(*journal->master, acct_formatter, report.sort_string); acct_formatter.flush(); if (account_has_xdata(*journal->master)) { account_xdata_t& xdata = account_xdata(*journal->master); - if (! config.show_collapsed && xdata.total) { + if (! report.show_collapsed && xdata.total) { *out << "--------------------\n"; xdata.value = xdata.total; acct_formatter.format.format(*out, details_t(*journal->master)); @@ -356,9 +359,9 @@ int parse_and_report(config_t& config, int argc, char * argv[], char * envp[]) else if (command == "E") { TRACE_PUSH(main, "Walking journal accounts"); - format_equity acct_formatter(*out, *format, config.display_predicate); + format_equity acct_formatter(*out, *format, report.display_predicate); sum_accounts(*journal->master); - walk_accounts(*journal->master, acct_formatter, config.sort_string); + walk_accounts(*journal->master, acct_formatter, report.sort_string); acct_formatter.flush(); TRACE_POP(main, "Finished account walk"); @@ -373,7 +376,7 @@ int parse_and_report(config_t& config, int argc, char * argv[], char * envp[]) clear_account_xdata acct_cleaner; walk_accounts(*journal->master, acct_cleaner); - if (! config.output_file.empty()) + if (! report.output_file.empty()) delete out; for (std::list<item_handler<transaction_t> *>::iterator i @@ -419,8 +422,11 @@ int main(int argc, char * argv[], char * envp[]) ledger::do_cleanup = false; #endif config_t config; - TRACE_PUSH(main, "Starting Ledger " PACKAGE_VERSION); - int status = parse_and_report(config, argc, argv, envp); + report_t report; + ledger::config = &config; + ledger::report = &report; + TRACE_PUSH(main, "Ledger starting"); + int status = parse_and_report(config, report, argc, argv, envp); TRACE_POP(main, "Ledger done"); return status; } @@ -1,5 +1,6 @@ #include "option.h" #include "config.h" +#include "report.h" #include "debug.h" #include "error.h" @@ -166,3 +167,876 @@ void process_environment(option_t * options, char ** envp, } } } + +////////////////////////////////////////////////////////////////////// + +namespace ledger { + +config_t * config = NULL; +report_t * report = NULL; + +static void show_version(std::ostream& out) +{ + out << "Ledger " << ledger::version << ", the command-line accounting tool"; + out << "\n\nCopyright (c) 2003-2006, John Wiegley. All rights reserved.\n\n\ +This program is made available under the terms of the BSD Public License.\n\ +See LICENSE file included with the distribution for details and disclaimer.\n"; + out << "\n(modules: gmp, pcre"; +#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) + out << ", xml"; +#endif +#ifdef HAVE_LIBOFX + out << ", ofx"; +#endif + out << ")\n"; +} + +void option_full_help(std::ostream& out) +{ + out << "usage: ledger [options] COMMAND [ACCT REGEX]... [-- [PAYEE REGEX]...]\n\n\ +Basic options:\n\ + -H, --full-help display this help text\n\ + -h, --help display summarized help text\n\ + -v, --version show version information\n\ + -f, --file FILE read ledger data from FILE\n\ + -o, --output FILE write output to FILE\n\ + -i, --init-file FILE initialize ledger using FILE (default: ~/.ledgerrc)\n\ + --cache FILE use FILE as a binary cache when --file is not used\n\ + --no-cache don't use a cache, even if it would be appropriate\n\ + -a, --account NAME use NAME for the default account (useful with QIF)\n\n\ +Report filtering:\n\ + -c, --current show only current and past entries (not future)\n\ + -b, --begin DATE set report begin date\n\ + -e, --end DATE set report end date\n\ + -p, --period STR report using the given period\n\ + --period-sort EXPR sort each report period's entries by EXPR\n\ + -C, --cleared consider only cleared transactions\n\ + -U, --uncleared consider only uncleared transactions\n\ + -R, --real consider only real (non-virtual) transactions\n\ + -L, --actual consider only actual (non-automated) transactions\n\ + -r, --related calculate report using related transactions\n\ + --budget generate budget entries based on FILE\n\ + --add-budget show all transactions plus the budget\n\ + --unbudgeted show only unbudgeted transactions\n\ + --forecast EXPR generate forecast entries while EXPR is true\n\ + -l, --limit EXPR calculate only transactions matching EXPR\n\ + -t, --amount EXPR use EXPR to calculate the displayed amount\n\ + -T, --total EXPR use EXPR to calculate the displayed total\n\n\ +Output customization:\n\ + -n, --collapse register: collapse entries; balance: no grand total\n\ + -s, --subtotal balance: show sub-accounts; other: show subtotals\n\ + -P, --by-payee show summarized totals by payee\n\ + -x, --comm-as-payee set commodity name as the payee, for reporting\n\ + -E, --empty balance: show accounts with zero balance\n\ + -W, --weekly show weekly sub-totals\n\ + -M, --monthly show monthly sub-totals\n\ + -Y, --yearly show yearly sub-totals\n\ + --dow show a days-of-the-week report\n\ + -S, --sort EXPR sort report according to the value expression EXPR\n\ + -w, --wide for the default register report, use 132 columns\n\ + --head COUNT show only the first COUNT entries (negative inverts)\n\ + --tail COUNT show only the last COUNT entries (negative inverts)\n\ + --pager PAGER send all output through the given PAGER program\n\ + -A, --average report average transaction amount\n\ + -D, --deviation report deviation from the average\n\ + -%, --percentage report balance totals as a percentile of the parent\n\ + --totals in the \"xml\" report, include running total\n\ + -j, --amount-data print only raw amount data (useful for scripting)\n\ + -J, --total-data print only raw total data\n\ + -d, --display EXPR display only transactions matching EXPR\n\ + -y, --date-format STR use STR as the date format (default: %Y/%m/%d)\n\ + -F, --format STR use STR as the format; for each report type, use:\n\ + --balance-format --register-format --print-format\n\ + --plot-amount-format --plot-total-format --equity-format\n\ + --prices-format --wide-register-format\n\n\ +Commodity reporting:\n\ + --price-db FILE sets the price database to FILE (def: ~/.pricedb)\n\ + -L, --price-exp MINS download quotes only if newer than MINS (def: 1440)\n\ + -Q, --download download price information when needed\n\ + -O, --quantity report commodity totals (this is the default)\n\ + -B, --basis report cost basis of commodities\n\ + -V, --market report last known market value\n\ + -g, --performance report gain/loss for each displayed transaction\n\ + -G, --gain report net gain/loss\n\n\ +Commands:\n\ + balance [REGEXP]... show balance totals for matching accounts\n\ + register [REGEXP]... show register of matching transactions\n\ + print [REGEXP]... print all matching entries\n\ + xml [REGEXP]... print matching entries in XML format\n\ + equity [REGEXP]... output equity entries for matching accounts\n\ + prices [REGEXP]... display price history for matching commodities\n\ + entry DATE PAYEE AMT output a derived entry, based on the arguments\n"; +} + +void option_help(std::ostream& out) +{ + out << "usage: ledger [options] COMMAND [ACCT REGEX]... [-- [PAYEE REGEX]...]\n\n\ +Use -H to see all the help text on one page, or:\n\ + --help-calc calculation options\n\ + --help-disp display options\n\ + --help-comm commodity options\n\n\ +Basic options:\n\ + -h, --help display this help text\n\ + -v, --version show version information\n\ + -f, --file FILE read ledger data from FILE\n\ + -o, --output FILE write output to FILE\n\ + -i, --init-file FILE initialize ledger using FILE (default: ~/.ledgerrc)\n\ + --cache FILE use FILE as a binary cache when --file is not used\n\ + --no-cache don't use a cache, even if it would be appropriate\n\ + -a, --account NAME use NAME for the default account (useful with QIF)\n\n\ +Commands:\n\ + balance [REGEXP]... show balance totals for matching accounts\n\ + register [REGEXP]... show register of matching transactions\n\ + print [REGEXP]... print all matching entries\n\ + xml [REGEXP]... print matching entries in XML format\n\ + equity [REGEXP]... output equity entries for matching accounts\n\ + prices [REGEXP]... display price history for matching commodities\n\ + entry DATE PAYEE AMT output a derived entry, based on the arguments\n"; +} + +void option_calc_help(std::ostream& out) +{ + out << "Options to control how a report is calculated:\n\ + -c, --current show only current and past entries (not future)\n\ + -b, --begin DATE set report begin date\n\ + -e, --end DATE set report end date\n\ + -p, --period STR report using the given period\n\ + --period-sort EXPR sort each report period's entries by EXPR\n\ + -C, --cleared consider only cleared transactions\n\ + -U, --uncleared consider only uncleared transactions\n\ + -R, --real consider only real (non-virtual) transactions\n\ + -L, --actual consider only actual (non-automated) transactions\n\ + -r, --related calculate report using related transactions\n\ + --budget generate budget entries based on FILE\n\ + --add-budget show all transactions plus the budget\n\ + --unbudgeted show only unbudgeted transactions\n\ + --forecast EXPR generate forecast entries while EXPR is true\n\ + -l, --limit EXPR calculate only transactions matching EXPR\n\ + -t, --amount EXPR use EXPR to calculate the displayed amount\n\ + -T, --total EXPR use EXPR to calculate the displayed total\n"; +} + +void option_disp_help(std::ostream& out) +{ + out << "Output to control how report results are displayed:\n\ + -n, --collapse register: collapse entries; balance: no grand total\n\ + -s, --subtotal balance: show sub-accounts; other: show subtotals\n\ + -P, --by-payee show summarized totals by payee\n\ + -x, --comm-as-payee set commodity name as the payee, for reporting\n\ + -E, --empty balance: show accounts with zero balance\n\ + -W, --weekly show weekly sub-totals\n\ + -M, --monthly show monthly sub-totals\n\ + -Y, --yearly show yearly sub-totals\n\ + --dow show a days-of-the-week report\n\ + -S, --sort EXPR sort report according to the value expression EXPR\n\ + -w, --wide for the default register report, use 132 columns\n\ + --head COUNT show only the first COUNT entries (negative inverts)\n\ + --tail COUNT show only the last COUNT entries (negative inverts)\n\ + --pager PAGER send all output through the given PAGER program\n\ + -A, --average report average transaction amount\n\ + -D, --deviation report deviation from the average\n\ + -%, --percentage report balance totals as a percentile of the parent\n\ + --totals in the \"xml\" report, include running total\n\ + -j, --amount-data print only raw amount data (useful for scripting)\n\ + -J, --total-data print only raw total data\n\ + -d, --display EXPR display only transactions matching EXPR\n\ + -y, --date-format STR use STR as the date format (default: %Y/%m/%d)\n\ + -F, --format STR use STR as the format; for each report type, use:\n\ + --balance-format --register-format --print-format\n\ + --plot-amount-format --plot-total-format --equity-format\n\ + --prices-format --wide-register-format\n"; +} + +void option_comm_help(std::ostream& out) +{ + out << "Options to control how commodity values are determined:\n\ + --price-db FILE sets the price database to FILE (def: ~/.pricedb)\n\ + -Z, --price-exp MINS download quotes only if newer than MINS (def: 1440)\n\ + -Q, --download download price information when needed\n\ + -O, --quantity report commodity totals (this is the default)\n\ + -B, --basis report cost basis of commodities\n\ + -V, --market report last known market value\n\ + -g, --performance report gain/loss for each displayed transaction\n\ + -G, --gain report net gain/loss\n"; +} + +////////////////////////////////////////////////////////////////////// +// +// Basic options + +OPT_BEGIN(full_help, "H") { + option_full_help(std::cout); + throw 0; +} OPT_END(full_help); + +OPT_BEGIN(help, "h") { + option_help(std::cout); + throw 0; +} OPT_END(help); + +OPT_BEGIN(help_calc, "") { + option_calc_help(std::cout); + throw 0; +} OPT_END(help_calc); + +OPT_BEGIN(help_disp, "") { + option_disp_help(std::cout); + throw 0; +} OPT_END(help_disp); + +OPT_BEGIN(help_comm, "") { + option_comm_help(std::cout); + throw 0; +} OPT_END(help_comm); + +OPT_BEGIN(version, "v") { + show_version(std::cout); + throw 0; +} OPT_END(version); + +OPT_BEGIN(init_file, "i:") { + std::string path = resolve_path(optarg); + if (access(path.c_str(), R_OK) != -1) + config->init_file = path; + else + throw new error(std::string("The init file '") + path + + "' does not exist or is not readable"); +} OPT_END(init_file); + +OPT_BEGIN(file, "f:") { + if (std::string(optarg) == "-") { + config->data_file = optarg; + } else { + std::string path = resolve_path(optarg); + if (access(path.c_str(), R_OK) != -1) + config->data_file = path; + else + throw new error(std::string("The ledger file '") + path + + "' does not exist or is not readable"); + } +} OPT_END(file); + +OPT_BEGIN(cache, ":") { + config->cache_file = resolve_path(optarg); +} OPT_END(cache); + +OPT_BEGIN(no_cache, "") { + config->cache_file = "<none>"; +} OPT_END(no_cache); + +OPT_BEGIN(output, "o:") { + if (std::string(optarg) != "-") { + std::string path = resolve_path(optarg); + report->output_file = path; + } +} OPT_END(output); + +OPT_BEGIN(account, "a:") { + config->account = optarg; +} OPT_END(account); + +OPT_BEGIN(debug, ":") { + config->debug_mode = true; + ::setenv("DEBUG_CLASS", optarg, 1); +} OPT_END(debug); + +OPT_BEGIN(verbose, "") { + config->verbose_mode = true; +} OPT_END(verbose); + +OPT_BEGIN(trace, "") { + config->trace_mode = true; +} OPT_END(trace); + +////////////////////////////////////////////////////////////////////// +// +// 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); + if (interval.begin) + std::strftime(buf, 127, formats[0], std::localtime(&interval.begin)); + else + throw new error(std::string("Could not determine beginning of period '") + + optarg + "'"); + + if (! report->predicate.empty()) + report->predicate += "&"; + report->predicate += "d>=["; + report->predicate += buf; + report->predicate += "]"; +} OPT_END(begin); + +OPT_BEGIN(end, "e:") { + char buf[128]; + interval_t interval(optarg); + if (interval.end) + std::strftime(buf, 127, formats[0], std::localtime(&interval.end)); + else + throw new error(std::string("Could not determine end of period '") + + optarg + "'"); + + if (! report->predicate.empty()) + report->predicate += "&"; + report->predicate += "d<["; + report->predicate += buf; + report->predicate += "]"; + + terminus = interval.end; +} OPT_END(end); + +OPT_BEGIN(current, "c") { + if (! report->predicate.empty()) + report->predicate += "&"; + report->predicate += "d<=m"; +} OPT_END(current); + +OPT_BEGIN(cleared, "C") { + if (! report->predicate.empty()) + report->predicate += "&"; + report->predicate += "X"; +} OPT_END(cleared); + +OPT_BEGIN(uncleared, "U") { + if (! report->predicate.empty()) + report->predicate += "&"; + report->predicate += "!X"; +} OPT_END(uncleared); + +OPT_BEGIN(real, "R") { + if (! report->predicate.empty()) + report->predicate += "&"; + report->predicate += "R"; +} OPT_END(real); + +OPT_BEGIN(actual, "L") { + if (! report->predicate.empty()) + report->predicate += "&"; + report->predicate += "L"; +} OPT_END(actual); + +OPT_BEGIN(lots, "") { + report->keep_price = + report->keep_date = + report->keep_tag = true; +} OPT_END(lots); + +OPT_BEGIN(lot_prices, "") { + report->keep_price = true; +} OPT_END(lots_prices); + +OPT_BEGIN(lot_dates, "") { + report->keep_date = true; +} OPT_END(lots_dates); + +OPT_BEGIN(lot_tags, "") { + report->keep_tag = true; +} OPT_END(lots_tags); + +////////////////////////////////////////////////////////////////////// +// +// Output customization + +OPT_BEGIN(format, "F:") { + report->format_string = optarg; +} OPT_END(format); + +OPT_BEGIN(date_format, "y:") { + report->date_format = optarg; +} OPT_END(date_format); + +OPT_BEGIN(input_date_format, ":") { + std::strcpy(input_format, optarg); + formats[0] = input_format; +} OPT_END(input_date_format); + +OPT_BEGIN(balance_format, ":") { + config->balance_format = optarg; +} OPT_END(balance_format); + +OPT_BEGIN(register_format, ":") { + config->register_format = optarg; +} OPT_END(register_format); + +OPT_BEGIN(wide_register_format, ":") { + config->wide_register_format = optarg; +} OPT_END(wide_register_format); + +OPT_BEGIN(csv_register_format, ":") { + config->csv_register_format = optarg; +} OPT_END(csv_register_format); + +OPT_BEGIN(plot_amount_format, ":") { + config->plot_amount_format = optarg; +} OPT_END(plot_amount_format); + +OPT_BEGIN(plot_total_format, ":") { + config->plot_total_format = optarg; +} OPT_END(plot_total_format); + +OPT_BEGIN(print_format, ":") { + config->print_format = optarg; +} OPT_END(print_format); + +OPT_BEGIN(write_hdr_format, ":") { + config->write_hdr_format = optarg; +} OPT_END(write_hdr_format); + +OPT_BEGIN(write_xact_format, ":") { + config->write_xact_format = optarg; +} OPT_END(write_xact_format); + +OPT_BEGIN(equity_format, ":") { + config->equity_format = optarg; +} OPT_END(equity_format); + +OPT_BEGIN(prices_format, ":") { + config->prices_format = optarg; +} OPT_END(prices_format); + +OPT_BEGIN(wide, "w") { + config->register_format = config->wide_register_format; +} OPT_END(wide); + +OPT_BEGIN(head, ":") { + report->head_entries = std::atoi(optarg); +} OPT_END(head); + +OPT_BEGIN(tail, ":") { + report->tail_entries = std::atoi(optarg); +} OPT_END(tail); + +OPT_BEGIN(pager, ":") { + config->pager = optarg; +} OPT_END(pager); + +OPT_BEGIN(empty, "E") { + report->show_empty = true; +} OPT_END(empty); + +OPT_BEGIN(collapse, "n") { + report->show_collapsed = true; +} OPT_END(collapse); + +OPT_BEGIN(subtotal, "s") { + report->show_subtotal = true; +} OPT_END(subtotal); + +OPT_BEGIN(totals, "") { + report->show_totals = true; +} OPT_END(totals); + +OPT_BEGIN(sort, "S:") { + report->sort_string = optarg; +} OPT_END(sort); + +OPT_BEGIN(sort_entries, "") { + report->sort_string = optarg; + report->entry_sort = true; +} OPT_END(sort_entries); + +OPT_BEGIN(sort_all, "") { + report->sort_string = optarg; + report->entry_sort = false; + report->sort_all = true; +} OPT_END(sort_all); + +OPT_BEGIN(period_sort, ":") { + report->sort_string = optarg; + report->entry_sort = true; +} OPT_END(period_sort); + +OPT_BEGIN(related, "r") { + report->show_related = true; +} OPT_END(related); + +OPT_BEGIN(descend, "") { + std::string arg(optarg); + std::string::size_type beg = 0; + report->descend_expr = ""; + for (std::string::size_type pos = arg.find(';'); + pos != std::string::npos; + beg = pos + 1, pos = arg.find(';', beg)) + report->descend_expr += (std::string("t=={") + + std::string(arg, beg, pos) + "};"); + report->descend_expr += (std::string("t=={") + + std::string(arg, beg) + "}"); +} OPT_END(descend); + +OPT_BEGIN(descend_if, "") { + report->descend_expr = optarg; +} OPT_END(descend_if); + +OPT_BEGIN(period, "p:") { + if (report->report_period.empty()) { + report->report_period = optarg; + } else { + report->report_period += " "; + report->report_period += optarg; + } + + // If the period gives a beginning and/or ending date, make sure to + // modify the calculation predicate (via the --begin and --end + // options) to take this into account. + + char buf[128]; + interval_t interval(report->report_period); + + if (interval.begin) { + std::strftime(buf, 127, formats[0], std::localtime(&interval.begin)); + + if (! report->predicate.empty()) + report->predicate += "&"; + report->predicate += "d>=["; + report->predicate += buf; + report->predicate += "]"; + } + + if (interval.end) { + std::strftime(buf, 127, formats[0], std::localtime(&interval.end)); + + if (! report->predicate.empty()) + report->predicate += "&"; + report->predicate += "d<["; + report->predicate += buf; + report->predicate += "]"; + + terminus = interval.end; + } +} OPT_END(period); + +OPT_BEGIN(daily, "") { + if (report->report_period.empty()) + report->report_period = "daily"; + else + report->report_period = std::string("daily ") + report->report_period; +} OPT_END(daily); + +OPT_BEGIN(weekly, "W") { + if (report->report_period.empty()) + report->report_period = "weekly"; + else + report->report_period = std::string("weekly ") + report->report_period; +} OPT_END(weekly); + +OPT_BEGIN(monthly, "M") { + if (report->report_period.empty()) + report->report_period = "monthly"; + else + report->report_period = std::string("monthly ") + report->report_period; +} OPT_END(monthly); + +OPT_BEGIN(yearly, "Y") { + if (report->report_period.empty()) + report->report_period = "yearly"; + else + report->report_period = std::string("yearly ") + report->report_period; +} OPT_END(yearly); + +OPT_BEGIN(dow, "") { + report->days_of_the_week = true; +} OPT_END(dow); + +OPT_BEGIN(by_payee, "P") { + report->by_payee = true; +} OPT_END(by_payee); + +OPT_BEGIN(comm_as_payee, "x") { + report->comm_as_payee = true; +} OPT_END(comm_as_payee); + +OPT_BEGIN(code_as_payee, "") { + report->code_as_payee = true; +} OPT_END(code_as_payee); + +OPT_BEGIN(budget, "") { + report->budget_flags = BUDGET_BUDGETED; +} OPT_END(budget); + +OPT_BEGIN(add_budget, "") { + report->budget_flags = BUDGET_BUDGETED | BUDGET_UNBUDGETED; +} OPT_END(add_budget); + +OPT_BEGIN(unbudgeted, "") { + report->budget_flags = BUDGET_UNBUDGETED; +} OPT_END(unbudgeted); + +OPT_BEGIN(forecast, ":") { + report->forecast_limit = optarg; +} OPT_END(forecast); + +OPT_BEGIN(reconcile, ":") { + report->reconcile_balance = optarg; +} OPT_END(reconcile); + +OPT_BEGIN(reconcile_date, ":") { + report->reconcile_date = optarg; +} OPT_END(reconcile_date); + +OPT_BEGIN(limit, "l:") { + if (! report->predicate.empty()) + report->predicate += "&"; + report->predicate += "("; + report->predicate += optarg; + report->predicate += ")"; +} OPT_END(limit); + +OPT_BEGIN(only, ":") { + if (! report->secondary_predicate.empty()) + report->secondary_predicate += "&"; + report->secondary_predicate += "("; + report->secondary_predicate += optarg; + report->secondary_predicate += ")"; +} OPT_END(only); + +OPT_BEGIN(display, "d:") { + if (! report->display_predicate.empty()) + report->display_predicate += "&"; + report->display_predicate += "("; + report->display_predicate += optarg; + report->display_predicate += ")"; +} OPT_END(display); + +OPT_BEGIN(amount, "t:") { + ledger::amount_expr = optarg; +} OPT_END(amount); + +OPT_BEGIN(total, "T:") { + ledger::total_expr = optarg; +} OPT_END(total); + +OPT_BEGIN(amount_data, "j") { + report->format_string = config->plot_amount_format; +} OPT_END(amount_data); + +OPT_BEGIN(total_data, "J") { + report->format_string = config->plot_total_format; +} OPT_END(total_data); + +OPT_BEGIN(ansi, "") { + format_t::ansi_codes = true; + format_t::ansi_invert = false; +} OPT_END(ansi); + +OPT_BEGIN(ansi_invert, "") { + format_t::ansi_codes = + format_t::ansi_invert = true; +} OPT_END(ansi); + +////////////////////////////////////////////////////////////////////// +// +// Commodity reporting + +OPT_BEGIN(base, ":") { + amount_t::keep_base = true; +} OPT_END(base); + +OPT_BEGIN(price_db, ":") { + config->price_db = optarg; +} OPT_END(price_db); + +OPT_BEGIN(price_exp, "Z:") { + report->pricing_leeway = std::atol(optarg) * 60; +} OPT_END(price_exp); + +OPT_BEGIN(download, "Q") { + config->download_quotes = true; +} OPT_END(download); + +OPT_BEGIN(quantity, "O") { + ledger::amount_expr = "@a"; + ledger::total_expr = "@O"; +} OPT_END(quantity); + +OPT_BEGIN(basis, "B") { + ledger::amount_expr = "@b"; + ledger::total_expr = "@B"; +} OPT_END(basis); + +OPT_BEGIN(price, "I") { + ledger::amount_expr = "@i"; + ledger::total_expr = "@I"; +} OPT_END(price); + +OPT_BEGIN(market, "V") { + report->show_revalued = true; + + ledger::amount_expr = "@v"; + ledger::total_expr = "@V"; +} OPT_END(market); + +namespace { + void parse_price_setting(const char * optarg) + { + char * equals = std::strchr(optarg, '='); + if (! equals) + return; + + while (std::isspace(*optarg)) + optarg++; + while (equals > optarg && std::isspace(*(equals - 1))) + equals--; + + std::string symbol(optarg, 0, equals - optarg); + amount_t price(equals + 1); + + if (commodity_t * commodity = commodity_t::find_or_create(symbol)) { + commodity->add_price(now, price); + commodity->history()->bogus_time = now; + } + } +} + +OPT_BEGIN(set_price, ":") { + std::string arg(optarg); + std::string::size_type beg = 0; + for (std::string::size_type pos = arg.find(';'); + pos != std::string::npos; + beg = pos + 1, pos = arg.find(';', beg)) + parse_price_setting(std::string(arg, beg, pos).c_str()); + parse_price_setting(std::string(arg, beg).c_str()); +} OPT_END(set_price); + +OPT_BEGIN(performance, "g") { + ledger::amount_expr = "@P(@a,@m)-@b"; + ledger::total_expr = "@P(@O,@m)-@B"; +} OPT_END(performance); + +OPT_BEGIN(gain, "G") { + report->show_revalued = + report->show_revalued_only = true; + + ledger::amount_expr = "@a"; + ledger::total_expr = "@G"; +} OPT_END(gain); + +static std::string expand_value_expr(const std::string& tmpl, + const std::string& expr) +{ + std::string xp = tmpl; + for (std::string::size_type i = xp.find('#'); + i != std::string::npos; + i = xp.find('#')) + xp = (std::string(xp, 0, i) + "(" + expr + ")" + + std::string(xp, i + 1)); + return xp; +} + +OPT_BEGIN(average, "A") { + ledger::total_expr = expand_value_expr("@A(#)", ledger::total_expr.expr); +} OPT_END(average); + +OPT_BEGIN(deviation, "D") { + ledger::total_expr = expand_value_expr("@t-@A(#)", ledger::total_expr.expr); +} OPT_END(deviation); + +OPT_BEGIN(percentage, "%") { + ledger::total_expr = expand_value_expr("^#&{100.0%}*(#/^#)", + ledger::total_expr.expr); +} OPT_END(percentage); + +////////////////////////////////////////////////////////////////////// + +option_t config_options[CONFIG_OPTIONS_SIZE] = { + { "account", 'a', true, opt_account, false }, + { "actual", 'L', false, opt_actual, false }, + { "add-budget", '\0', false, opt_add_budget, false }, + { "amount", 't', true, opt_amount, false }, + { "amount-data", 'j', false, opt_amount_data, false }, + { "ansi", '\0', false, opt_ansi, false }, + { "ansi-invert", '\0', false, opt_ansi_invert, false }, + { "average", 'A', false, opt_average, false }, + { "balance-format", '\0', true, opt_balance_format, false }, + { "base", '\0', false, opt_base, false }, + { "basis", 'B', false, opt_basis, false }, + { "begin", 'b', true, opt_begin, false }, + { "budget", '\0', false, opt_budget, false }, + { "by-payee", 'P', false, opt_by_payee, false }, + { "cache", '\0', true, opt_cache, false }, + { "cleared", 'C', false, opt_cleared, false }, + { "code-as-payee", '\0', false, opt_code_as_payee, false }, + { "collapse", 'n', false, opt_collapse, false }, + { "comm-as-payee", 'x', false, opt_comm_as_payee, false }, + { "csv-register-format", '\0', true, opt_csv_register_format, false }, + { "current", 'c', false, opt_current, false }, + { "daily", '\0', false, opt_daily, false }, + { "date-format", 'y', true, opt_date_format, false }, + { "debug", '\0', true, opt_debug, false }, + { "descend", '\0', true, opt_descend, false }, + { "descend-if", '\0', true, opt_descend_if, false }, + { "deviation", 'D', false, opt_deviation, false }, + { "display", 'd', true, opt_display, false }, + { "dow", '\0', false, opt_dow, false }, + { "download", 'Q', false, opt_download, false }, + { "effective", '\0', false, opt_effective, false }, + { "empty", 'E', false, opt_empty, false }, + { "end", 'e', true, opt_end, false }, + { "equity-format", '\0', true, opt_equity_format, false }, + { "file", 'f', true, opt_file, false }, + { "forecast", '\0', true, opt_forecast, false }, + { "format", 'F', true, opt_format, false }, + { "full-help", 'H', false, opt_full_help, false }, + { "gain", 'G', false, opt_gain, false }, + { "head", '\0', true, opt_head, false }, + { "help", 'h', false, opt_help, false }, + { "help-calc", '\0', false, opt_help_calc, false }, + { "help-comm", '\0', false, opt_help_comm, false }, + { "help-disp", '\0', false, opt_help_disp, false }, + { "init-file", 'i', true, opt_init_file, false }, + { "input-date-format", '\0', true, opt_input_date_format, false }, + { "limit", 'l', true, opt_limit, false }, + { "lot-dates", '\0', false, opt_lot_dates, false }, + { "lot-prices", '\0', false, opt_lot_prices, false }, + { "lot-tags", '\0', false, opt_lot_tags, false }, + { "lots", '\0', false, opt_lots, false }, + { "market", 'V', false, opt_market, false }, + { "monthly", 'M', false, opt_monthly, false }, + { "no-cache", '\0', false, opt_no_cache, false }, + { "only", '\0', true, opt_only, false }, + { "output", 'o', true, opt_output, false }, + { "pager", '\0', true, opt_pager, false }, + { "percentage", '%', false, opt_percentage, false }, + { "performance", 'g', false, opt_performance, false }, + { "period", 'p', true, opt_period, false }, + { "period-sort", '\0', true, opt_period_sort, false }, + { "plot-amount-format", '\0', true, opt_plot_amount_format, false }, + { "plot-total-format", '\0', true, opt_plot_total_format, false }, + { "price", 'I', false, opt_price, false }, + { "price-db", '\0', true, opt_price_db, false }, + { "price-exp", 'Z', true, opt_price_exp, false }, + { "prices-format", '\0', true, opt_prices_format, false }, + { "print-format", '\0', true, opt_print_format, false }, + { "quantity", 'O', false, opt_quantity, false }, + { "real", 'R', false, opt_real, false }, + { "reconcile", '\0', true, opt_reconcile, false }, + { "reconcile-date", '\0', true, opt_reconcile_date, false }, + { "register-format", '\0', true, opt_register_format, false }, + { "related", 'r', false, opt_related, false }, + { "set-price", '\0', true, opt_set_price, false }, + { "sort", 'S', true, opt_sort, false }, + { "sort-all", '\0', true, opt_sort_all, false }, + { "sort-entries", '\0', true, opt_sort_entries, false }, + { "subtotal", 's', false, opt_subtotal, false }, + { "tail", '\0', true, opt_tail, false }, + { "total", 'T', true, opt_total, false }, + { "total-data", 'J', false, opt_total_data, false }, + { "totals", '\0', false, opt_totals, false }, + { "trace", '\0', false, opt_trace, false }, + { "unbudgeted", '\0', false, opt_unbudgeted, false }, + { "uncleared", 'U', false, opt_uncleared, false }, + { "verbose", '\0', false, opt_verbose, false }, + { "version", 'v', false, opt_version, false }, + { "weekly", 'W', false, opt_weekly, false }, + { "wide", 'w', false, opt_wide, false }, + { "wide-register-format", '\0', true, opt_wide_register_format, false }, + { "write-hdr-format", '\0', true, opt_write_hdr_format, false }, + { "write-xact-format", '\0', true, opt_write_xact_format, false }, + { "yearly", 'Y', false, opt_yearly, false }, +}; + +} // namespace ledger @@ -30,4 +30,24 @@ void process_arguments(option_t * options, int argc, char ** argv, void process_environment(option_t * options, char ** envp, const std::string& tag); +namespace ledger { + +class config_t; +class report_t; + +extern config_t * config; +extern report_t * report; + +#define CONFIG_OPTIONS_SIZE 94 +extern option_t config_options[CONFIG_OPTIONS_SIZE]; + +void option_help(std::ostream& out); + +#define OPT_BEGIN(tag, chars) \ + void opt_ ## tag(const char * optarg) + +#define OPT_END(tag) + +} // namespace ledger + #endif // _OPTION_H @@ -168,6 +168,7 @@ unsigned int parse_ledger_data(config_t& config, config.use_cache = false; journal->sources.push_back("<stdin>"); #if 0 + // jww (2006-03-23): Why doesn't XML work on stdin? if (xml_parser && std::cin.peek() == '<') entry_count += xml_parser->parse(std::cin, config, journal, acct); @@ -64,10 +64,11 @@ inline char * next_element(char * buf, bool variable = false) return NULL; } -value_expr parse_amount_expr(std::istream& in, amount_t& amount, - transaction_t * xact) +static value_expr parse_amount_expr(std::istream& in, amount_t& amount, + transaction_t * xact, + unsigned short flags = 0) { - value_expr expr(parse_value_expr(in, NULL, PARSE_VALEXPR_RELAXED | + value_expr expr(parse_value_expr(in, NULL, flags | PARSE_VALEXPR_RELAXED | PARSE_VALEXPR_PARTIAL)->acquire()); DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << @@ -213,8 +214,10 @@ transaction_t * parse_transaction(char * line, account_t * account, try { unsigned long beg = (long)in.tellg(); - if (parse_amount_expr(in, *xact->cost, xact.get())) - throw new parse_error("A transaction's cost must evalute to a constant value"); + if (parse_amount_expr(in, *xact->cost, xact.get(), + PARSE_VALEXPR_NO_MIGRATE)) + throw new parse_error + ("A transaction's cost must evalute to a constant value"); unsigned long end = (long)in.tellg(); @@ -700,7 +703,7 @@ unsigned int textual_parser_t::parse(std::istream& in, if (p) *p++ = '\0'; } - config.process_option(line + 2, p); + process_option(config_options, line + 2, p); break; } @@ -772,7 +772,8 @@ value_expr_t * parse_value_term(std::istream& in, scope_t * scope, // the current maximum precision displayed. try { pos = (long)in.tellg(); - temp.parse(in); + temp.parse(in, flags & PARSE_VALEXPR_NO_MIGRATE ? + AMOUNT_PARSE_NO_MIGRATE : 0); } catch (amount_error * err) { // If the amount had no commodity, it must be an unambiguous @@ -276,9 +276,10 @@ bool compute_amount(value_expr_t * expr, amount_t& amt, const transaction_t * xact, value_expr_t * context = NULL); -#define PARSE_VALEXPR_NORMAL 0x00 -#define PARSE_VALEXPR_PARTIAL 0x01 -#define PARSE_VALEXPR_RELAXED 0x02 +#define PARSE_VALEXPR_NORMAL 0x00 +#define PARSE_VALEXPR_PARTIAL 0x01 +#define PARSE_VALEXPR_RELAXED 0x02 +#define PARSE_VALEXPR_NO_MIGRATE 0x04 value_expr_t * parse_value_expr(std::istream& in, scope_t * scope = NULL, @@ -1129,6 +1129,25 @@ value_t value_t::value(const std::time_t moment) const } } +void value_t::reduce() +{ + switch (type) { + case BOOLEAN: + case DATETIME: + case INTEGER: + break; + case AMOUNT: + ((amount_t *) data)->reduce(); + break; + case BALANCE: + ((balance_t *) data)->reduce(); + break; + case BALANCE_PAIR: + ((balance_pair_t *) data)->reduce(); + break; + } +} + void value_t::round() { switch (type) { @@ -316,6 +316,14 @@ class value_t value_t& add(const amount_t& amount, const amount_t * cost = NULL); value_t value(const std::time_t moment) const; + void reduce(); + + value_t reduced() const { + value_t temp(*this); + temp.reduce(); + return temp; + } + void round(); value_t unround() const; }; @@ -17,12 +17,14 @@ bool compare_items<transaction_t>::operator()(const transaction_t * left, transaction_xdata_t& lxdata(transaction_xdata(*left)); if (! (lxdata.dflags & TRANSACTION_SORT_CALC)) { guarded_compute(sort_order, lxdata.sort_value, details_t(*left)); + lxdata.sort_value.reduce(); lxdata.dflags |= TRANSACTION_SORT_CALC; } transaction_xdata_t& rxdata(transaction_xdata(*right)); if (! (rxdata.dflags & TRANSACTION_SORT_CALC)) { guarded_compute(sort_order, rxdata.sort_value, details_t(*right)); + rxdata.sort_value.reduce(); rxdata.dflags |= TRANSACTION_SORT_CALC; } |