diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/account.cc | 15 | ||||
-rw-r--r-- | src/amount.cc | 77 | ||||
-rw-r--r-- | src/amount.h | 4 | ||||
-rw-r--r-- | src/balance.h | 24 | ||||
-rw-r--r-- | src/chain.cc | 129 | ||||
-rw-r--r-- | src/chain.h | 30 | ||||
-rw-r--r-- | src/commodity.cc | 12 | ||||
-rw-r--r-- | src/commodity.h | 26 | ||||
-rw-r--r-- | src/convert.cc | 4 | ||||
-rw-r--r-- | src/draft.cc | 6 | ||||
-rw-r--r-- | src/emacs.cc | 19 | ||||
-rw-r--r-- | src/filters.cc | 272 | ||||
-rw-r--r-- | src/filters.h | 224 | ||||
-rw-r--r-- | src/item.cc | 9 | ||||
-rw-r--r-- | src/option.h | 35 | ||||
-rw-r--r-- | src/output.cc | 157 | ||||
-rw-r--r-- | src/output.h | 130 | ||||
-rw-r--r-- | src/parser.cc | 9 | ||||
-rw-r--r-- | src/parser.h | 5 | ||||
-rw-r--r-- | src/post.cc | 94 | ||||
-rw-r--r-- | src/post.h | 3 | ||||
-rw-r--r-- | src/print.cc | 100 | ||||
-rw-r--r-- | src/print.h | 10 | ||||
-rw-r--r-- | src/py_account.cc | 6 | ||||
-rw-r--r-- | src/py_commodity.cc | 39 | ||||
-rw-r--r-- | src/py_journal.cc | 19 | ||||
-rw-r--r-- | src/py_xact.cc | 4 | ||||
-rw-r--r-- | src/query.cc | 53 | ||||
-rw-r--r-- | src/query.h | 27 | ||||
-rw-r--r-- | src/report.cc | 201 | ||||
-rw-r--r-- | src/report.h | 53 | ||||
-rw-r--r-- | src/scope.h | 24 | ||||
-rw-r--r-- | src/session.cc | 4 | ||||
-rw-r--r-- | src/session.h | 6 | ||||
-rw-r--r-- | src/temps.cc | 51 | ||||
-rw-r--r-- | src/temps.h | 6 | ||||
-rw-r--r-- | src/textual.cc | 42 | ||||
-rw-r--r-- | src/times.cc | 59 | ||||
-rw-r--r-- | src/token.cc | 47 | ||||
-rw-r--r-- | src/token.h | 7 | ||||
-rw-r--r-- | src/value.cc | 54 | ||||
-rw-r--r-- | src/xact.cc | 97 | ||||
-rw-r--r-- | src/xml.h | 8 |
43 files changed, 1652 insertions, 549 deletions
diff --git a/src/account.cc b/src/account.cc index e02d21d7..8d4341e7 100644 --- a/src/account.cc +++ b/src/account.cc @@ -42,9 +42,12 @@ account_t::~account_t() { TRACE_DTOR(account_t); - foreach (accounts_map::value_type& pair, accounts) - if (! pair.second->has_flags(ACCOUNT_TEMP)) + foreach (accounts_map::value_type& pair, accounts) { + if (! pair.second->has_flags(ACCOUNT_TEMP) || + has_flags(ACCOUNT_TEMP)) { checked_delete(pair.second); + } + } } account_t * account_t::find_account(const string& name, @@ -79,6 +82,14 @@ account_t * account_t::find_account(const string& name, return NULL; account = new account_t(this, first); + + // An account created within a temporary or generated account is itself + // temporary or generated, so that the whole tree has the same status. + if (has_flags(ACCOUNT_TEMP)) + account->add_flags(ACCOUNT_TEMP); + if (has_flags(ACCOUNT_GENERATED)) + account->add_flags(ACCOUNT_GENERATED); + std::pair<accounts_map::iterator, bool> result = accounts.insert(accounts_map::value_type(first, account)); assert(result.second); diff --git a/src/amount.cc b/src/amount.cc index 3a64577f..13f30755 100644 --- a/src/amount.cc +++ b/src/amount.cc @@ -165,8 +165,8 @@ namespace { for (const char * p = buf; *p; p++) { if (*p == '.') { - if (commodity_t::european_by_default || - (comm && comm->has_flags(COMMODITY_STYLE_EUROPEAN))) + if (commodity_t::decimal_comma_by_default || + (comm && comm->has_flags(COMMODITY_STYLE_DECIMAL_COMMA))) out << ','; else out << *p; @@ -179,8 +179,8 @@ namespace { out << *p; if (integer_digits > 3 && --integer_digits % 3 == 0) { - if (commodity_t::european_by_default || - (comm && comm->has_flags(COMMODITY_STYLE_EUROPEAN))) + if (commodity_t::decimal_comma_by_default || + (comm && comm->has_flags(COMMODITY_STYLE_DECIMAL_COMMA))) out << '.'; else out << ','; @@ -594,6 +594,44 @@ void amount_t::in_place_round() set_keep_precision(false); } +void amount_t::in_place_truncate() +{ +#if 1 + if (! quantity) + throw_(amount_error, _("Cannot truncate an uninitialized amount")); + + _dup(); + + DEBUG("amount.truncate", + "Truncating " << *this << " to precision " << display_precision()); + + std::ostringstream out; + stream_out_mpq(out, MP(quantity), display_precision()); + + scoped_array<char> buf(new char [out.str().length() + 1]); + std::strcpy(buf.get(), out.str().c_str()); + + char * q = buf.get(); + for (char * p = q; *p != '\0'; p++, q++) { + if (*p == '.') p++; + if (p != q) *q = *p; + } + *q = '\0'; + + mpq_set_str(MP(quantity), buf.get(), 10); + + mpz_ui_pow_ui(temp, 10, display_precision()); + mpq_set_z(tempq, temp); + mpq_div(MP(quantity), MP(quantity), tempq); + + DEBUG("amount.truncate", "Truncated = " << *this); +#else + // This naive implementation is straightforward, but extremely inefficient + // as it requires parsing the commodity too, which might be fully annotated. + *this = amount_t(to_string()); +#endif +} + void amount_t::in_place_floor() { if (! quantity) @@ -993,8 +1031,9 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags) bool no_more_commas = false; bool no_more_periods = false; - bool european_style = (commodity_t::european_by_default || - commodity().has_flags(COMMODITY_STYLE_EUROPEAN)); + bool decimal_comma_style + = (commodity_t::decimal_comma_by_default || + commodity().has_flags(COMMODITY_STYLE_DECIMAL_COMMA)); new_quantity->prec = 0; @@ -1005,16 +1044,16 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags) if (no_more_periods) throw_(amount_error, _("Too many periods in amount")); - if (european_style) { + if (decimal_comma_style) { if (decimal_offset % 3 != 0) - throw_(amount_error, _("Incorrect use of european-style period")); + throw_(amount_error, _("Incorrect use of thousand-mark period")); comm_flags |= COMMODITY_STYLE_THOUSANDS; no_more_commas = true; } else { if (last_comma != string::npos) { - european_style = true; + decimal_comma_style = true; if (decimal_offset % 3 != 0) - throw_(amount_error, _("Incorrect use of european-style period")); + throw_(amount_error, _("Incorrect use of thousand-mark period")); } else { no_more_periods = true; new_quantity->prec = decimal_offset; @@ -1029,9 +1068,9 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags) if (no_more_commas) throw_(amount_error, _("Too many commas in amount")); - if (european_style) { + if (decimal_comma_style) { if (last_period != string::npos) { - throw_(amount_error, _("Incorrect use of european-style comma")); + throw_(amount_error, _("Incorrect use of decimal comma")); } else { no_more_commas = true; new_quantity->prec = decimal_offset; @@ -1041,12 +1080,12 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags) if (decimal_offset % 3 != 0) { if (last_comma != string::npos || last_period != string::npos) { - throw_(amount_error, _("Incorrect use of American-style comma")); + throw_(amount_error, _("Incorrect use of thousand-mark comma")); } else { - european_style = true; - no_more_commas = true; - new_quantity->prec = decimal_offset; - decimal_offset = 0; + decimal_comma_style = true; + no_more_commas = true; + new_quantity->prec = decimal_offset; + decimal_offset = 0; } } else { comm_flags |= COMMODITY_STYLE_THOUSANDS; @@ -1062,8 +1101,8 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags) } } - if (european_style) - comm_flags |= COMMODITY_STYLE_EUROPEAN; + if (decimal_comma_style) + comm_flags |= COMMODITY_STYLE_DECIMAL_COMMA; if (flags.has_flags(PARSE_NO_MIGRATE)) { // Can't call set_keep_precision here, because it assumes that `quantity' diff --git a/src/amount.h b/src/amount.h index 5c1bca46..ae0e5a69 100644 --- a/src/amount.h +++ b/src/amount.h @@ -346,9 +346,7 @@ public: temp.in_place_truncate(); return temp; } - void in_place_truncate() { - *this = amount_t(to_string()); - } + void in_place_truncate(); /** Yields an amount which has lost all of its extra precision, beyond what the display precision of the commodity would have printed. */ diff --git a/src/balance.h b/src/balance.h index f8455d49..5c00c55a 100644 --- a/src/balance.h +++ b/src/balance.h @@ -321,10 +321,8 @@ public: return temp; } void in_place_round() { - balance_t temp; - foreach (const amounts_map::value_type& pair, amounts) - temp += pair.second.rounded(); - *this = temp; + foreach (amounts_map::value_type& pair, amounts) + pair.second.in_place_round(); } balance_t truncated() const { @@ -333,10 +331,8 @@ public: return temp; } void in_place_truncate() { - balance_t temp; - foreach (const amounts_map::value_type& pair, amounts) - temp += pair.second.truncated(); - *this = temp; + foreach (amounts_map::value_type& pair, amounts) + pair.second.in_place_truncate(); } balance_t floored() const { @@ -345,10 +341,8 @@ public: return temp; } void in_place_floor() { - balance_t temp; - foreach (const amounts_map::value_type& pair, amounts) - temp += pair.second.floored(); - *this = temp; + foreach (amounts_map::value_type& pair, amounts) + pair.second.in_place_floor(); } balance_t unrounded() const { @@ -357,10 +351,8 @@ public: return temp; } void in_place_unround() { - balance_t temp; - foreach (const amounts_map::value_type& pair, amounts) - temp += pair.second.unrounded(); - *this = temp; + foreach (amounts_map::value_type& pair, amounts) + pair.second.in_place_unround(); } balance_t reduced() const { diff --git a/src/chain.cc b/src/chain.cc index 86f639ad..b8c2eb0a 100644 --- a/src/chain.cc +++ b/src/chain.cc @@ -39,6 +39,73 @@ namespace ledger { +post_handler_ptr chain_pre_post_handlers(report_t& report, + post_handler_ptr base_handler) +{ + post_handler_ptr handler(base_handler); + + // anonymize_posts removes all meaningful information from xact payee's and + // account names, for the sake of creating useful bug reports. + if (report.HANDLED(anon)) + handler.reset(new anonymize_posts(handler)); + + // This filter_posts will only pass through posts matching the `predicate'. + if (report.HANDLED(limit_)) { + DEBUG("report.predicate", + "Report predicate expression = " << report.HANDLER(limit_).str()); + handler.reset(new filter_posts + (handler, predicate_t(report.HANDLER(limit_).str(), + report.what_to_keep()), + report)); + } + + // budget_posts takes a set of posts from a data file and uses them to + // generate "budget posts" which balance against the reported posts. + // + // forecast_posts is a lot like budget_posts, except that it adds xacts + // only for the future, and does not balance them against anything but the + // future balance. + + if (report.budget_flags != BUDGET_NO_BUDGET) { + budget_posts * budget_handler = new budget_posts(handler, + report.budget_flags); + budget_handler->add_period_xacts(report.session.journal->period_xacts); + handler.reset(budget_handler); + + // Apply this before the budget handler, so that only matching posts are + // calculated toward the budget. The use of filter_posts above will + // further clean the results so that no automated posts that don't match + // the filter get reported. + if (report.HANDLED(limit_)) + handler.reset(new filter_posts + (handler, predicate_t(report.HANDLER(limit_).str(), + report.what_to_keep()), + report)); + } + else if (report.HANDLED(forecast_while_)) { + forecast_posts * forecast_handler + = new forecast_posts(handler, + predicate_t(report.HANDLER(forecast_while_).str(), + report.what_to_keep()), + report, + report.HANDLED(forecast_years_) ? + static_cast<std::size_t> + (report.HANDLER(forecast_years_).value.to_long()) : + 5UL); + forecast_handler->add_period_xacts(report.session.journal->period_xacts); + handler.reset(forecast_handler); + + // See above, under budget_posts. + if (report.HANDLED(limit_)) + handler.reset(new filter_posts + (handler, predicate_t(report.HANDLER(limit_).str(), + report.what_to_keep()), + report)); + } + + return handler; +} + post_handler_ptr chain_post_handlers(report_t& report, post_handler_ptr base_handler, bool for_accounts_report) @@ -86,7 +153,8 @@ post_handler_ptr chain_post_handlers(report_t& report, report.HANDLED(unrealized))) handler.reset(new changed_value_posts(handler, report, for_accounts_report, - report.HANDLED(unrealized))); + report.HANDLED(unrealized), + ! report.HANDLED(no_rounding))); // calc_posts computes the running total. When this appears will determine, // for example, whether filtered posts are included or excluded from the @@ -188,65 +256,6 @@ post_handler_ptr chain_post_handlers(report_t& report, if (report.HANDLED(related)) handler.reset(new related_posts(handler, report.HANDLED(related_all))); - // anonymize_posts removes all meaningful information from xact payee's and - // account names, for the sake of creating useful bug reports. - if (report.HANDLED(anon)) - handler.reset(new anonymize_posts(handler)); - - // This filter_posts will only pass through posts matching the `predicate'. - if (report.HANDLED(limit_)) { - DEBUG("report.predicate", - "Report predicate expression = " << report.HANDLER(limit_).str()); - handler.reset(new filter_posts - (handler, predicate_t(report.HANDLER(limit_).str(), - report.what_to_keep()), - report)); - } - - // budget_posts takes a set of posts from a data file and uses them to - // generate "budget posts" which balance against the reported posts. - // - // forecast_posts is a lot like budget_posts, except that it adds xacts - // only for the future, and does not balance them against anything but the - // future balance. - - if (report.budget_flags != BUDGET_NO_BUDGET) { - budget_posts * budget_handler = new budget_posts(handler, - report.budget_flags); - budget_handler->add_period_xacts(report.session.journal->period_xacts); - handler.reset(budget_handler); - - // Apply this before the budget handler, so that only matching posts are - // calculated toward the budget. The use of filter_posts above will - // further clean the results so that no automated posts that don't match - // the filter get reported. - if (report.HANDLED(limit_)) - handler.reset(new filter_posts - (handler, predicate_t(report.HANDLER(limit_).str(), - report.what_to_keep()), - report)); - } - else if (report.HANDLED(forecast_while_)) { - forecast_posts * forecast_handler - = new forecast_posts(handler, - predicate_t(report.HANDLER(forecast_while_).str(), - report.what_to_keep()), - report, - report.HANDLED(forecast_years_) ? - static_cast<std::size_t> - (report.HANDLER(forecast_years_).value.to_long()) : - 5UL); - forecast_handler->add_period_xacts(report.session.journal->period_xacts); - handler.reset(forecast_handler); - - // See above, under budget_posts. - if (report.HANDLED(limit_)) - handler.reset(new filter_posts - (handler, predicate_t(report.HANDLER(limit_).str(), - report.what_to_keep()), - report)); - } - return handler; } diff --git a/src/chain.h b/src/chain.h index 94d54317..59b04eb8 100644 --- a/src/chain.h +++ b/src/chain.h @@ -65,27 +65,51 @@ public: TRACE_DTOR(item_handler); } + virtual void title(const string& str) { + if (handler) + handler->title(str); + } + virtual void flush() { - if (handler.get()) + if (handler) handler->flush(); } virtual void operator()(T& item) { - if (handler.get()) { + if (handler) { check_for_signal(); - (*handler.get())(item); + (*handler)(item); } } + + virtual void clear() { + if (handler) + handler->clear(); + } }; typedef shared_ptr<item_handler<post_t> > post_handler_ptr; typedef shared_ptr<item_handler<account_t> > acct_handler_ptr; class report_t; + +post_handler_ptr +chain_pre_post_handlers(report_t& report, + post_handler_ptr base_handler); + post_handler_ptr chain_post_handlers(report_t& report, post_handler_ptr base_handler, bool for_accounts_report = false); +inline post_handler_ptr +chain_handlers(report_t& report, + post_handler_ptr handler, + bool for_accounts_report = false) { + handler = chain_post_handlers(report, handler, for_accounts_report); + handler = chain_pre_post_handlers(report, handler); + return handler; +} + } // namespace ledger #endif // _CHAIN_H diff --git a/src/commodity.cc b/src/commodity.cc index 836a4269..1b85910f 100644 --- a/src/commodity.cc +++ b/src/commodity.cc @@ -38,7 +38,7 @@ namespace ledger { -bool commodity_t::european_by_default = false; +bool commodity_t::decimal_comma_by_default = false; void commodity_t::history_t::add_price(commodity_t& source, const datetime_t& date, @@ -454,7 +454,7 @@ void commodity_t::parse_symbol(std::istream& in, string& symbol) { // Invalid commodity characters: // SPACE, TAB, NEWLINE, RETURN - // 0-9 . , ; - + * / ^ ? : & | ! = + // 0-9 . , ; : ? ! - + * / ^ & | = // < > { } [ ] ( ) @ static int invalid_chars[256] = { @@ -663,10 +663,10 @@ void to_xml(std::ostream& out, const commodity_t& comm, push_xml x(out, "commodity", true); out << " flags=\""; - if (! (comm.has_flags(COMMODITY_STYLE_SUFFIXED))) out << 'P'; - if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) out << 'S'; - if (comm.has_flags(COMMODITY_STYLE_THOUSANDS)) out << 'T'; - if (comm.has_flags(COMMODITY_STYLE_EUROPEAN)) out << 'E'; + if (! (comm.has_flags(COMMODITY_STYLE_SUFFIXED))) out << 'P'; + if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) out << 'S'; + if (comm.has_flags(COMMODITY_STYLE_THOUSANDS)) out << 'T'; + if (comm.has_flags(COMMODITY_STYLE_DECIMAL_COMMA)) out << 'D'; out << '"'; x.close_attrs(); diff --git a/src/commodity.h b/src/commodity.h index 10f209fa..53e3033f 100644 --- a/src/commodity.h +++ b/src/commodity.h @@ -155,16 +155,16 @@ protected: class base_t : public noncopyable, public supports_flags<uint_least16_t> { public: -#define COMMODITY_STYLE_DEFAULTS 0x000 -#define COMMODITY_STYLE_SUFFIXED 0x001 -#define COMMODITY_STYLE_SEPARATED 0x002 -#define COMMODITY_STYLE_EUROPEAN 0x004 -#define COMMODITY_STYLE_THOUSANDS 0x008 -#define COMMODITY_NOMARKET 0x010 -#define COMMODITY_BUILTIN 0x020 -#define COMMODITY_WALKED 0x040 -#define COMMODITY_KNOWN 0x080 -#define COMMODITY_PRIMARY 0x100 +#define COMMODITY_STYLE_DEFAULTS 0x000 +#define COMMODITY_STYLE_SUFFIXED 0x001 +#define COMMODITY_STYLE_SEPARATED 0x002 +#define COMMODITY_STYLE_DECIMAL_COMMA 0x004 +#define COMMODITY_STYLE_THOUSANDS 0x008 +#define COMMODITY_NOMARKET 0x010 +#define COMMODITY_BUILTIN 0x020 +#define COMMODITY_WALKED 0x040 +#define COMMODITY_KNOWN 0x080 +#define COMMODITY_PRIMARY 0x100 string symbol; amount_t::precision_t precision; @@ -179,8 +179,8 @@ protected: public: explicit base_t(const string& _symbol) : supports_flags<uint_least16_t> - (commodity_t::european_by_default ? - static_cast<uint_least16_t>(COMMODITY_STYLE_EUROPEAN) : + (commodity_t::decimal_comma_by_default ? + static_cast<uint_least16_t>(COMMODITY_STYLE_DECIMAL_COMMA) : static_cast<uint_least16_t>(COMMODITY_STYLE_DEFAULTS)), symbol(_symbol), precision(0), searched(false) { TRACE_CTOR(base_t, "const string&"); @@ -228,7 +228,7 @@ protected: } public: - static bool european_by_default; + static bool decimal_comma_by_default; virtual ~commodity_t() { TRACE_DTOR(commodity_t); diff --git a/src/convert.cc b/src/convert.cc index 2e6da2f6..aa9bbb6f 100644 --- a/src/convert.cc +++ b/src/convert.cc @@ -117,7 +117,7 @@ value_t convert_command(call_scope_t& scope) if (matched) { DEBUG("convert.csv", "Ignored xact with code: " << *xact->code); - delete xact; // ignore it + checked_delete(xact); // ignore it } else { if (xact->posts.front()->account == NULL) { @@ -135,7 +135,7 @@ value_t convert_command(call_scope_t& scope) } if (! journal.add_xact(xact)) { - delete xact; + checked_delete(xact); throw_(std::runtime_error, _("Failed to finalize derived transaction (check commodities)")); } diff --git a/src/draft.cc b/src/draft.cc index 18075731..69dc7025 100644 --- a/src/draft.cc +++ b/src/draft.cc @@ -240,6 +240,9 @@ void draft_t::parse_args(const value_t& args) xact_t * draft_t::insert(journal_t& journal) { + if (! tmpl) + return NULL; + if (tmpl->payee_mask.empty()) throw std::runtime_error(_("'xact' command requires at least a payee")); @@ -528,7 +531,8 @@ value_t xact_command(call_scope_t& args) // Only consider actual postings for the "xact" command report.HANDLER(limit_).on(string("#xact"), "actual"); - report.xact_report(post_handler_ptr(new print_xacts(report)), *new_xact); + if (new_xact) + report.xact_report(post_handler_ptr(new print_xacts(report)), *new_xact); return true; } diff --git a/src/emacs.cc b/src/emacs.cc index d47f04ad..3c8bb256 100644 --- a/src/emacs.cc +++ b/src/emacs.cc @@ -40,18 +40,21 @@ namespace ledger { void format_emacs_posts::write_xact(xact_t& xact) { - out << "\"" << xact.pos->pathname << "\" " - << xact.pos->beg_line << " "; + if (xact.pos) + out << "\"" << xact.pos->pathname << "\" " + << xact.pos->beg_line << " "; + else + out << "\"\" " << -1 << " "; tm when = gregorian::to_tm(xact.date()); std::time_t date = std::mktime(&when); out << "(" << (date / 65536) << " " << (date % 65536) << " 0) "; - if (! xact.code) - out << "nil "; - else + if (xact.code) out << "\"" << *xact.code << "\" "; + else + out << "nil "; if (xact.payee.empty()) out << "nil"; @@ -77,7 +80,11 @@ void format_emacs_posts::operator()(post_t& post) out << "\n"; } - out << " (" << post.pos->beg_line << " "; + if (post.pos) + out << " (" << post.pos->beg_line << " "; + else + out << " (" << -1 << " "; + out << "\"" << post.reported_account()->fullname() << "\" \"" << post.amount << "\""; diff --git a/src/filters.cc b/src/filters.cc index 0c45d356..ad4b88a0 100644 --- a/src/filters.cc +++ b/src/filters.cc @@ -39,8 +39,51 @@ namespace ledger { +void post_splitter::print_title(const value_t& val) +{ + if (! report.HANDLED(no_titles)) { + std::ostringstream buf; + val.print(buf); + post_chain->title(buf.str()); + } +} + +void post_splitter::flush() +{ + foreach (value_to_posts_map::value_type pair, posts_map) { + preflush_func(pair.first); + + foreach (post_t * post, pair.second) + (*post_chain)(*post); + + post_chain->flush(); + post_chain->clear(); + + if (postflush_func) + (*postflush_func)(pair.first); + } +} + +void post_splitter::operator()(post_t& post) +{ + bind_scope_t bound_scope(report, post); + value_t result(group_by_expr.calc(bound_scope)); + + if (! result.is_null()) { + value_to_posts_map::iterator i = posts_map.find(result); + if (i != posts_map.end()) { + (*i).second.push_back(&post); + } else { + std::pair<value_to_posts_map::iterator, bool> inserted + = posts_map.insert(value_to_posts_map::value_type(result, posts_list())); + assert(inserted.second); + (*inserted.first).second.push_back(&post); + } + } +} + pass_down_posts::pass_down_posts(post_handler_ptr handler, - posts_iterator& iter) + posts_iterator& iter) : item_handler<post_t>(handler) { TRACE_CTOR(pass_down_posts, "post_handler_ptr, posts_iterator"); @@ -312,7 +355,7 @@ namespace { if (functor) (*functor)(post); - DEBUG("filter.changed_value.rounding", "post.amount = " << post.amount); + DEBUG("filters.changed_value.rounding", "post.amount = " << post.amount); (*handler)(post); @@ -355,7 +398,7 @@ void collapse_posts::report_subtotal() xact.payee = last_xact->payee; xact._date = (is_valid(earliest_date) ? earliest_date : last_xact->_date); - DEBUG("filter.collapse", "Pseudo-xact date = " << *xact._date); + DEBUG("filters.collapse", "Pseudo-xact date = " << *xact._date); handle_value(subtotal, &totals_account, &xact, temps, handler); } @@ -420,10 +463,12 @@ void related_posts::flush() changed_value_posts::changed_value_posts(post_handler_ptr handler, report_t& _report, bool _for_accounts_report, - bool _show_unrealized) + bool _show_unrealized, + bool _show_rounding) : item_handler<post_t>(handler), report(_report), for_accounts_report(_for_accounts_report), - show_unrealized(_show_unrealized), last_post(NULL), + show_unrealized(_show_unrealized), + show_rounding(_show_rounding), last_post(NULL), revalued_account(temps.create_account(_("<Revalued>"))), rounding_account(temps.create_account(_("<Rounding>"))) { @@ -458,6 +503,8 @@ changed_value_posts::changed_value_posts(post_handler_ptr handler, void changed_value_posts::flush() { if (last_post && last_post->date() <= report.terminus.date()) { + if (! for_accounts_report) + output_intermediate_prices(*last_post, report.terminus.date()); output_revaluation(*last_post, report.terminus.date()); last_post = NULL; } @@ -469,7 +516,6 @@ void changed_value_posts::output_revaluation(post_t& post, const date_t& date) if (is_valid(date)) post.xdata().date = date; - value_t repriced_total; try { bind_scope_t bound_scope(report, post); repriced_total = total_expr.calc(bound_scope); @@ -480,14 +526,14 @@ void changed_value_posts::output_revaluation(post_t& post, const date_t& date) } post.xdata().date = date_t(); - DEBUG("filter.changed_value", - "output_revaluation(last_balance) = " << last_total); - DEBUG("filter.changed_value", + DEBUG("filters.changed_value", + "output_revaluation(last_total) = " << last_total); + DEBUG("filters.changed_value", "output_revaluation(repriced_total) = " << repriced_total); if (! last_total.is_null()) { if (value_t diff = repriced_total - last_total) { - DEBUG("filter.changed_value", "output_revaluation(strip(diff)) = " + DEBUG("filters.changed_value", "output_revaluation(strip(diff)) = " << diff.strip_annotations(report.what_to_keep())); xact_t& xact = temps.create_xact(); @@ -505,9 +551,10 @@ void changed_value_posts::output_revaluation(post_t& post, const date_t& date) /* total= */ repriced_total, /* direct_amount= */ false, /* mark_visited= */ false, - /* functor= */ (optional<post_functor_t> + /* functor= */ (show_rounding ? + optional<post_functor_t> (bind(&changed_value_posts::output_rounding, - this, _1)))); + this, _1)) : none)); } else if (show_unrealized) { handle_value @@ -527,29 +574,146 @@ void changed_value_posts::output_revaluation(post_t& post, const date_t& date) } } +void changed_value_posts::output_intermediate_prices(post_t& post, + const date_t& current) +{ + // To fix BZ#199, examine the balance of last_post and determine whether the + // price of that amount changed after its date and before the new post's + // date. If so, generate an output_revaluation for that price change. + // Mostly this is only going to occur if the user has a series of pricing + // entries, since a posting-based revaluation would be seen here as a post. + + value_t display_total(last_total); + + if (display_total.type() == value_t::SEQUENCE) { + xact_t& xact(temps.create_xact()); + + xact.payee = _("Commodities revalued"); + xact._date = is_valid(current) ? current : post.date(); + + post_t& temp(temps.copy_post(post, xact)); + temp.add_flags(ITEM_GENERATED); + + post_t::xdata_t& xdata(temp.xdata()); + if (is_valid(current)) + xdata.date = current; + + DEBUG("filters.revalued", "intermediate last_total = " << last_total); + + switch (last_total.type()) { + case value_t::BOOLEAN: + case value_t::INTEGER: + last_total.in_place_cast(value_t::AMOUNT); + // fall through... + + case value_t::AMOUNT: + temp.amount = last_total.as_amount(); + break; + + case value_t::BALANCE: + case value_t::SEQUENCE: + xdata.compound_value = last_total; + xdata.add_flags(POST_EXT_COMPOUND); + break; + + case value_t::DATETIME: + case value_t::DATE: + default: + assert(false); + break; + } + + bind_scope_t inner_scope(report, temp); + display_total = display_total_expr.calc(inner_scope); + + DEBUG("filters.revalued", "intermediate display_total = " << display_total); + } + + switch (display_total.type()) { + case value_t::VOID: + case value_t::INTEGER: + case value_t::SEQUENCE: + break; + + case value_t::AMOUNT: + display_total.in_place_cast(value_t::BALANCE); + // fall through... + + case value_t::BALANCE: { + commodity_t::history_map all_prices; + + foreach (const balance_t::amounts_map::value_type& amt_comm, + display_total.as_balance().amounts) { + if (optional<commodity_t::varied_history_t&> hist = + amt_comm.first->varied_history()) { + foreach + (const commodity_t::history_by_commodity_map::value_type& comm_hist, + hist->histories) { + foreach (const commodity_t::history_map::value_type& price, + comm_hist.second.prices) { + if (price.first.date() > post.date() && + price.first.date() < current) { + DEBUG("filters.revalued", post.date() << " < " + << price.first.date() << " < " << current); + DEBUG("filters.revalued", "inserting " + << price.second << " at " << price.first.date()); + all_prices.insert(price); + } + } + } + } + } + + // Choose the last price from each day as the price to use + typedef std::map<const date_t, bool> date_map; + date_map pricing_dates; + + BOOST_REVERSE_FOREACH + (const commodity_t::history_map::value_type& price, all_prices) { + // This insert will fail if a later price has already been inserted + // for that date. + DEBUG("filters.revalued", + "re-inserting " << price.second << " at " << price.first.date()); + pricing_dates.insert(date_map::value_type(price.first.date(), true)); + } + + // Go through the time-sorted prices list, outputting a revaluation for + // each price difference. + foreach (const date_map::value_type& price, pricing_dates) { + output_revaluation(post, price.first); + last_total = repriced_total; + } + break; + } + default: + assert(false); + break; + } +} + void changed_value_posts::output_rounding(post_t& post) { bind_scope_t bound_scope(report, post); value_t new_display_total(display_total_expr.calc(bound_scope)); - DEBUG("filter.changed_value.rounding", + DEBUG("filters.changed_value.rounding", "rounding.new_display_total = " << new_display_total); if (! last_display_total.is_null()) { if (value_t repriced_amount = display_amount_expr.calc(bound_scope)) { - DEBUG("filter.changed_value.rounding", + DEBUG("filters.changed_value.rounding", "rounding.repriced_amount = " << repriced_amount); value_t precise_display_total(new_display_total.truncated() - repriced_amount.truncated()); - DEBUG("filter.changed_value.rounding", + DEBUG("filters.changed_value.rounding", "rounding.precise_display_total = " << precise_display_total); - DEBUG("filter.changed_value.rounding", + DEBUG("filters.changed_value.rounding", "rounding.last_display_total = " << last_display_total); if (value_t diff = precise_display_total - last_display_total) { - DEBUG("filter.changed_value.rounding", + DEBUG("filters.changed_value.rounding", "rounding.diff = " << diff); xact_t& xact = temps.create_xact(); @@ -566,13 +730,16 @@ void changed_value_posts::output_rounding(post_t& post) void changed_value_posts::operator()(post_t& post) { - if (last_post) + if (last_post) { + if (! for_accounts_report) + output_intermediate_prices(*last_post, post.date()); output_revaluation(*last_post, post.date()); + } if (changed_values_only) post.xdata().add_flags(POST_EXT_DISPLAYED); - if (! for_accounts_report) + if (! for_accounts_report && show_rounding) output_rounding(post); item_handler<post_t>::operator()(post); @@ -793,41 +960,43 @@ void transfer_details::operator()(post_t& post) bind_scope_t bound_scope(scope, temp); value_t substitute(expr.calc(bound_scope)); - switch (which_element) { - case SET_DATE: - temp.xdata().date = substitute.to_date(); - break; + if (! substitute.is_null()) { + switch (which_element) { + case SET_DATE: + temp.xdata().date = substitute.to_date(); + break; - case SET_ACCOUNT: { - string account_name = substitute.to_string(); - if (! account_name.empty() && - account_name[account_name.length() - 1] != ':') { - account_t * prev_account = temp.account; - temp.account->remove_post(&temp); - - account_name += ':'; - account_name += prev_account->fullname(); - - std::list<string> account_names; - split_string(account_name, ':', account_names); - temp.account = create_temp_account_from_path(account_names, temps, - xact.journal->master); - temp.account->add_post(&temp); - - temp.account->add_flags(prev_account->flags()); - if (prev_account->has_xdata()) - temp.account->xdata().add_flags(prev_account->xdata().flags()); + case SET_ACCOUNT: { + string account_name = substitute.to_string(); + if (! account_name.empty() && + account_name[account_name.length() - 1] != ':') { + account_t * prev_account = temp.account; + temp.account->remove_post(&temp); + + account_name += ':'; + account_name += prev_account->fullname(); + + std::list<string> account_names; + split_string(account_name, ':', account_names); + temp.account = create_temp_account_from_path(account_names, temps, + xact.journal->master); + temp.account->add_post(&temp); + + temp.account->add_flags(prev_account->flags()); + if (prev_account->has_xdata()) + temp.account->xdata().add_flags(prev_account->xdata().flags()); + } + break; } - break; - } - case SET_PAYEE: - xact.payee = substitute.to_string(); - break; + case SET_PAYEE: + xact.payee = substitute.to_string(); + break; - default: - assert(false); - break; + default: + assert(false); + break; + } } item_handler<post_t>::operator()(temp); @@ -869,7 +1038,8 @@ void budget_posts::report_budget_items(const date_t& date) optional<date_t> begin = pair.first.start; if (! begin) { if (! pair.first.find_period(date)) - throw_(std::runtime_error, _("Something odd has happened")); + throw_(std::runtime_error, + _("Something odd has happened at date %1") << date); begin = pair.first.start; } assert(begin); diff --git a/src/filters.h b/src/filters.h index 82fbf687..a66d8c47 100644 --- a/src/filters.h +++ b/src/filters.h @@ -52,6 +52,56 @@ namespace ledger { ////////////////////////////////////////////////////////////////////// // +// Posting collector +// + +class post_splitter : public item_handler<post_t> +{ +public: + typedef std::map<value_t, posts_list> value_to_posts_map; + typedef function<void (const value_t&)> custom_flusher_t; + +protected: + value_to_posts_map posts_map; + report_t& report; + post_handler_ptr post_chain; + expr_t group_by_expr; + custom_flusher_t preflush_func; + optional<custom_flusher_t> postflush_func; + +public: + post_splitter(report_t& _report, + post_handler_ptr _post_chain, + expr_t _group_by_expr) + : report(_report), post_chain(_post_chain), + group_by_expr(_group_by_expr), + preflush_func(bind(&post_splitter::print_title, this, _1)) { + TRACE_CTOR(post_splitter, "scope_t&, post_handler_ptr, expr_t"); + } + virtual ~post_splitter() { + TRACE_DTOR(post_splitter); + } + + void set_preflush_func(custom_flusher_t functor) { + preflush_func = functor; + } + void set_postflush_func(custom_flusher_t functor) { + postflush_func = functor; + } + + virtual void print_title(const value_t& val); + + virtual void flush(); + virtual void operator()(post_t& post); + + virtual void clear() { + posts_map.clear(); + post_chain->clear(); + } +}; + +////////////////////////////////////////////////////////////////////// +// // Posting filters // @@ -88,6 +138,11 @@ public: virtual void operator()(post_t& post) { posts.push_back(&post); } + + virtual void clear() { + posts.clear(); + item_handler<post_t>::clear(); + } }; class posts_iterator; @@ -149,27 +204,34 @@ public: virtual void flush(); virtual void operator()(post_t& post); + + virtual void clear() { + completed = false; + posts.clear(); + xacts_seen = 0; + last_xact = NULL; + + item_handler<post_t>::clear(); + } }; class sort_posts : public item_handler<post_t> { typedef std::deque<post_t *> posts_deque; - posts_deque posts; - const expr_t sort_order; + posts_deque posts; + expr_t sort_order; sort_posts(); public: - sort_posts(post_handler_ptr handler, - const expr_t& _sort_order) + sort_posts(post_handler_ptr handler, const expr_t& _sort_order) : item_handler<post_t>(handler), sort_order(_sort_order) { TRACE_CTOR(sort_posts, "post_handler_ptr, const value_expr&"); } - sort_posts(post_handler_ptr handler, - const string& _sort_order) + sort_posts(post_handler_ptr handler, const string& _sort_order) : item_handler<post_t>(handler), sort_order(_sort_order) { TRACE_CTOR(sort_posts, @@ -189,6 +251,13 @@ public: virtual void operator()(post_t& post) { posts.push_back(&post); } + + virtual void clear() { + posts.clear(); + sort_order.mark_uncompiled(); + + item_handler<post_t>::clear(); + } }; class sort_xacts : public item_handler<post_t> @@ -199,14 +268,12 @@ class sort_xacts : public item_handler<post_t> sort_xacts(); public: - sort_xacts(post_handler_ptr handler, - const expr_t& _sort_order) + sort_xacts(post_handler_ptr handler, const expr_t& _sort_order) : sorter(handler, _sort_order) { TRACE_CTOR(sort_xacts, "post_handler_ptr, const value_expr&"); } - sort_xacts(post_handler_ptr handler, - const string& _sort_order) + sort_xacts(post_handler_ptr handler, const string& _sort_order) : sorter(handler, _sort_order) { TRACE_CTOR(sort_xacts, "post_handler_ptr, const string&"); @@ -228,6 +295,13 @@ public: last_xact = post.xact; } + + virtual void clear() { + sorter.clear(); + last_xact = NULL; + + item_handler<post_t>::clear(); + } }; class filter_posts : public item_handler<post_t> @@ -255,6 +329,11 @@ public: (*handler)(post); } } + + virtual void clear() { + pred.mark_uncompiled(); + item_handler<post_t>::clear(); + } }; class anonymize_posts : public item_handler<post_t> @@ -274,6 +353,13 @@ public: } virtual void operator()(post_t& post); + + virtual void clear() { + temps.clear(); + last_xact = NULL; + + item_handler<post_t>::clear(); + } }; class calc_posts : public item_handler<post_t> @@ -297,6 +383,13 @@ public: } virtual void operator()(post_t& post); + + virtual void clear() { + last_post = NULL; + amount_expr.mark_uncompiled(); + + item_handler<post_t>::clear(); + } }; class collapse_posts : public item_handler<post_t> @@ -334,13 +427,29 @@ public: } virtual void flush() { - report_subtotal(); + report_subtotal(); item_handler<post_t>::flush(); } void report_subtotal(); virtual void operator()(post_t& post); + + virtual void clear() { + amount_expr.mark_uncompiled(); + display_predicate.mark_uncompiled(); + only_predicate.mark_uncompiled(); + + subtotal = value_t(); + count = 0; + last_xact = NULL; + last_post = NULL; + + temps.clear(); + component_posts.clear(); + + item_handler<post_t>::clear(); + } }; class related_posts : public item_handler<post_t> @@ -367,6 +476,11 @@ public: post.xdata().add_flags(POST_EXT_RECEIVED); posts.push_back(&post); } + + virtual void clear() { + posts.clear(); + item_handler<post_t>::clear(); + } }; class changed_value_posts : public item_handler<post_t> @@ -381,9 +495,11 @@ class changed_value_posts : public item_handler<post_t> bool changed_values_only; bool for_accounts_report; bool show_unrealized; + bool show_rounding; post_t * last_post; value_t last_total; value_t last_display_total; + value_t repriced_total; temporaries_t temps; account_t& revalued_account; account_t& rounding_account; @@ -396,7 +512,8 @@ public: changed_value_posts(post_handler_ptr handler, report_t& _report, bool _for_accounts_report, - bool _show_unrealized); + bool _show_unrealized, + bool _show_rounding); virtual ~changed_value_posts() { TRACE_DTOR(changed_value_posts); @@ -405,9 +522,24 @@ public: virtual void flush(); void output_revaluation(post_t& post, const date_t& current); + void output_intermediate_prices(post_t& post, const date_t& current); void output_rounding(post_t& post); virtual void operator()(post_t& post); + + virtual void clear() { + display_amount_expr.mark_uncompiled(); + total_expr.mark_uncompiled(); + display_total_expr.mark_uncompiled(); + + last_post = NULL; + last_total = value_t(); + last_display_total = value_t(); + + temps.clear(); + + item_handler<post_t>::clear(); + } }; class subtotal_posts : public item_handler<post_t> @@ -469,10 +601,20 @@ public: item_handler<post_t>::flush(); } virtual void operator()(post_t& post); + + virtual void clear() { + amount_expr.mark_uncompiled(); + values.clear(); + temps.clear(); + component_posts.clear(); + + item_handler<post_t>::clear(); + } }; class interval_posts : public subtotal_posts { + date_interval_t start_interval; date_interval_t interval; date_interval_t last_interval; post_t * last_post; @@ -489,8 +631,9 @@ public: const date_interval_t& _interval, bool _exact_periods = false, bool _generate_empty_posts = false) - : subtotal_posts(_handler, amount_expr), interval(_interval), - last_post(NULL), empty_account(temps.create_account(_("<None>"))), + : subtotal_posts(_handler, amount_expr), start_interval(_interval), + interval(start_interval), last_post(NULL), + empty_account(temps.create_account(_("<None>"))), exact_periods(_exact_periods), generate_empty_posts(_generate_empty_posts) { TRACE_CTOR(interval_posts, @@ -510,6 +653,14 @@ public: } } virtual void operator()(post_t& post); + + virtual void clear() { + interval = start_interval; + last_interval = date_interval_t(); + last_post = NULL; + + item_handler<post_t>::clear(); + } }; class posts_as_equity : public subtotal_posts @@ -537,6 +688,11 @@ public: report_subtotal(); subtotal_posts::flush(); } + + virtual void clear() { + last_post = NULL; + item_handler<post_t>::clear(); + } }; class by_payee_posts : public item_handler<post_t> @@ -560,6 +716,13 @@ class by_payee_posts : public item_handler<post_t> virtual void flush(); virtual void operator()(post_t& post); + + virtual void clear() { + amount_expr.mark_uncompiled(); + payee_subtotals.clear(); + + item_handler<post_t>::clear(); + } }; class transfer_details : public item_handler<post_t> @@ -593,6 +756,13 @@ public: } virtual void operator()(post_t& post); + + virtual void clear() { + expr.mark_uncompiled(); + temps.clear(); + + item_handler<post_t>::clear(); + } }; class dow_posts : public subtotal_posts @@ -614,6 +784,13 @@ public: virtual void operator()(post_t& post) { days_of_the_week[post.date().day_of_week()].push_back(&post); } + + virtual void clear() { + for (int i = 0; i < 7; i++) + days_of_the_week[i].clear(); + + item_handler<post_t>::clear(); + } }; class generate_posts : public item_handler<post_t> @@ -640,6 +817,13 @@ public: void add_period_xacts(period_xacts_list& period_xacts); virtual void add_post(const date_interval_t& period, post_t& post); + + virtual void clear() { + pending_posts.clear(); + temps.clear(); + + item_handler<post_t>::clear(); + } }; class budget_posts : public generate_posts @@ -690,6 +874,11 @@ class forecast_posts : public generate_posts virtual void add_post(const date_interval_t& period, post_t& post); virtual void flush(); + + virtual void clear() { + pred.mark_uncompiled(); + item_handler<post_t>::clear(); + } }; ////////////////////////////////////////////////////////////////////// @@ -715,6 +904,13 @@ public: virtual ~pass_down_accounts() { TRACE_DTOR(pass_down_accounts); } + + virtual void clear() { + if (pred) + pred->mark_uncompiled(); + + item_handler<account_t>::clear(); + } }; } // namespace ledger diff --git a/src/item.cc b/src/item.cc index 14a0896f..0a22b260 100644 --- a/src/item.cc +++ b/src/item.cc @@ -227,7 +227,7 @@ namespace { return NULL_VALUE; } value_t get_note(item_t& item) { - return string_value(item.note ? *item.note : empty_string); + return item.note ? string_value(*item.note) : NULL_VALUE; } value_t has_tag(call_scope_t& args) { @@ -260,7 +260,8 @@ namespace { return false; } - value_t get_tag(call_scope_t& args) { + value_t get_tag(call_scope_t& args) + { item_t& item(find_scope<item_t>(args)); optional<string> str; @@ -292,14 +293,14 @@ namespace { if (str) return string_value(*str); else - return string_value(empty_string); + return NULL_VALUE; } value_t get_pathname(item_t& item) { if (item.pos) return string_value(item.pos->pathname.string()); else - return string_value(empty_string); + return NULL_VALUE; } value_t get_beg_pos(item_t& item) { diff --git a/src/option.h b/src/option.h index 9688171e..f11497a4 100644 --- a/src/option.h +++ b/src/option.h @@ -90,13 +90,16 @@ public: void report(std::ostream& out) const { if (handled && source) { + out.width(24); + out << std::right << desc(); if (wants_arg) { - out << desc() << " => "; - value.dump(out); + out << " = "; + value.print(out, 42); } else { - out << desc(); + out.width(45); + out << ' '; } - out << " <" << *source << ">" << std::endl; + out << std::left << *source << std::endl; } } @@ -202,20 +205,20 @@ public: }; #define BEGIN(type, name) \ - struct name ## _option_t : public option_t<type> + struct name ## option_t : public option_t<type> #define CTOR(type, name) \ - name ## _option_t() : option_t<type>(#name) + name ## option_t() : option_t<type>(#name) #define DECL1(type, name, vartype, var, value) \ vartype var ; \ - name ## _option_t() : option_t<type>(#name), var(value) + name ## option_t() : option_t<type>(#name), var(value) #define DO() virtual void handler_thunk(call_scope_t&) #define DO_(var) virtual void handler_thunk(call_scope_t& var) -#define END(name) name ## _handler +#define END(name) name ## handler -#define COPY_OPT(name, other) name ## _handler(other.name ## _handler) +#define COPY_OPT(name, other) name ## handler(other.name ## handler) #define MAKE_OPT_HANDLER(type, x) \ expr_t::op_t::wrap_functor(bind(&option_t<type>::handler_wrapper, x, _1)) @@ -235,26 +238,26 @@ inline bool is_eq(const char * p, const char * n) { #define OPT(name) \ if (is_eq(p, #name)) \ - return ((name ## _handler).parent = this, &(name ## _handler)) + return ((name ## handler).parent = this, &(name ## handler)) #define OPT_ALT(name, alt) \ if (is_eq(p, #name) || is_eq(p, #alt)) \ - return ((name ## _handler).parent = this, &(name ## _handler)) + return ((name ## handler).parent = this, &(name ## handler)) #define OPT_(name) \ if (! *(p + 1) || \ - ((name ## _handler).wants_arg && \ + ((name ## handler).wants_arg && \ *(p + 1) == '_' && ! *(p + 2)) || \ is_eq(p, #name)) \ - return ((name ## _handler).parent = this, &(name ## _handler)) + return ((name ## handler).parent = this, &(name ## handler)) #define OPT_CH(name) \ if (! *(p + 1) || \ - ((name ## _handler).wants_arg && \ + ((name ## handler).wants_arg && \ *(p + 1) == '_' && ! *(p + 2))) \ - return ((name ## _handler).parent = this, &(name ## _handler)) + return ((name ## handler).parent = this, &(name ## handler)) -#define HANDLER(name) name ## _handler +#define HANDLER(name) name ## handler #define HANDLED(name) HANDLER(name) #define OPTION(type, name) \ diff --git a/src/output.cc b/src/output.cc index 30775310..f697dee4 100644 --- a/src/output.cc +++ b/src/output.cc @@ -42,8 +42,10 @@ namespace ledger { format_posts::format_posts(report_t& _report, const string& format, - const optional<string>& _prepend_format) - : report(_report), last_xact(NULL), last_post(NULL) + const optional<string>& _prepend_format, + std::size_t _prepend_width) + : report(_report), prepend_width(_prepend_width), + last_xact(NULL), last_post(NULL), first_report_title(true) { TRACE_CTOR(format_posts, "report&, const string&, bool"); @@ -76,14 +78,34 @@ void format_posts::flush() void format_posts::operator()(post_t& post) { - std::ostream& out(report.output_stream); - if (! post.has_xdata() || ! post.xdata().has_flags(POST_EXT_DISPLAYED)) { + std::ostream& out(report.output_stream); + bind_scope_t bound_scope(report, post); - if (prepend_format) + if (! report_title.empty()) { + if (first_report_title) + first_report_title = false; + else + out << '\n'; + + value_scope_t val_scope(string_value(report_title)); + bind_scope_t inner_scope(bound_scope, val_scope); + + format_t group_title_format; + group_title_format + .parse_format(report.HANDLER(group_title_format_).str()); + + out << group_title_format(inner_scope); + + report_title = ""; + } + + if (prepend_format) { + out.width(prepend_width); out << prepend_format(bound_scope); + } if (last_xact != post.xact) { if (last_xact) { @@ -107,8 +129,10 @@ void format_posts::operator()(post_t& post) format_accounts::format_accounts(report_t& _report, const string& format, - const optional<string>& _prepend_format) - : report(_report), disp_pred() + const optional<string>& _prepend_format, + std::size_t _prepend_width) + : report(_report), prepend_width(_prepend_width), disp_pred(), + first_report_title(true) { TRACE_CTOR(format_accounts, "report&, const string&"); @@ -139,17 +163,37 @@ std::size_t format_accounts::post_account(account_t& account, const bool flat) if (account.xdata().has_flags(ACCOUNT_EXT_TO_DISPLAY) && ! account.xdata().has_flags(ACCOUNT_EXT_DISPLAYED)) { + std::ostream& out(report.output_stream); + DEBUG("account.display", "Displaying account: " << account.fullname()); account.xdata().add_flags(ACCOUNT_EXT_DISPLAYED); bind_scope_t bound_scope(report, account); - if (prepend_format) - static_cast<std::ostream&>(report.output_stream) - << prepend_format(bound_scope); + if (! report_title.empty()) { + if (first_report_title) + first_report_title = false; + else + out << '\n'; + + value_scope_t val_scope(string_value(report_title)); + bind_scope_t inner_scope(bound_scope, val_scope); + + format_t group_title_format; + group_title_format + .parse_format(report.HANDLER(group_title_format_).str()); - static_cast<std::ostream&>(report.output_stream) - << account_line_format(bound_scope); + out << group_title_format(inner_scope); + + report_title = ""; + } + + if (prepend_format) { + out.width(prepend_width); + out << prepend_format(bound_scope); + } + + out << account_line_format(bound_scope); return 1; } @@ -216,9 +260,11 @@ void format_accounts::flush() bind_scope_t bound_scope(report, *report.session.journal->master); out << separator_format(bound_scope); - if (prepend_format) + if (prepend_format) { + static_cast<std::ostream&>(report.output_stream).width(prepend_width); static_cast<std::ostream&>(report.output_stream) << prepend_format(bound_scope); + } out << total_line_format(bound_scope); } @@ -232,4 +278,89 @@ void format_accounts::operator()(account_t& account) posted_accounts.push_back(&account); } +void report_accounts::flush() +{ + std::ostream& out(report.output_stream); + + foreach (accounts_pair& entry, accounts) { + if (report.HANDLED(count)) + out << entry.second << ' '; + out << *entry.first << '\n'; + } +} + +void report_accounts::operator()(post_t& post) +{ + std::map<account_t *, std::size_t>::iterator i = accounts.find(post.account); + if (i == accounts.end()) + accounts.insert(accounts_pair(post.account, 1)); + else + (*i).second++; +} + +void report_payees::flush() +{ + std::ostream& out(report.output_stream); + + foreach (payees_pair& entry, payees) { + if (report.HANDLED(count)) + out << entry.second << ' '; + out << entry.first << '\n'; + } +} + +void report_payees::operator()(post_t& post) +{ + std::map<string, std::size_t>::iterator i = payees.find(post.xact->payee); + if (i == payees.end()) + payees.insert(payees_pair(post.xact->payee, 1)); + else + (*i).second++; +} + +void report_commodities::flush() +{ + std::ostream& out(report.output_stream); + + foreach (commodities_pair& entry, commodities) { + if (report.HANDLED(count)) + out << entry.second << ' '; + out << *entry.first << '\n'; + } +} + +void report_commodities::operator()(post_t& post) +{ + amount_t temp(post.amount.strip_annotations(report.what_to_keep())); + commodity_t& comm(temp.commodity()); + + std::map<commodity_t *, std::size_t>::iterator i = commodities.find(&comm); + if (i == commodities.end()) + commodities.insert(commodities_pair(&comm, 1)); + else + (*i).second++; + + if (comm.has_annotation()) { + annotated_commodity_t& ann_comm(as_annotated_commodity(comm)); + if (ann_comm.details.price) { + std::map<commodity_t *, std::size_t>::iterator i = + commodities.find(&ann_comm.details.price->commodity()); + if (i == commodities.end()) + commodities.insert + (commodities_pair(&ann_comm.details.price->commodity(), 1)); + else + (*i).second++; + } + } + + if (post.cost) { + amount_t temp_cost(post.cost->strip_annotations(report.what_to_keep())); + i = commodities.find(&temp_cost.commodity()); + if (i == commodities.end()) + commodities.insert(commodities_pair(&temp_cost.commodity(), 1)); + else + (*i).second++; + } +} + } // namespace ledger diff --git a/src/output.h b/src/output.h index 7618e567..a19c6235 100644 --- a/src/output.h +++ b/src/output.h @@ -56,23 +56,40 @@ class report_t; class format_posts : public item_handler<post_t> { protected: - report_t& report; - format_t first_line_format; - format_t next_lines_format; - format_t between_format; - format_t prepend_format; - xact_t * last_xact; - post_t * last_post; + report_t& report; + format_t first_line_format; + format_t next_lines_format; + format_t between_format; + format_t prepend_format; + std::size_t prepend_width; + xact_t * last_xact; + post_t * last_post; + bool first_report_title; + string report_title; public: format_posts(report_t& _report, const string& format, - const optional<string>& _prepend_format = none); + const optional<string>& _prepend_format = none, + std::size_t _prepend_width = 0); virtual ~format_posts() { TRACE_DTOR(format_posts); } + virtual void title(const string& str) { + report_title = str; + } + virtual void flush(); virtual void operator()(post_t& post); + + virtual void clear() { + last_xact = NULL; + last_post = NULL; + + report_title = ""; + + item_handler<post_t>::clear(); + } }; class format_accounts : public item_handler<account_t> @@ -83,13 +100,17 @@ protected: format_t total_line_format; format_t separator_format; format_t prepend_format; + std::size_t prepend_width; predicate_t disp_pred; + bool first_report_title; + string report_title; std::list<account_t *> posted_accounts; public: format_accounts(report_t& _report, const string& _format, - const optional<string>& _prepend_format = none); + const optional<string>& _prepend_format = none, + std::size_t _prepend_width = 0); virtual ~format_accounts() { TRACE_DTOR(format_accounts); } @@ -97,10 +118,101 @@ public: std::pair<std::size_t, std::size_t> mark_accounts(account_t& account, const bool flat); + virtual void title(const string& str) { + report_title = str; + } + virtual std::size_t post_account(account_t& account, const bool flat); virtual void flush(); virtual void operator()(account_t& account); + + virtual void clear() { + disp_pred.mark_uncompiled(); + posted_accounts.clear(); + + report_title = ""; + + item_handler<account_t>::clear(); + } +}; + +class report_accounts : public item_handler<post_t> +{ +protected: + report_t& report; + + std::map<account_t *, std::size_t> accounts; + + typedef std::map<account_t *, std::size_t>::value_type accounts_pair; + +public: + report_accounts(report_t& _report) : report(_report) { + TRACE_CTOR(report_accounts, "report&"); + } + virtual ~report_accounts() { + TRACE_DTOR(report_accounts); + } + + virtual void flush(); + virtual void operator()(post_t& post); + + virtual void clear() { + accounts.clear(); + item_handler<post_t>::clear(); + } +}; + +class report_payees : public item_handler<post_t> +{ +protected: + report_t& report; + + std::map<string, std::size_t> payees; + + typedef std::map<string, std::size_t>::value_type payees_pair; + +public: + report_payees(report_t& _report) : report(_report) { + TRACE_CTOR(report_payees, "report&"); + } + virtual ~report_payees() { + TRACE_DTOR(report_payees); + } + + virtual void flush(); + virtual void operator()(post_t& post); + + virtual void clear() { + payees.clear(); + item_handler<post_t>::clear(); + } +}; + +class report_commodities : public item_handler<post_t> +{ +protected: + report_t& report; + + std::map<commodity_t *, std::size_t> commodities; + + typedef std::map<commodity_t *, std::size_t>::value_type commodities_pair; + +public: + report_commodities(report_t& _report) : report(_report) { + TRACE_CTOR(report_commodities, "report&"); + } + virtual ~report_commodities() { + TRACE_DTOR(report_commodities); + } + + virtual void flush(); + virtual void operator()(post_t& post); + + virtual void clear() { + commodities.clear(); + item_handler<post_t>::clear(); + } }; } // namespace ledger diff --git a/src/parser.cc b/src/parser.cc index e8e987cb..f52949ce 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -79,9 +79,7 @@ expr_t::parser_t::parse_value_term(std::istream& in, case token_t::LPAREN: node = parse_value_expr(in, tflags.plus_flags(PARSE_PARTIAL) .minus_flags(PARSE_SINGLE)); - tok = next_token(in, tflags); - if (tok.kind != token_t::RPAREN) - tok.expected(')'); + tok = next_token(in, tflags, ')'); if (node->kind == op_t::O_CONS) { ptr_op_t prev(node); @@ -383,10 +381,7 @@ expr_t::parser_t::parse_querycolon_expr(std::istream& in, throw_(parse_error, _("%1 operator not followed by argument") << tok.symbol); - token_t& next_tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT)); - if (next_tok.kind != token_t::COLON) - next_tok.expected(':'); - + next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT), ':'); prev = node->right(); ptr_op_t subnode = new op_t(op_t::O_COLON); subnode->set_left(prev); diff --git a/src/parser.h b/src/parser.h index 5eba4ffd..aab48830 100644 --- a/src/parser.h +++ b/src/parser.h @@ -52,11 +52,12 @@ class expr_t::parser_t : public noncopyable mutable token_t lookahead; mutable bool use_lookahead; - token_t& next_token(std::istream& in, const parse_flags_t& tflags) const { + token_t& next_token(std::istream& in, const parse_flags_t& tflags, + const char expecting = '\0') const { if (use_lookahead) use_lookahead = false; else - lookahead.next(in, tflags); + lookahead.next(in, tflags, expecting); return lookahead; } diff --git a/src/post.cc b/src/post.cc index 183fb901..7dc15830 100644 --- a/src/post.cc +++ b/src/post.cc @@ -142,11 +142,15 @@ namespace { return value_t(static_cast<scope_t *>(post.xact)); } + value_t get_xact_id(post_t& post) { + return static_cast<long>(post.xact_id()); + } + value_t get_code(post_t& post) { if (post.xact->code) return string_value(*post.xact->code); else - return string_value(empty_string); + return NULL_VALUE; } value_t get_payee(post_t& post) { @@ -154,9 +158,13 @@ namespace { } value_t get_note(post_t& post) { - string note = post.note ? *post.note : empty_string; - note += post.xact->note ? *post.xact->note : empty_string; - return string_value(note); + if (post.note || post.xact->note) { + string note = post.note ? *post.note : empty_string; + note += post.xact->note ? *post.xact->note : empty_string; + return string_value(note); + } else { + return NULL_VALUE; + } } value_t get_magnitude(post_t& post) { @@ -183,11 +191,21 @@ namespace { } value_t get_commodity(post_t& post) { - return string_value(post.amount.commodity().symbol()); + if (post.has_xdata() && + post.xdata().has_flags(POST_EXT_COMPOUND)) + return string_value(post.xdata().compound_value.to_amount() + .commodity().symbol()); + else + return string_value(post.amount.commodity().symbol()); } value_t get_commodity_is_primary(post_t& post) { - return post.amount.commodity().has_flags(COMMODITY_PRIMARY); + if (post.has_xdata() && + post.xdata().has_flags(POST_EXT_COMPOUND)) + return post.xdata().compound_value.to_amount() + .commodity().has_flags(COMMODITY_PRIMARY); + else + return post.amount.commodity().has_flags(COMMODITY_PRIMARY); } value_t get_has_cost(post_t& post) { @@ -222,7 +240,7 @@ namespace { return 1L; } - value_t get_account(call_scope_t& scope) + value_t account_name(call_scope_t& scope) { in_context_t<post_t> env(scope, "&v"); @@ -265,14 +283,32 @@ namespace { } else { name = env->reported_account()->fullname(); } + return string_value(name); + } - if (env->has_flags(POST_VIRTUAL)) { - if (env->must_balance()) - name = string("[") + name + "]"; - else - name = string("(") + name + ")"; + value_t get_display_account(call_scope_t& scope) + { + in_context_t<post_t> env(scope, "&v"); + + value_t acct = account_name(scope); + if (acct.is_string()) { + if (env->has_flags(POST_VIRTUAL)) { + if (env->must_balance()) + acct = string_value(string("[") + acct.as_string() + "]"); + else + acct = string_value(string("(") + acct.as_string() + ")"); + } } - return string_value(name); + return acct; + } + + value_t get_account(call_scope_t& scope) + { + return account_name(scope); + } + + value_t get_account_id(post_t& post) { + return static_cast<long>(post.account_id()); } value_t get_account_base(post_t& post) { @@ -351,6 +387,8 @@ expr_t::ptr_op_t post_t::lookup(const symbol_t::kind_t kind, return WRAP_FUNCTOR(get_account); else if (name == "account_base") return WRAP_FUNCTOR(get_wrapper<&get_account_base>); + else if (name == "account_id") + return WRAP_FUNCTOR(get_wrapper<&get_account_id>); else if (name == "any") return WRAP_FUNCTOR(&fn_any); else if (name == "all") @@ -378,7 +416,9 @@ expr_t::ptr_op_t post_t::lookup(const symbol_t::kind_t kind, break; case 'd': - if (name == "depth") + if (name == "display_account") + return WRAP_FUNCTOR(get_display_account); + else if (name == "depth") return WRAP_FUNCTOR(get_wrapper<&get_account_depth>); else if (name == "datetime") return WRAP_FUNCTOR(get_wrapper<&get_datetime>); @@ -444,6 +484,8 @@ expr_t::ptr_op_t post_t::lookup(const symbol_t::kind_t kind, case 'x': if (name == "xact") return WRAP_FUNCTOR(get_wrapper<&get_xact>); + else if (name == "xact_id") + return WRAP_FUNCTOR(get_wrapper<&get_xact_id>); break; case 'N': @@ -479,6 +521,30 @@ amount_t post_t::resolve_expr(scope_t& scope, expr_t& expr) } } +std::size_t post_t::xact_id() const +{ + std::size_t id = 1; + foreach (post_t * p, xact->posts) { + if (p == this) + return id; + id++; + } + assert(! "Failed to find posting within its transaction"); + return 0; +} + +std::size_t post_t::account_id() const +{ + std::size_t id = 1; + foreach (post_t * p, account->posts) { + if (p == this) + return id; + id++; + } + assert(! "Failed to find posting within its transaction"); + return 0; +} + bool post_t::valid() const { if (! xact) { @@ -118,6 +118,9 @@ public: amount_t resolve_expr(scope_t& scope, expr_t& expr); + std::size_t xact_id() const; + std::size_t account_id() const; + bool valid() const; struct xdata_t : public supports_flags<uint_least16_t> diff --git a/src/print.cc b/src/print.cc index 34f5af51..a8aa5872 100644 --- a/src/print.cc +++ b/src/print.cc @@ -42,15 +42,20 @@ namespace ledger { print_xacts::print_xacts(report_t& _report, bool _print_raw) - : report(_report), print_raw(_print_raw) + : report(_report), print_raw(_print_raw), first_title(true) { TRACE_CTOR(print_xacts, "report&, bool"); } namespace { - void print_note(std::ostream& out, const string& note) + void print_note(std::ostream& out, + const string& note, + const std::size_t columns, + const std::size_t prior_width) { - if (note.length() > 15) + // The 4 is for four leading spaces at the beginning of the posting, and + // the 3 is for two spaces and a semi-colon before the note. + if (columns > 0 && note.length() > columns - (prior_width + 3)) out << "\n ;"; else out << " ;"; @@ -71,23 +76,40 @@ namespace { void print_xact(report_t& report, std::ostream& out, xact_t& xact) { - out << format_date(item_t::use_effective_date ? + format_type_t format_type = FMT_WRITTEN; + optional<const char *> format; + + if (report.HANDLED(date_format_)) { + format_type = FMT_CUSTOM; + format = report.HANDLER(date_format_).str().c_str(); + } + + std::ostringstream buf; + + buf << format_date(item_t::use_effective_date ? xact.date() : xact.actual_date(), - FMT_WRITTEN); + format_type, format); if (! item_t::use_effective_date && xact.effective_date()) - out << '=' << format_date(*xact.effective_date(), FMT_WRITTEN); - out << ' '; + buf << '=' << format_date(*xact.effective_date(), + format_type, format); + buf << ' '; - out << (xact.state() == item_t::CLEARED ? "* " : + buf << (xact.state() == item_t::CLEARED ? "* " : (xact.state() == item_t::PENDING ? "! " : "")); if (xact.code) - out << '(' << *xact.code << ") "; + buf << '(' << *xact.code << ") "; + + buf << xact.payee; - out << xact.payee; + string leader = buf.str(); + out << leader; + + std::size_t columns = (report.HANDLED(columns_) ? + report.HANDLER(columns_).value.to_long() : 80); if (xact.note) - print_note(out, *xact.note); + print_note(out, *xact.note, columns, unistring(leader).length()); out << '\n'; if (xact.metadata) { @@ -132,20 +154,37 @@ namespace { buf << ')'; } - if (! post->has_flags(POST_CALCULATED) || report.HANDLED(print_virtual)) { - unistring name(buf.str()); + unistring name(buf.str()); + + std::size_t account_width = + (report.HANDLER(account_width_).specified ? + report.HANDLER(account_width_).value.to_long() : 36); + if (account_width < name.length()) + account_width = name.length(); + + if (! post->has_flags(POST_CALCULATED) || report.HANDLED(print_virtual)) { out << name.extract(); - int slip = 36 - static_cast<int>(name.length()); - if (slip > 0) - out << string(slip, ' '); + int slip = (static_cast<int>(account_width) - + static_cast<int>(name.length())); + if (slip > 0) { + out.width(slip); + out << ' '; + } + + std::ostringstream amtbuf; string amt; if (post->amount_expr) { amt = post->amount_expr->text(); } else { + std::size_t amount_width = + (report.HANDLER(amount_width_).specified ? + report.HANDLER(amount_width_).value.to_long() : 12); + std::ostringstream amt_str; - report.scrub(post->amount).print(amt_str, 12, -1, true); + report.scrub(post->amount) + .print(amt_str, static_cast<int>(amount_width), -1, true); amt = amt_str.str(); } @@ -154,29 +193,44 @@ namespace { int amt_slip = (static_cast<int>(amt.length()) - static_cast<int>(trimmed_amt.length())); if (slip + amt_slip < 2) - out << string(2 - (slip + amt_slip), ' '); - out << amt; + amtbuf << string(2 - (slip + amt_slip), ' '); + amtbuf << amt; if (post->cost && ! post->has_flags(POST_CALCULATED)) { if (post->has_flags(POST_COST_IN_FULL)) - out << " @@ " << report.scrub(post->cost->abs()); + amtbuf << " @@ " << report.scrub(post->cost->abs()); else - out << " @ " << report.scrub((*post->cost / post->amount).abs()); + amtbuf << " @ " << report.scrub((*post->cost / post->amount).abs()); } if (post->assigned_amount) - out << " = " << report.scrub(*post->assigned_amount); + amtbuf << " = " << report.scrub(*post->assigned_amount); + + string trailer = amtbuf.str(); + out << trailer; + + account_width += unistring(trailer).length(); } else { out << buf.str(); } if (post->note) - print_note(out, *post->note); + print_note(out, *post->note, columns, 4 + account_width); out << '\n'; } } } +void print_xacts::title(const string&) +{ + if (first_title) { + first_title = false; + } else { + std::ostream& out(report.output_stream); + out << '\n'; + } +} + void print_xacts::flush() { std::ostream& out(report.output_stream); diff --git a/src/print.h b/src/print.h index f323b153..5263ec91 100644 --- a/src/print.h +++ b/src/print.h @@ -62,6 +62,7 @@ protected: xacts_present_map xacts_present; xacts_list xacts; bool print_raw; + bool first_title; public: print_xacts(report_t& _report, bool _print_raw = false); @@ -69,8 +70,17 @@ public: TRACE_DTOR(print_xacts); } + virtual void title(const string&); + virtual void flush(); virtual void operator()(post_t& post); + + virtual void clear() { + xacts_present.clear(); + xacts.clear(); + + item_handler<post_t>::clear(); + } }; diff --git a/src/py_account.cc b/src/py_account.cc index 3114cc0b..2b860a24 100644 --- a/src/py_account.cc +++ b/src/py_account.cc @@ -207,11 +207,11 @@ void export_account() .def("__len__", accounts_len) .def("__getitem__", accounts_getitem, return_internal_reference<>()) - .def("__iter__", range<return_internal_reference<> > + .def("__iter__", python::range<return_internal_reference<> > (&account_t::accounts_begin, &account_t::accounts_end)) - .def("accounts", range<return_internal_reference<> > + .def("accounts", python::range<return_internal_reference<> > (&account_t::accounts_begin, &account_t::accounts_end)) - .def("posts", range<return_internal_reference<> > + .def("posts", python::range<return_internal_reference<> > (&account_t::posts_begin, &account_t::posts_end)) .def("has_xdata", &account_t::has_xdata) diff --git a/src/py_commodity.cc b/src/py_commodity.cc index 22a4f153..fc7e8c3e 100644 --- a/src/py_commodity.cc +++ b/src/py_commodity.cc @@ -295,13 +295,16 @@ void export_commodity() .def("keys", py_pool_keys) .def("has_key", py_pool_contains) .def("__contains__", py_pool_contains) - .def("__iter__", range<return_value_policy<reference_existing_object> > + .def("__iter__", + python::range<return_value_policy<reference_existing_object> > (py_pool_commodities_begin, py_pool_commodities_end)) - .def("iteritems", range<return_value_policy<reference_existing_object> > + .def("iteritems", + python::range<return_value_policy<reference_existing_object> > (py_pool_commodities_begin, py_pool_commodities_end)) - .def("iterkeys", range<>(py_pool_commodities_keys_begin, - py_pool_commodities_keys_end)) - .def("itervalues", range<return_value_policy<reference_existing_object> > + .def("iterkeys", python::range<>(py_pool_commodities_keys_begin, + py_pool_commodities_keys_end)) + .def("itervalues", + python::range<return_value_policy<reference_existing_object> > (py_pool_commodities_values_begin, py_pool_commodities_values_end)) ; @@ -309,16 +312,16 @@ void export_commodity() scope().attr("commodities") = commodity_pool_t::current_pool; - scope().attr("COMMODITY_STYLE_DEFAULTS") = COMMODITY_STYLE_DEFAULTS; - scope().attr("COMMODITY_STYLE_SUFFIXED") = COMMODITY_STYLE_SUFFIXED; - scope().attr("COMMODITY_STYLE_SEPARATED") = COMMODITY_STYLE_SEPARATED; - scope().attr("COMMODITY_STYLE_EUROPEAN") = COMMODITY_STYLE_EUROPEAN; - scope().attr("COMMODITY_STYLE_THOUSANDS") = COMMODITY_STYLE_THOUSANDS; - scope().attr("COMMODITY_NOMARKET") = COMMODITY_NOMARKET; - scope().attr("COMMODITY_BUILTIN") = COMMODITY_BUILTIN; - scope().attr("COMMODITY_WALKED") = COMMODITY_WALKED; - scope().attr("COMMODITY_KNOWN") = COMMODITY_KNOWN; - scope().attr("COMMODITY_PRIMARY") = COMMODITY_PRIMARY; + scope().attr("COMMODITY_STYLE_DEFAULTS") = COMMODITY_STYLE_DEFAULTS; + scope().attr("COMMODITY_STYLE_SUFFIXED") = COMMODITY_STYLE_SUFFIXED; + scope().attr("COMMODITY_STYLE_SEPARATED") = COMMODITY_STYLE_SEPARATED; + scope().attr("COMMODITY_STYLE_DECIMAL_COMMA") = COMMODITY_STYLE_DECIMAL_COMMA; + scope().attr("COMMODITY_STYLE_THOUSANDS") = COMMODITY_STYLE_THOUSANDS; + scope().attr("COMMODITY_NOMARKET") = COMMODITY_NOMARKET; + scope().attr("COMMODITY_BUILTIN") = COMMODITY_BUILTIN; + scope().attr("COMMODITY_WALKED") = COMMODITY_WALKED; + scope().attr("COMMODITY_KNOWN") = COMMODITY_KNOWN; + scope().attr("COMMODITY_PRIMARY") = COMMODITY_PRIMARY; class_< commodity_t, boost::noncopyable > ("Commodity", no_init) #if 1 @@ -331,9 +334,9 @@ void export_commodity() .def("drop_flags", &delegates_flags<uint_least16_t>::drop_flags) #endif - .add_static_property("european_by_default", - make_getter(&commodity_t::european_by_default), - make_setter(&commodity_t::european_by_default)) + .add_static_property("decimal_comma_by_default", + make_getter(&commodity_t::decimal_comma_by_default), + make_setter(&commodity_t::decimal_comma_by_default)) .def("__str__", &commodity_t::symbol) .def("__unicode__", py_commodity_unicode) diff --git a/src/py_journal.cc b/src/py_journal.cc index 81ce290d..1848adc4 100644 --- a/src/py_journal.cc +++ b/src/py_journal.cc @@ -226,8 +226,8 @@ void export_journal() class_< collect_posts, bases<item_handler<post_t> >, shared_ptr<collect_posts>, boost::noncopyable >("PostCollector") .def("__len__", &collect_posts::length) - .def("__iter__", range<return_internal_reference<1, - with_custodian_and_ward_postcall<1, 0> > > + .def("__iter__", python::range<return_internal_reference<1, + with_custodian_and_ward_postcall<1, 0> > > (&collect_posts::begin, &collect_posts::end)) ; @@ -236,8 +236,9 @@ void export_journal() .def("__len__", &collector_wrapper::length) .def("__getitem__", posts_getitem, return_internal_reference<1, with_custodian_and_ward_postcall<0, 1> >()) - .def("__iter__", range<return_value_policy<reference_existing_object, - with_custodian_and_ward_postcall<0, 1> > > + .def("__iter__", + python::range<return_value_policy<reference_existing_object, + with_custodian_and_ward_postcall<0, 1> > > (&collector_wrapper::begin, &collector_wrapper::end)) ; @@ -296,15 +297,15 @@ void export_journal() with_custodian_and_ward_postcall<0, 1> >()) #endif - .def("__iter__", range<return_internal_reference<> > + .def("__iter__", python::range<return_internal_reference<> > (&journal_t::xacts_begin, &journal_t::xacts_end)) - .def("xacts", range<return_internal_reference<> > + .def("xacts", python::range<return_internal_reference<> > (&journal_t::xacts_begin, &journal_t::xacts_end)) - .def("auto_xacts", range<return_internal_reference<> > + .def("auto_xacts", python::range<return_internal_reference<> > (&journal_t::auto_xacts_begin, &journal_t::auto_xacts_end)) - .def("period_xacts", range<return_internal_reference<> > + .def("period_xacts", python::range<return_internal_reference<> > (&journal_t::period_xacts_begin, &journal_t::period_xacts_end)) - .def("sources", range<return_internal_reference<> > + .def("sources", python::range<return_internal_reference<> > (&journal_t::sources_begin, &journal_t::sources_end)) .def("read", py_read) diff --git a/src/py_xact.cc b/src/py_xact.cc index c4e1fd32..6553a67f 100644 --- a/src/py_xact.cc +++ b/src/py_xact.cc @@ -98,9 +98,9 @@ void export_xact() .def("finalize", &xact_base_t::finalize) - .def("__iter__", range<return_internal_reference<> > + .def("__iter__", python::range<return_internal_reference<> > (&xact_t::posts_begin, &xact_t::posts_end)) - .def("posts", range<return_internal_reference<> > + .def("posts", python::range<return_internal_reference<> > (&xact_t::posts_begin, &xact_t::posts_end)) .def("valid", &xact_base_t::valid) diff --git a/src/query.cc b/src/query.cc index 1f086df8..363c6f73 100644 --- a/src/query.cc +++ b/src/query.cc @@ -53,45 +53,51 @@ query_t::lexer_t::token_t query_t::lexer_t::next_token() } } - if (consume_next_arg) { - consume_next_arg = false; - arg_i = arg_end; - return token_t(token_t::TERM, (*begin).as_string()); - } - - resume: - bool consume_next = false; switch (*arg_i) { - case ' ': - case '\t': - case '\r': - case '\n': - if (++arg_i == arg_end) - return next_token(); - goto resume; - + case '\'': + case '"': case '/': { string pat; - bool found_end_slash = false; + char closing = *arg_i; + bool found_closing = false; for (++arg_i; arg_i != arg_end; ++arg_i) { if (*arg_i == '\\') { if (++arg_i == arg_end) throw_(parse_error, _("Unexpected '\\' at end of pattern")); } - else if (*arg_i == '/') { + else if (*arg_i == closing) { ++arg_i; - found_end_slash = true; + found_closing = true; break; } pat.push_back(*arg_i); } - if (! found_end_slash) - throw_(parse_error, _("Expected '/' at end of pattern")); + if (! found_closing) + throw_(parse_error, _("Expected '%1' at end of pattern") << closing); if (pat.empty()) throw_(parse_error, _("Match pattern is empty")); return token_t(token_t::TERM, pat); } + } + + if (multiple_args && consume_next_arg) { + consume_next_arg = false; + token_t tok(token_t::TERM, string(arg_i, arg_end)); + arg_i = arg_end; + return tok; + } + + resume: + bool consume_next = false; + switch (*arg_i) { + case ' ': + case '\t': + case '\r': + case '\n': + if (++arg_i == arg_end) + return next_token(); + goto resume; case '(': ++arg_i; return token_t(token_t::LPAREN); case ')': ++arg_i; return token_t(token_t::RPAREN); @@ -101,7 +107,10 @@ query_t::lexer_t::token_t query_t::lexer_t::next_token() case '@': ++arg_i; return token_t(token_t::TOK_PAYEE); case '#': ++arg_i; return token_t(token_t::TOK_CODE); case '%': ++arg_i; return token_t(token_t::TOK_META); - case '=': ++arg_i; return token_t(token_t::TOK_EQ); + case '=': + ++arg_i; + consume_next_arg = true; + return token_t(token_t::TOK_EQ); case '\\': consume_next = true; diff --git a/src/query.h b/src/query.h index 2b0bc75d..59adfd72 100644 --- a/src/query.h +++ b/src/query.h @@ -62,6 +62,7 @@ public: bool consume_whitespace; bool consume_next_arg; + bool multiple_args; public: struct token_t @@ -177,10 +178,11 @@ public: token_t token_cache; lexer_t(value_t::sequence_t::const_iterator _begin, - value_t::sequence_t::const_iterator _end) + value_t::sequence_t::const_iterator _end, + bool _multiple_args = true) : begin(_begin), end(_end), - consume_whitespace(false), - consume_next_arg(false) + consume_whitespace(false), consume_next_arg(false), + multiple_args(_multiple_args) { TRACE_CTOR(query_t::lexer_t, ""); assert(begin != end); @@ -192,6 +194,7 @@ public: arg_i(lexer.arg_i), arg_end(lexer.arg_end), consume_whitespace(lexer.consume_whitespace), consume_next_arg(lexer.consume_next_arg), + multiple_args(lexer.multiple_args), token_cache(lexer.token_cache) { TRACE_CTOR(query_t::lexer_t, "copy"); @@ -227,8 +230,8 @@ protected: expr_t::ptr_op_t parse_query_expr(lexer_t::token_t::kind_t tok_context); public: - parser_t(const value_t& _args) - : args(_args), lexer(args.begin(), args.end()) { + parser_t(const value_t& _args, bool multiple_args = true) + : args(_args), lexer(args.begin(), args.end(), multiple_args) { TRACE_CTOR(query_t::parser_t, ""); } parser_t(const parser_t& parser) @@ -261,28 +264,30 @@ public: TRACE_CTOR(query_t, "copy"); } query_t(const string& arg, - const keep_details_t& _what_to_keep = keep_details_t()) + const keep_details_t& _what_to_keep = keep_details_t(), + bool multiple_args = true) : predicate_t(_what_to_keep) { TRACE_CTOR(query_t, "string, keep_details_t"); if (! arg.empty()) { value_t temp(string_value(arg)); - parse_args(temp.to_sequence()); + parse_args(temp.to_sequence(), multiple_args); } } query_t(const value_t& args, - const keep_details_t& _what_to_keep = keep_details_t()) + const keep_details_t& _what_to_keep = keep_details_t(), + bool multiple_args = true) : predicate_t(_what_to_keep) { TRACE_CTOR(query_t, "value_t, keep_details_t"); if (! args.empty()) - parse_args(args); + parse_args(args, multiple_args); } virtual ~query_t() { TRACE_DTOR(query_t); } - void parse_args(const value_t& args) { + void parse_args(const value_t& args, bool multiple_args = true) { if (! parser) - parser = parser_t(args); + parser = parser_t(args, multiple_args); ptr = parser->parse(); // expr_t::ptr } diff --git a/src/report.cc b/src/report.cc index 1180c019..90de9a3f 100644 --- a/src/report.cc +++ b/src/report.cc @@ -119,6 +119,8 @@ void report_t::normalize_options(const string& verb) HANDLER(meta_).str() + "\"))"); } } + if (! HANDLED(prepend_width_)) + HANDLER(prepend_width_).on_with(string("?normalize"), static_cast<long>(0)); if (verb == "print" || verb == "xact" || verb == "dump") { HANDLER(related).on_only(string("?normalize")); @@ -143,9 +145,6 @@ void report_t::normalize_options(const string& verb) // then ignore the period since the begin/end are the only interesting // details. if (HANDLED(period_)) { - if (! HANDLED(sort_all_)) - HANDLER(sort_xacts_).on_only(string("?normalize")); - date_interval_t interval(HANDLER(period_).str()); optional<date_t> begin = interval.begin(session.current_year); @@ -162,6 +161,8 @@ void report_t::normalize_options(const string& verb) if (! interval.duration) HANDLER(period_).off(); + else if (! HANDLED(sort_all_)) + HANDLER(sort_xacts_).on_only(string("?normalize")); } // If -j or -J were specified, set the appropriate format string now so as @@ -273,77 +274,158 @@ void report_t::parse_query_args(const value_t& args, const string& whence) } } +namespace { + struct posts_flusher + { + report_t& report; + post_handler_ptr handler; + + posts_flusher(report_t& _report, post_handler_ptr _handler) + : report(_report), handler(_handler) {} + + void operator()(const value_t&) { + report.session.journal->clear_xdata(); + } + }; +} + void report_t::posts_report(post_handler_ptr handler) { + handler = chain_post_handlers(*this, handler); + if (HANDLED(group_by_)) { + std::auto_ptr<post_splitter> + splitter(new post_splitter(*this, handler, HANDLER(group_by_).expr)); + splitter->set_postflush_func(posts_flusher(*this, handler)); + handler = post_handler_ptr(splitter.release()); + } + handler = chain_pre_post_handlers(*this, handler); + journal_posts_iterator walker(*session.journal.get()); - pass_down_posts(chain_post_handlers(*this, handler), walker); - session.journal->clear_xdata(); + pass_down_posts(handler, walker); + + if (! HANDLED(group_by_)) + posts_flusher(*this, handler)(value_t()); } void report_t::generate_report(post_handler_ptr handler) { HANDLER(limit_).on(string("#generate"), "actual"); + handler = chain_handlers(*this, handler); + generate_posts_iterator walker (session, HANDLED(seed_) ? static_cast<unsigned int>(HANDLER(seed_).value.to_long()) : 0, HANDLED(head_) ? static_cast<unsigned int>(HANDLER(head_).value.to_long()) : 50); - pass_down_posts(chain_post_handlers(*this, handler), walker); + pass_down_posts(handler, walker); } void report_t::xact_report(post_handler_ptr handler, xact_t& xact) { + handler = chain_handlers(*this, handler); + xact_posts_iterator walker(xact); - pass_down_posts(chain_post_handlers(*this, handler), walker); + pass_down_posts(handler, walker); + xact.clear_xdata(); } +namespace { + struct accounts_title_printer + { + report_t& report; + acct_handler_ptr handler; + + accounts_title_printer(report_t& _report, acct_handler_ptr _handler) + : report(_report), handler(_handler) {} + + void operator()(const value_t& val) + { + if (! report.HANDLED(no_titles)) { + std::ostringstream buf; + val.print(buf); + handler->title(buf.str()); + } + } + }; + + struct accounts_flusher + { + report_t& report; + acct_handler_ptr handler; + + accounts_flusher(report_t& _report, acct_handler_ptr _handler) + : report(_report), handler(_handler) {} + + void operator()(const value_t&) + { + report.HANDLER(amount_).expr.mark_uncompiled(); + report.HANDLER(total_).expr.mark_uncompiled(); + report.HANDLER(display_amount_).expr.mark_uncompiled(); + report.HANDLER(display_total_).expr.mark_uncompiled(); + report.HANDLER(revalued_total_).expr.mark_uncompiled(); + + scoped_ptr<accounts_iterator> iter; + if (! report.HANDLED(sort_)) { + iter.reset(new basic_accounts_iterator(*report.session.journal->master)); + } else { + expr_t sort_expr(report.HANDLER(sort_).str()); + sort_expr.set_context(&report); + iter.reset(new sorted_accounts_iterator(*report.session.journal->master, + sort_expr, report.HANDLED(flat))); + } + + if (report.HANDLED(display_)) { + DEBUG("report.predicate", + "Display predicate = " << report.HANDLER(display_).str()); + pass_down_accounts(handler, *iter.get(), + predicate_t(report.HANDLER(display_).str(), + report.what_to_keep()), + report); + } else { + pass_down_accounts(handler, *iter.get()); + } + + report.session.journal->clear_xdata(); + } + }; +} + void report_t::accounts_report(acct_handler_ptr handler) { - journal_posts_iterator walker(*session.journal.get()); - - // The lifetime of the chain object controls the lifetime of all temporary - // objects created within it during the call to pass_down_posts, which will - // be needed later by the pass_down_accounts. post_handler_ptr chain = - chain_post_handlers(*this, post_handler_ptr(new ignore_posts), true); - pass_down_posts(chain, walker); + chain_post_handlers(*this, post_handler_ptr(new ignore_posts), + /* for_accounts_report= */ true); + if (HANDLED(group_by_)) { + std::auto_ptr<post_splitter> + splitter(new post_splitter(*this, chain, HANDLER(group_by_).expr)); - HANDLER(amount_).expr.mark_uncompiled(); - HANDLER(total_).expr.mark_uncompiled(); - HANDLER(display_amount_).expr.mark_uncompiled(); - HANDLER(display_total_).expr.mark_uncompiled(); - HANDLER(revalued_total_).expr.mark_uncompiled(); + splitter->set_preflush_func(accounts_title_printer(*this, handler)); + splitter->set_postflush_func(accounts_flusher(*this, handler)); - scoped_ptr<accounts_iterator> iter; - if (! HANDLED(sort_)) { - iter.reset(new basic_accounts_iterator(*session.journal->master)); - } else { - expr_t sort_expr(HANDLER(sort_).str()); - sort_expr.set_context(this); - iter.reset(new sorted_accounts_iterator(*session.journal->master, - sort_expr, HANDLED(flat))); + chain = post_handler_ptr(splitter.release()); } + chain = chain_pre_post_handlers(*this, chain); - if (HANDLED(display_)) { - DEBUG("report.predicate", - "Display predicate = " << HANDLER(display_).str()); - pass_down_accounts(handler, *iter.get(), - predicate_t(HANDLER(display_).str(), what_to_keep()), - *this); - } else { - pass_down_accounts(handler, *iter.get()); - } + // The lifetime of the chain object controls the lifetime of all temporary + // objects created within it during the call to pass_down_posts, which will + // be needed later by the pass_down_accounts. + journal_posts_iterator walker(*session.journal.get()); + pass_down_posts(chain, walker); - session.journal->clear_xdata(); + if (! HANDLED(group_by_)) + accounts_flusher(*this, handler)(value_t()); } void report_t::commodities_report(post_handler_ptr handler) { + handler = chain_handlers(*this, handler); + posts_commodities_iterator walker(*session.journal.get()); - pass_down_posts(chain_post_handlers(*this, handler), walker); + pass_down_posts(handler, walker); + session.journal->clear_xdata(); } @@ -853,6 +935,7 @@ option_t<report_t> * report_t::lookup_option(const char * p) else OPT(columns_); else OPT_ALT(basis, cost); else OPT_(current); + else OPT(count); break; case 'd': OPT(daily); @@ -886,6 +969,8 @@ option_t<report_t> * report_t::lookup_option(const char * p) break; case 'g': OPT(gain); + else OPT(group_by_); + else OPT(group_title_format_); break; case 'h': OPT(head_); @@ -914,6 +999,8 @@ option_t<report_t> * report_t::lookup_option(const char * p) case 'n': OPT_CH(collapse); else OPT(no_color); + else OPT(no_rounding); + else OPT(no_titles); else OPT(no_total); else OPT(now_); break; @@ -936,6 +1023,7 @@ option_t<report_t> * report_t::lookup_option(const char * p) else OPT(pricedb_format_); else OPT(payee_width_); else OPT(prepend_format_); + else OPT(prepend_width_); else OPT(print_virtual); break; case 'q': @@ -1222,12 +1310,19 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, case symbol_t::COMMAND: switch (*p) { + case 'a': + if (is_eq(p, "accounts")) + return WRAP_FUNCTOR(reporter<>(new report_accounts(*this), *this, + "#accounts")); + break; + case 'b': if (*(p + 1) == '\0' || is_eq(p, "bal") || is_eq(p, "balance")) { return expr_t::op_t::wrap_functor (reporter<account_t, acct_handler_ptr, &report_t::accounts_report> (new format_accounts(*this, report_format(HANDLER(balance_format_)), - maybe_format(HANDLER(prepend_format_))), + maybe_format(HANDLER(prepend_format_)), + HANDLER(prepend_width_).value.to_long()), *this, "#balance")); } else if (is_eq(p, "budget")) { @@ -1240,7 +1335,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return expr_t::op_t::wrap_functor (reporter<account_t, acct_handler_ptr, &report_t::accounts_report> (new format_accounts(*this, report_format(HANDLER(budget_format_)), - maybe_format(HANDLER(prepend_format_))), + maybe_format(HANDLER(prepend_format_)), + HANDLER(prepend_width_).value.to_long()), *this, "#budget")); } break; @@ -1250,7 +1346,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return WRAP_FUNCTOR (reporter<> (new format_posts(*this, report_format(HANDLER(csv_format_)), - maybe_format(HANDLER(prepend_format_))), + maybe_format(HANDLER(prepend_format_)), + HANDLER(prepend_width_).value.to_long()), *this, "#csv")); } else if (is_eq(p, "cleared")) { @@ -1259,11 +1356,17 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return expr_t::op_t::wrap_functor (reporter<account_t, acct_handler_ptr, &report_t::accounts_report> (new format_accounts(*this, report_format(HANDLER(cleared_format_)), - maybe_format(HANDLER(prepend_format_))), + maybe_format(HANDLER(prepend_format_)), + HANDLER(prepend_width_).value.to_long()), *this, "#cleared")); } - else if (is_eq(p, "convert")) + else if (is_eq(p, "convert")) { return WRAP_FUNCTOR(convert_command); + } + else if (is_eq(p, "commodities")) { + return WRAP_FUNCTOR(reporter<>(new report_commodities(*this), *this, + "#commodities")); + } break; case 'e': @@ -1288,14 +1391,19 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return expr_t::op_t::wrap_functor (reporter<post_t, post_handler_ptr, &report_t::commodities_report> (new format_posts(*this, report_format(HANDLER(prices_format_)), - maybe_format(HANDLER(prepend_format_))), + maybe_format(HANDLER(prepend_format_)), + HANDLER(prepend_width_).value.to_long()), *this, "#prices")); else if (is_eq(p, "pricedb")) return expr_t::op_t::wrap_functor (reporter<post_t, post_handler_ptr, &report_t::commodities_report> (new format_posts(*this, report_format(HANDLER(pricedb_format_)), - maybe_format(HANDLER(prepend_format_))), + maybe_format(HANDLER(prepend_format_)), + HANDLER(prepend_width_).value.to_long()), *this, "#pricedb")); + else if (is_eq(p, "payees")) + return WRAP_FUNCTOR(reporter<>(new report_payees(*this), *this, + "#payees")); break; case 'r': @@ -1303,7 +1411,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return WRAP_FUNCTOR (reporter<> (new format_posts(*this, report_format(HANDLER(register_format_)), - maybe_format(HANDLER(prepend_format_))), + maybe_format(HANDLER(prepend_format_)), + HANDLER(prepend_width_).value.to_long()), *this, "#register")); else if (is_eq(p, "reload")) return MAKE_FUNCTOR(report_t::reload_command); diff --git a/src/report.h b/src/report.h index 6b10dbcc..6fa238f0 100644 --- a/src/report.h +++ b/src/report.h @@ -256,6 +256,8 @@ public: HANDLER(forecast_years_).report(out); HANDLER(format_).report(out); HANDLER(gain).report(out); + HANDLER(group_by_).report(out); + HANDLER(group_title_format_).report(out); HANDLER(head_).report(out); HANDLER(invert).report(out); HANDLER(limit_).report(out); @@ -267,6 +269,8 @@ public: HANDLER(market).report(out); HANDLER(meta_).report(out); HANDLER(monthly).report(out); + HANDLER(no_rounding).report(out); + HANDLER(no_titles).report(out); HANDLER(no_total).report(out); HANDLER(now_).report(out); HANDLER(only_).report(out); @@ -280,6 +284,7 @@ public: HANDLER(plot_amount_format_).report(out); HANDLER(plot_total_format_).report(out); HANDLER(prepend_format_).report(out); + HANDLER(prepend_width_).report(out); HANDLER(price).report(out); HANDLER(prices_format_).report(out); HANDLER(pricedb_format_).report(out); @@ -372,7 +377,7 @@ public: OPTION__(report_t, balance_format_, CTOR(report_t, balance_format_) { on(none, - "%(justify(scrub(display_total), 20, -1, true, color))" + "%(justify(scrub(display_total), 20, 20 + prepend_width, true, color))" " %(!options.flat ? depth_spacer : \"\")" "%-(ansify_if(partial_account(options.flat), blue if color))\n%/" "%$1\n%/" @@ -432,8 +437,9 @@ public: OPTION__(report_t, cleared_format_, CTOR(report_t, cleared_format_) { on(none, - "%(justify(scrub(get_at(total_expr, 0)), 16, -1, true, color))" - " %(justify(scrub(get_at(total_expr, 1)), 16, -1, true, color))" + "%(justify(scrub(get_at(total_expr, 0)), 16, 16 + prepend_width, " + " true, color)) %(justify(scrub(get_at(total_expr, 1)), 18, " + " 36 + prepend_width, true, color))" " %(latest_cleared ? format_date(latest_cleared) : \" \")" " %(!options.flat ? depth_spacer : \"\")" "%-(ansify_if(partial_account(options.flat), blue if color))\n%/" @@ -454,15 +460,17 @@ public: }); OPTION(report_t, columns_); + OPTION(report_t, count); OPTION__(report_t, csv_format_, CTOR(report_t, csv_format_) { on(none, "%(quoted(date))," + "%(quoted(code))," "%(quoted(payee))," - "%(quoted(account))," - "%(quoted(scrub(display_amount)))," + "%(quoted(display_account))," + "%(quoted(commodity))," + "%(quoted(quantity(scrub(display_amount))))," "%(quoted(cleared ? \"*\" : (pending ? \"!\" : \"\")))," - "%(quoted(code))," "%(quoted(join(note | xact.note)))\n"); }); @@ -590,6 +598,22 @@ public: " - get_at(total_expr, 1)"); }); + OPTION__ + (report_t, group_by_, + expr_t expr; + CTOR(report_t, group_by_) {} + void set_expr(const optional<string>& whence, const string& str) { + expr = str; + on(whence, str); + } + DO_(args) { + set_expr(args[0].to_string(), args[1].to_string()); + }); + + OPTION__(report_t, group_title_format_, CTOR(report_t, group_title_format_) { + on(none, "%(value)\n"); + }); + OPTION(report_t, head_); OPTION_(report_t, invert, DO() { @@ -632,6 +656,8 @@ public: parent->HANDLER(color).off(); }); + OPTION(report_t, no_rounding); + OPTION(report_t, no_titles); OPTION(report_t, no_total); OPTION_(report_t, now_, DO_(args) { @@ -735,6 +761,9 @@ public: }); OPTION(report_t, prepend_format_); + OPTION_(report_t, prepend_width_, DO_(args) { + value = args[1].to_long(); + }); OPTION_(report_t, price, DO() { // -I parent->HANDLER(display_amount_) @@ -745,13 +774,13 @@ public: OPTION__(report_t, prices_format_, CTOR(report_t, prices_format_) { on(none, - "%(date) %-8(account) %(justify(scrub(display_amount), 12, " + "%(date) %-8(display_account) %(justify(scrub(display_amount), 12, " " 2 + 9 + 8 + 12, true, color))\n"); }); OPTION__(report_t, pricedb_format_, CTOR(report_t, pricedb_format_) { on(none, - "P %(datetime) %(account) %(scrub(display_amount))\n"); + "P %(datetime) %(display_account) %(scrub(display_amount))\n"); }); OPTION(report_t, print_virtual); @@ -778,14 +807,14 @@ public: " if color & date > today))" " %(ansify_if(justify(truncated(payee, payee_width), payee_width), " " bold if color & !cleared & actual))" - " %(ansify_if(justify(truncated(account, account_width, abbrev_len), " - " account_width), blue if color))" + " %(ansify_if(justify(truncated(display_account, account_width, " + " abbrev_len), account_width), blue if color))" " %(justify(scrub(display_amount), amount_width, " " 3 + meta_width + date_width + payee_width + account_width" - " + amount_width, true, color))" + " + amount_width + prepend_width, true, color))" " %(justify(scrub(display_total), total_width, " " 4 + meta_width + date_width + payee_width + account_width" - " + amount_width + total_width, true, color))\n%/" + " + amount_width + total_width + prepend_width, true, color))\n%/" "%(justify(\" \", 2 + date_width + payee_width))" "%$3 %$4 %$5\n"); }); diff --git a/src/scope.h b/src/scope.h index 30ba6823..1e6f24a1 100644 --- a/src/scope.h +++ b/src/scope.h @@ -354,6 +354,30 @@ inline T& find_scope(child_scope_t& scope, bool skip_this = true) return reinterpret_cast<T&>(scope); // never executed } +class value_scope_t : public scope_t +{ + value_t value; + + value_t get_value(call_scope_t&) { + return value; + } + +public: + value_scope_t(const value_t& _value) : value(_value) {} + + virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind, + const string& name) + { + if (kind != symbol_t::FUNCTION) + return NULL; + + if (name == "value") + return MAKE_FUNCTOR(value_scope_t::get_value); + + return NULL; + } +}; + } // namespace ledger #endif // _SCOPE_H diff --git a/src/session.cc b/src/session.cc index 8e5536b0..f8befde4 100644 --- a/src/session.cc +++ b/src/session.cc @@ -196,9 +196,7 @@ option_t<session_t> * session_t::lookup_option(const char * p) break; case 'd': OPT(download); // -Q - break; - case 'e': - OPT(european); + else OPT(decimal_comma); break; case 'f': OPT_(file_); // -f diff --git a/src/session.h b/src/session.h index de1771ad..10f636bb 100644 --- a/src/session.h +++ b/src/session.h @@ -80,7 +80,7 @@ public: { HANDLER(cache_).report(out); HANDLER(download).report(out); - HANDLER(european).report(out); + HANDLER(decimal_comma).report(out); HANDLER(file_).report(out); HANDLER(input_date_format_).report(out); HANDLER(master_account_).report(out); @@ -101,8 +101,8 @@ public: OPTION(session_t, cache_); OPTION(session_t, download); // -Q - OPTION_(session_t, european, DO() { - commodity_t::european_by_default = true; + OPTION_(session_t, decimal_comma, DO() { + commodity_t::decimal_comma_by_default = true; }); OPTION__ diff --git a/src/temps.cc b/src/temps.cc index 68b9ffa0..7a630176 100644 --- a/src/temps.cc +++ b/src/temps.cc @@ -38,26 +38,6 @@ namespace ledger { -temporaries_t::~temporaries_t() -{ - if (post_temps) { - foreach (post_t& post, *post_temps) { - if (! post.xact->has_flags(ITEM_TEMP)) - post.xact->remove_post(&post); - - if (post.account && ! post.account->has_flags(ACCOUNT_TEMP)) - post.account->remove_post(&post); - } - } - - if (acct_temps) { - foreach (account_t& acct, *acct_temps) { - if (acct.parent && ! acct.parent->has_flags(ACCOUNT_TEMP)) - acct.parent->remove_account(&acct); - } - } -} - xact_t& temporaries_t::copy_xact(xact_t& origin) { if (! xact_temps) @@ -91,9 +71,9 @@ post_t& temporaries_t::copy_post(post_t& origin, xact_t& xact, post_temps->push_back(origin); post_t& temp(post_temps->back()); + temp.add_flags(ITEM_TEMP); if (account) temp.account = account; - temp.add_flags(ITEM_TEMP); temp.account->add_post(&temp); xact.add_post(&temp); @@ -109,8 +89,8 @@ post_t& temporaries_t::create_post(xact_t& xact, account_t * account) post_temps->push_back(post_t(account)); post_t& temp(post_temps->back()); - temp.account = account; temp.add_flags(ITEM_TEMP); + temp.account = account; temp.account->add_post(&temp); xact.add_post(&temp); @@ -127,11 +107,36 @@ account_t& temporaries_t::create_account(const string& name, acct_temps->push_back(account_t(parent, name)); account_t& temp(acct_temps->back()); + temp.add_flags(ACCOUNT_TEMP); if (parent) parent->add_account(&temp); - temp.add_flags(ACCOUNT_TEMP); return temp; } +void temporaries_t::clear() +{ + if (post_temps) { + foreach (post_t& post, *post_temps) { + if (! post.xact->has_flags(ITEM_TEMP)) + post.xact->remove_post(&post); + + if (post.account && ! post.account->has_flags(ACCOUNT_TEMP)) + post.account->remove_post(&post); + } + post_temps->clear(); + } + + if (xact_temps) + xact_temps->clear(); + + if (acct_temps) { + foreach (account_t& acct, *acct_temps) { + if (acct.parent && ! acct.parent->has_flags(ACCOUNT_TEMP)) + acct.parent->remove_account(&acct); + } + acct_temps->clear(); + } +} + } // namespace ledger diff --git a/src/temps.h b/src/temps.h index ac6d08cd..210bbf63 100644 --- a/src/temps.h +++ b/src/temps.h @@ -51,7 +51,9 @@ class temporaries_t optional<std::list<account_t> > acct_temps; public: - ~temporaries_t(); + ~temporaries_t() { + clear(); + } xact_t& copy_xact(xact_t& origin); xact_t& create_xact(); @@ -69,6 +71,8 @@ public: account_t& last_account() { return acct_temps->back(); } + + void clear(); }; } // namespace ledger diff --git a/src/textual.cc b/src/textual.cc index dfca7943..9a49edd4 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -95,7 +95,6 @@ namespace { public: parse_context_t& context; instance_t * parent; - account_t * master; accounts_map account_aliases; const path * original_file; path pathname; @@ -109,7 +108,6 @@ namespace { instance_t(parse_context_t& _context, std::istream& _in, - account_t * _master = NULL, const path * _original_file = NULL, instance_t * _parent = NULL); @@ -200,30 +198,17 @@ namespace { instance_t::instance_t(parse_context_t& _context, std::istream& _in, - account_t * _master, const path * _original_file, instance_t * _parent) - : context(_context), parent(_parent), master(_master), - original_file(_original_file), in(_in) + : context(_context), parent(_parent), original_file(_original_file), + pathname(original_file ? *original_file : "/dev/stdin"), in(_in) { TRACE_CTOR(instance_t, "..."); - - if (! master) - master = context.journal.master; - context.state_stack.push_front(master); - - if (_original_file) - pathname = *_original_file; - else - pathname = "/dev/stdin"; } instance_t::~instance_t() { TRACE_DTOR(instance_t); - - assert(! context.state_stack.empty()); - context.state_stack.pop_front(); } void instance_t::parse() @@ -411,8 +396,7 @@ void instance_t::read_next_directive() #if defined(TIMELOG_SUPPORT) -void instance_t::clock_in_directive(char * line, - bool /*capitalized*/) +void instance_t::clock_in_directive(char * line, bool /*capitalized*/) { string datetime(line, 2, 19); @@ -441,8 +425,7 @@ void instance_t::clock_in_directive(char * line, context.timelog.clock_in(event); } -void instance_t::clock_out_directive(char * line, - bool /*capitalized*/) +void instance_t::clock_out_directive(char * line, bool /*capitalized*/) { string datetime(line, 2, 19); @@ -543,7 +526,7 @@ void instance_t::automated_xact_directive(char * line) std::auto_ptr<auto_xact_t> ae (new auto_xact_t(query_t(string(skip_ws(line + 1)), - keep_details_t(true, true, true)))); + keep_details_t(true, true, true), false))); ae->pos = position_t(); ae->pos->pathname = pathname; ae->pos->beg_pos = line_beg_pos; @@ -680,7 +663,12 @@ void instance_t::include_directive(char * line) for (filesystem::directory_iterator iter(parent_path); iter != end; ++iter) { - if (is_regular_file(*iter)) { +#if BOOST_VERSION <= 103500 + if (is_regular(*iter)) +#else + if (is_regular_file(*iter)) +#endif + { #if BOOST_VERSION >= 103700 string base = (*iter).filename(); #else // BOOST_VERSION >= 103700 @@ -689,7 +677,7 @@ void instance_t::include_directive(char * line) if (glob.match(base)) { path inner_file(*iter); ifstream stream(inner_file); - instance_t instance(context, stream, master, &inner_file, this); + instance_t instance(context, stream, &inner_file, this); instance.parse(); files_found = true; } @@ -713,7 +701,7 @@ void instance_t::master_account_directive(char * line) void instance_t::end_directive(char * kind) { - string name(kind); + string name(kind ? kind : ""); if ((name.empty() || name == "account") && ! context.front_is_account()) throw_(std::runtime_error, @@ -1451,8 +1439,10 @@ std::size_t journal_t::parse(std::istream& in, parse_context_t context(*this, scope); context.strict = strict; + if (master || this->master) + context.state_stack.push_front(master ? master : this->master); - instance_t instance(context, in, master, original_file); + instance_t instance(context, in, original_file); instance.parse(); TRACE_STOP(parsing_total, 1); diff --git a/src/times.cc b/src/times.cc index be488baf..a7906aee 100644 --- a/src/times.cc +++ b/src/times.cc @@ -197,25 +197,33 @@ namespace { optional_year year, date_traits_t * traits = NULL) { - date_t when; + VERIFY(std::strlen(date_str) < 127); - if (std::strchr(date_str, '/')) { - when = io.parse(date_str); - } else { - char buf[128]; - VERIFY(std::strlen(date_str) < 127); - std::strcpy(buf, date_str); + char buf[128]; + std::strcpy(buf, date_str); - for (char * p = buf; *p; p++) - if (*p == '.' || *p == '-') - *p = '/'; + for (char * p = buf; *p; p++) + if (*p == '.' || *p == '-') + *p = '/'; - when = io.parse(buf); - } + date_t when = io.parse(buf); if (! when.is_not_a_date()) { - DEBUG("times.parse", "Parsed date string: " << date_str); - DEBUG("times.parse", "Parsed result is: " << when); + DEBUG("times.parse", "Passed date string: " << date_str); + DEBUG("times.parse", "Parsed date string: " << buf); + DEBUG("times.parse", "Parsed result is: " << when); + DEBUG("times.parse", "Formatted result is: " << io.format(when)); + + string when_str = io.format(when); + + const char * p = when_str.c_str(); + const char * q = buf; + for (; *p && *q; p++, q++) { + if (*p != *q && *p == '0') p++; + if (! *p || *p != *q) break; + } + if (*p != '\0' || *q != '\0') + throw_(date_error, _("Invalid date: %1") << date_str); if (traits) *traits = io.traits; @@ -1299,14 +1307,14 @@ date_parser_t::lexer_t::token_t date_parser_t::lexer_t::next_token() // "2009/08/01", but also dates that fit the user's --input-date-format, // assuming their format fits in one argument and begins with a digit. if (std::isdigit(*begin)) { - try { - string::const_iterator i = begin; - for (i = begin; i != end && ! std::isspace(*i); i++) {} - assert(i != begin); + string::const_iterator i = begin; + for (i = begin; i != end && ! std::isspace(*i); i++) {} + assert(i != begin); - string possible_date(start, i); - date_traits_t traits; + string possible_date(start, i); + try { + date_traits_t traits; date_t when = parse_date_mask(possible_date.c_str(), none, &traits); if (! when.is_not_a_date()) { begin = i; @@ -1314,7 +1322,12 @@ date_parser_t::lexer_t::token_t date_parser_t::lexer_t::next_token() token_t::content_t(date_specifier_t(when, traits))); } } - catch (...) {} + catch (date_error&) { + if (contains(possible_date, "/") || + contains(possible_date, "-") || + contains(possible_date, ".")) + throw; + } } start = begin; @@ -1457,7 +1470,7 @@ std::string format_datetime(const datetime_t& when, if (format_type == FMT_WRITTEN) { return written_datetime_io->format(when); } - else if (format_type == FMT_CUSTOM || format) { + else if (format_type == FMT_CUSTOM && format) { datetime_io_map::iterator i = temp_datetime_io.find(*format); if (i != temp_datetime_io.end()) { return (*i).second->format(when); @@ -1483,7 +1496,7 @@ std::string format_date(const date_t& when, if (format_type == FMT_WRITTEN) { return written_date_io->format(when); } - else if (format_type == FMT_CUSTOM || format) { + else if (format_type == FMT_CUSTOM && format) { date_io_map::iterator i = temp_date_io.find(*format); if (i != temp_date_io.end()) { return (*i).second->format(when); diff --git a/src/token.cc b/src/token.cc index a34cdcd0..add97b8b 100644 --- a/src/token.cc +++ b/src/token.cc @@ -138,7 +138,8 @@ void expr_t::token_t::parse_ident(std::istream& in) value.set_string(buf); } -void expr_t::token_t::next(std::istream& in, const parse_flags_t& pflags) +void expr_t::token_t::next(std::istream& in, const parse_flags_t& pflags, + const char expecting) { if (in.eof()) { kind = TOK_EOF; @@ -423,6 +424,13 @@ void expr_t::token_t::next(std::istream& in, const parse_flags_t& pflags) expected('\0', c); parse_ident(in); + + if (value.as_string().length() == 0) { + kind = ERROR; + symbol[0] = c; + symbol[1] = '\0'; + unexpected(expecting); + } } else { kind = VALUE; value = temp; @@ -447,21 +455,38 @@ void expr_t::token_t::rewind(std::istream& in) } -void expr_t::token_t::unexpected() +void expr_t::token_t::unexpected(const char wanted) { kind_t prev_kind = kind; kind = ERROR; - switch (prev_kind) { - case TOK_EOF: - throw_(parse_error, _("Unexpected end of expression")); - case IDENT: - throw_(parse_error, _("Unexpected symbol '%1'") << value); - case VALUE: - throw_(parse_error, _("Unexpected value '%1'") << value); - default: - throw_(parse_error, _("Unexpected token '%1'") << symbol); + if (wanted == '\0') { + switch (prev_kind) { + case TOK_EOF: + throw_(parse_error, _("Unexpected end of expression")); + case IDENT: + throw_(parse_error, _("Unexpected symbol '%1'") << value); + case VALUE: + throw_(parse_error, _("Unexpected value '%1'") << value); + default: + throw_(parse_error, _("Unexpected expression token '%1'") << symbol); + } + } else { + switch (prev_kind) { + case TOK_EOF: + throw_(parse_error, + _("Unexpected end of expression (wanted '%1')" << wanted)); + case IDENT: + throw_(parse_error, + _("Unexpected symbol '%1' (wanted '%2')") << value << wanted); + case VALUE: + throw_(parse_error, + _("Unexpected value '%1' (wanted '%2')") << value << wanted); + default: + throw_(parse_error, _("Unexpected expression token '%1' (wanted '%2')") + << symbol << wanted); + } } } diff --git a/src/token.h b/src/token.h index 8d70996b..582373cb 100644 --- a/src/token.h +++ b/src/token.h @@ -124,10 +124,11 @@ struct expr_t::token_t : public noncopyable int parse_reserved_word(std::istream& in); void parse_ident(std::istream& in); - void next(std::istream& in, const parse_flags_t& flags); + void next(std::istream& in, const parse_flags_t& flags, + const char expecting = '\0'); void rewind(std::istream& in); - void unexpected(); - void expected(char wanted, char c = '\0'); + void unexpected(const char wanted = '\0'); + void expected(const char wanted, char c = '\0'); }; } // namespace ledger diff --git a/src/value.cc b/src/value.cc index ce9b0e6a..a967eeb8 100644 --- a/src/value.cc +++ b/src/value.cc @@ -832,7 +832,7 @@ bool value_t::is_equal_to(const value_t& val) const break; } - throw_(value_error, _("Cannot compare %1 by %2") << label() << val.label()); + throw_(value_error, _("Cannot compare %1 to %2") << label() << val.label()); return *this; } @@ -840,6 +840,23 @@ bool value_t::is_equal_to(const value_t& val) const bool value_t::is_less_than(const value_t& val) const { switch (type()) { + case BOOLEAN: + if (val.is_boolean()) { + if (as_boolean()) { + if (! val.as_boolean()) + return false; + else + return false; + } + else if (! as_boolean()) { + if (! val.as_boolean()) + return false; + else + return true; + } + } + break; + case DATETIME: if (val.is_datetime()) return as_datetime() < val.as_datetime(); @@ -935,6 +952,22 @@ bool value_t::is_less_than(const value_t& val) const bool value_t::is_greater_than(const value_t& val) const { switch (type()) { + if (val.is_boolean()) { + if (as_boolean()) { + if (! val.as_boolean()) + return true; + else + return false; + } + else if (! as_boolean()) { + if (! val.as_boolean()) + return false; + else + return false; + } + } + break; + case DATETIME: if (val.is_datetime()) return as_datetime() > val.as_datetime(); @@ -1042,8 +1075,27 @@ void value_t::in_place_cast(type_t cast_type) } switch (type()) { + case VOID: + switch (cast_type) { + case INTEGER: + set_long(0L); + return; + case AMOUNT: + set_amount(0L); + return; + case STRING: + set_string(""); + return; + default: + break; + } + break; + case BOOLEAN: switch (cast_type) { + case INTEGER: + set_long(as_boolean() ? 1L : 0L); + return; case AMOUNT: set_amount(as_boolean() ? 1L : 0L); return; diff --git a/src/xact.cc b/src/xact.cc index 344f66ea..569e5869 100644 --- a/src/xact.cc +++ b/src/xact.cc @@ -172,44 +172,8 @@ bool xact_base_t::finalize() add_post(null_post); } - if (null_post != NULL) { - // If one post has no value at all, its value will become the inverse of - // the rest. If multiple commodities are involved, multiple posts are - // generated to balance them all. - - DEBUG("xact.finalize", "there was a null posting"); - - if (balance.is_balance()) { - bool first = true; - const balance_t& bal(balance.as_balance()); - foreach (const balance_t::amounts_map::value_type& pair, bal.amounts) { - if (first) { - null_post->amount = pair.second.negated(); - null_post->add_flags(POST_CALCULATED); - first = false; - } else { - post_t * p = new post_t(null_post->account, pair.second.negated(), - ITEM_GENERATED | POST_CALCULATED); - p->set_state(null_post->state()); - add_post(p); - } - } - } - else if (balance.is_amount()) { - null_post->amount = balance.as_amount().negated(); - null_post->add_flags(POST_CALCULATED); - } - else if (balance.is_long()) { - null_post->amount = amount_t(- balance.as_long()); - null_post->add_flags(POST_CALCULATED); - } - else if (! balance.is_null() && ! balance.is_realzero()) { - throw_(balance_error, _("Transaction does not balance")); - } - balance = NULL_VALUE; - } - else if (balance.is_balance() && - balance.as_balance().amounts.size() == 2) { + if (balance.is_balance() && + balance.as_balance().amounts.size() == 2) { // When an xact involves two different commodities (regardless of how // many posts there are) determine the conversion ratio by dividing the // total value of one commodity by the total value of the other. This @@ -293,12 +257,6 @@ bool xact_base_t::finalize() } } - // Now that the post list has its final form, calculate the balance once - // more in terms of total cost, accounting for any possible gain/loss - // amounts. - - DEBUG("xact.finalize", "resolved balance = " << balance); - posts_list copy(posts); foreach (post_t * post, copy) { @@ -340,7 +298,44 @@ bool xact_base_t::finalize() } } - DEBUG("xact.finalize", "final balance = " << balance); + if (null_post != NULL) { + // If one post has no value at all, its value will become the inverse of + // the rest. If multiple commodities are involved, multiple posts are + // generated to balance them all. + + DEBUG("xact.finalize", "there was a null posting"); + + if (balance.is_balance()) { + bool first = true; + const balance_t& bal(balance.as_balance()); + foreach (const balance_t::amounts_map::value_type& pair, bal.amounts) { + if (first) { + null_post->amount = pair.second.negated(); + null_post->add_flags(POST_CALCULATED); + first = false; + } else { + post_t * p = new post_t(null_post->account, pair.second.negated(), + ITEM_GENERATED | POST_CALCULATED); + p->set_state(null_post->state()); + add_post(p); + } + } + } + else if (balance.is_amount()) { + null_post->amount = balance.as_amount().negated(); + null_post->add_flags(POST_CALCULATED); + } + else if (balance.is_long()) { + null_post->amount = amount_t(- balance.as_long()); + null_post->add_flags(POST_CALCULATED); + } + else if (! balance.is_null() && ! balance.is_realzero()) { + throw_(balance_error, _("Transaction does not balance")); + } + balance = NULL_VALUE; + + } + DEBUG("xact.finalize", "resolved balance = " << balance); if (! balance.is_null() && ! balance.is_zero()) { add_error_context(item_context(*this, _("While balancing transaction"))); @@ -473,7 +468,7 @@ namespace { if (xact.code) return string_value(*xact.code); else - return string_value(empty_string); + return NULL_VALUE; } value_t get_payee(xact_t& xact) { @@ -716,8 +711,14 @@ void auto_xact_t::extend_xact(xact_base_t& xact) account_t * account = post->account; string fullname = account->fullname(); assert(! fullname.empty()); - if (fullname == "$account" || fullname == "@account") - account = initial_post->account; + + if (contains(fullname, "$account")) { + fullname = regex_replace(fullname, regex("\\$account\\>"), + initial_post->account->fullname()); + while (account->parent) + account = account->parent; + account = account->find_account(fullname); + } // Copy over details so that the resulting post is a mirror of // the automated xact's one. @@ -83,6 +83,14 @@ public: virtual void flush(); virtual void operator()(post_t& post); + + virtual void clear() { + commodities.clear(); + transactions_set.clear(); + transactions.clear(); + + item_handler<post_t>::clear(); + } }; } // namespace ledger |