From de3803d0277353520116f05c7b2357196a8cfe48 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 22 May 2010 15:40:38 -0400 Subject: Added new commands: acounts, payees, commodities These three reports simply dump an unordered list (with the exception of payees) shows all accounts, payees, and commodities represented in a given report. This can be used to easily generate per-entity report, for example: ledger payees | \ while read payee; do \ echo ; echo $payee ; \ ledger reg payee "$payee" ; \ done --- src/output.cc | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) (limited to 'src/output.cc') diff --git a/src/output.cc b/src/output.cc index 30775310..e3aa9f4a 100644 --- a/src/output.cc +++ b/src/output.cc @@ -232,4 +232,70 @@ 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) + out << *entry.first << '\n'; +} + +void report_accounts::operator()(post_t& post) +{ + std::map::iterator i = accounts.find(post.account); + if (i == accounts.end()) + accounts.insert(accounts_pair(post.account, true)); +} + +void report_payees::flush() +{ + std::ostream& out(report.output_stream); + + foreach (payees_pair& entry, payees) + out << entry.first << '\n'; +} + +void report_payees::operator()(post_t& post) +{ + std::map::iterator i = payees.find(post.xact->payee); + if (i == payees.end()) + payees.insert(payees_pair(post.xact->payee, true)); +} + +void report_commodities::flush() +{ + std::ostream& out(report.output_stream); + + foreach (commodities_pair& entry, commodities) + 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::iterator i = commodities.find(&comm); + if (i == commodities.end()) + commodities.insert(commodities_pair(&comm, true)); + + if (comm.has_annotation()) { + annotated_commodity_t& ann_comm(as_annotated_commodity(comm)); + if (ann_comm.details.price) { + std::map::iterator i = + commodities.find(&ann_comm.details.price->commodity()); + if (i == commodities.end()) + commodities.insert + (commodities_pair(&ann_comm.details.price->commodity(), true)); + } + } + + 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(), true)); + } +} + } // namespace ledger -- cgit v1.2.3 From 02e78255166b17bff08a408166cc56ff9269c925 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 22 May 2010 17:04:38 -0400 Subject: Option --count sums payees, account, commodities --- src/output.cc | 43 +++++++++++++++++++++++++++++++------------ src/output.h | 12 ++++++------ src/report.cc | 1 + src/report.h | 1 + 4 files changed, 39 insertions(+), 18 deletions(-) (limited to 'src/output.cc') diff --git a/src/output.cc b/src/output.cc index e3aa9f4a..ec1faba6 100644 --- a/src/output.cc +++ b/src/output.cc @@ -236,38 +236,51 @@ void report_accounts::flush() { std::ostream& out(report.output_stream); - foreach (accounts_pair& entry, accounts) + 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::iterator i = accounts.find(post.account); + std::map::iterator i = accounts.find(post.account); if (i == accounts.end()) - accounts.insert(accounts_pair(post.account, true)); + 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) + 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::iterator i = payees.find(post.xact->payee); + std::map::iterator i = payees.find(post.xact->payee); if (i == payees.end()) - payees.insert(payees_pair(post.xact->payee, true)); + 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) + foreach (commodities_pair& entry, commodities) { + if (report.HANDLED(count)) + out << entry.second << ' '; out << *entry.first << '\n'; + } } void report_commodities::operator()(post_t& post) @@ -275,18 +288,22 @@ 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::iterator i = commodities.find(&comm); + std::map::iterator i = commodities.find(&comm); if (i == commodities.end()) - commodities.insert(commodities_pair(&comm, true)); + 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::iterator i = + std::map::iterator i = commodities.find(&ann_comm.details.price->commodity()); if (i == commodities.end()) commodities.insert - (commodities_pair(&ann_comm.details.price->commodity(), true)); + (commodities_pair(&ann_comm.details.price->commodity(), 1)); + else + (*i).second++; } } @@ -294,7 +311,9 @@ void report_commodities::operator()(post_t& post) 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(), true)); + commodities.insert(commodities_pair(&temp_cost.commodity(), 1)); + else + (*i).second++; } } diff --git a/src/output.h b/src/output.h index 3e70d9fe..3a7b68df 100644 --- a/src/output.h +++ b/src/output.h @@ -108,9 +108,9 @@ class report_accounts : public item_handler protected: report_t& report; - std::map accounts; + std::map accounts; - typedef std::map::value_type accounts_pair; + typedef std::map::value_type accounts_pair; public: report_accounts(report_t& _report) : report(_report) { @@ -129,9 +129,9 @@ class report_payees : public item_handler protected: report_t& report; - std::map payees; + std::map payees; - typedef std::map::value_type payees_pair; + typedef std::map::value_type payees_pair; public: report_payees(report_t& _report) : report(_report) { @@ -150,9 +150,9 @@ class report_commodities : public item_handler protected: report_t& report; - std::map commodities; + std::map commodities; - typedef std::map::value_type commodities_pair; + typedef std::map::value_type commodities_pair; public: report_commodities(report_t& _report) : report(_report) { diff --git a/src/report.cc b/src/report.cc index 509be8b1..d2db87b0 100644 --- a/src/report.cc +++ b/src/report.cc @@ -853,6 +853,7 @@ option_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); diff --git a/src/report.h b/src/report.h index 6b10dbcc..783f0026 100644 --- a/src/report.h +++ b/src/report.h @@ -454,6 +454,7 @@ public: }); OPTION(report_t, columns_); + OPTION(report_t, count); OPTION__(report_t, csv_format_, CTOR(report_t, csv_format_) { on(none, -- cgit v1.2.3 From a7c28aa20057525a9247d0ae69eb063b53b21811 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 22 May 2010 19:17:13 -0400 Subject: Added new option --prepend-width This is useful for making sure that the column containing the results of --prepend-format is a consistent width throughout the report (including those lines where it is not applied). Fixes 64F9D913-75E1-4830-A3D9-29B72442E68B --- src/output.cc | 23 ++++++++++++++++------- src/output.h | 22 +++++++++++++--------- src/report.cc | 24 +++++++++++++++++------- src/report.h | 15 ++++++++++----- 4 files changed, 56 insertions(+), 28 deletions(-) (limited to 'src/output.cc') diff --git a/src/output.cc b/src/output.cc index ec1faba6..183d80b3 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& _prepend_format) - : report(_report), last_xact(NULL), last_post(NULL) + const optional& _prepend_format, + std::size_t _prepend_width) + : report(_report), prepend_width(_prepend_width), + last_xact(NULL), last_post(NULL) { TRACE_CTOR(format_posts, "report&, const string&, bool"); @@ -82,8 +84,10 @@ void format_posts::operator()(post_t& post) ! post.xdata().has_flags(POST_EXT_DISPLAYED)) { bind_scope_t bound_scope(report, post); - if (prepend_format) + if (prepend_format) { + out.width(prepend_width); out << prepend_format(bound_scope); + } if (last_xact != post.xact) { if (last_xact) { @@ -107,8 +111,9 @@ void format_posts::operator()(post_t& post) format_accounts::format_accounts(report_t& _report, const string& format, - const optional& _prepend_format) - : report(_report), disp_pred() + const optional& _prepend_format, + std::size_t _prepend_width) + : report(_report), prepend_width(_prepend_width), disp_pred() { TRACE_CTOR(format_accounts, "report&, const string&"); @@ -144,9 +149,11 @@ std::size_t format_accounts::post_account(account_t& account, const bool flat) bind_scope_t bound_scope(report, account); - if (prepend_format) + if (prepend_format) { + static_cast(report.output_stream).width(prepend_width); static_cast(report.output_stream) << prepend_format(bound_scope); + } static_cast(report.output_stream) << account_line_format(bound_scope); @@ -216,9 +223,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(report.output_stream).width(prepend_width); static_cast(report.output_stream) << prepend_format(bound_scope); + } out << total_line_format(bound_scope); } diff --git a/src/output.h b/src/output.h index 3a7b68df..00c664c1 100644 --- a/src/output.h +++ b/src/output.h @@ -56,17 +56,19 @@ class report_t; class format_posts : public item_handler { 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; public: format_posts(report_t& _report, const string& format, - const optional& _prepend_format = none); + const optional& _prepend_format = none, + std::size_t _prepend_width = 0); virtual ~format_posts() { TRACE_DTOR(format_posts); } @@ -83,13 +85,15 @@ protected: format_t total_line_format; format_t separator_format; format_t prepend_format; + std::size_t prepend_width; predicate_t disp_pred; std::list posted_accounts; public: format_accounts(report_t& _report, const string& _format, - const optional& _prepend_format = none); + const optional& _prepend_format = none, + std::size_t _prepend_width = 0); virtual ~format_accounts() { TRACE_DTOR(format_accounts); } diff --git a/src/report.cc b/src/report.cc index d2db87b0..4c8f4060 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(0)); if (verb == "print" || verb == "xact" || verb == "dump") { HANDLER(related).on_only(string("?normalize")); @@ -937,6 +939,7 @@ option_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': @@ -1234,7 +1237,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return expr_t::op_t::wrap_functor (reporter (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")) { @@ -1247,7 +1251,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return expr_t::op_t::wrap_functor (reporter (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; @@ -1257,7 +1262,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")) { @@ -1266,7 +1272,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return expr_t::op_t::wrap_functor (reporter (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")) { @@ -1300,13 +1307,15 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return expr_t::op_t::wrap_functor (reporter (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 (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, @@ -1318,7 +1327,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 df2f3469..64c14858 100644 --- a/src/report.h +++ b/src/report.h @@ -280,6 +280,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 +373,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 +433,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%/" @@ -737,6 +739,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_) @@ -784,10 +789,10 @@ public: " 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"); }); -- cgit v1.2.3 From 647d4aac2fa474085d01f7ea1cebdc34fafd64a6 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sun, 30 May 2010 02:28:58 -0600 Subject: New: --group-by=EXPR and --group-title-format=FMT The --group-by option allows for most reports to be split up into sections based on the varying value of EXPR. For example, to see register subtotals by payee, use: ledger reg --group-by=payee -s This works for separated balances too: ledger bal --group-by=payee Another interesting possibility is seeing a register of all the accounts affected by a related account: ledger reg -r --group-by=payee The option --group-title-format can be used to add a separator bar to the group titles. The option --no-titles can be used to drop titles altogether. --- src/chain.cc | 126 +++++++++++++++++++++++++----------------------- src/chain.h | 14 ++++++ src/output.cc | 55 +++++++++++++++++---- src/output.h | 16 +++++++ src/print.cc | 12 ++++- src/print.h | 10 ++++ src/report.cc | 150 +++++++++++++++++++++++++++++++++++++++++++++------------- src/report.h | 18 +++++++ 8 files changed, 298 insertions(+), 103 deletions(-) (limited to 'src/output.cc') diff --git a/src/chain.cc b/src/chain.cc index 1103fe42..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 + (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) @@ -189,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 - (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 935f2935..59b04eb8 100644 --- a/src/chain.h +++ b/src/chain.h @@ -91,11 +91,25 @@ typedef shared_ptr > post_handler_ptr; typedef shared_ptr > 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/output.cc b/src/output.cc index 183d80b3..f697dee4 100644 --- a/src/output.cc +++ b/src/output.cc @@ -45,7 +45,7 @@ format_posts::format_posts(report_t& _report, const optional& _prepend_format, std::size_t _prepend_width) : report(_report), prepend_width(_prepend_width), - last_xact(NULL), last_post(NULL) + last_xact(NULL), last_post(NULL), first_report_title(true) { TRACE_CTOR(format_posts, "report&, const string&, bool"); @@ -78,12 +78,30 @@ 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 (! 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); @@ -113,7 +131,8 @@ format_accounts::format_accounts(report_t& _report, const string& format, const optional& _prepend_format, std::size_t _prepend_width) - : report(_report), prepend_width(_prepend_width), disp_pred() + : report(_report), prepend_width(_prepend_width), disp_pred(), + first_report_title(true) { TRACE_CTOR(format_accounts, "report&, const string&"); @@ -144,19 +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 (! 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) { - static_cast(report.output_stream).width(prepend_width); - static_cast(report.output_stream) - << prepend_format(bound_scope); + out.width(prepend_width); + out << prepend_format(bound_scope); } - static_cast(report.output_stream) - << account_line_format(bound_scope); + out << account_line_format(bound_scope); return 1; } diff --git a/src/output.h b/src/output.h index f0e7f9a5..a19c6235 100644 --- a/src/output.h +++ b/src/output.h @@ -64,6 +64,8 @@ protected: 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, @@ -73,6 +75,10 @@ public: TRACE_DTOR(format_posts); } + virtual void title(const string& str) { + report_title = str; + } + virtual void flush(); virtual void operator()(post_t& post); @@ -80,6 +86,8 @@ public: last_xact = NULL; last_post = NULL; + report_title = ""; + item_handler::clear(); } }; @@ -94,6 +102,8 @@ protected: format_t prepend_format; std::size_t prepend_width; predicate_t disp_pred; + bool first_report_title; + string report_title; std::list posted_accounts; @@ -108,6 +118,10 @@ public: std::pair 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(); @@ -117,6 +131,8 @@ public: disp_pred.mark_uncompiled(); posted_accounts.clear(); + report_title = ""; + item_handler::clear(); } }; diff --git a/src/print.cc b/src/print.cc index 5c46f4e7..a8aa5872 100644 --- a/src/print.cc +++ b/src/print.cc @@ -42,7 +42,7 @@ 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"); } @@ -221,6 +221,16 @@ namespace { } } +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::clear(); + } }; diff --git a/src/report.cc b/src/report.cc index f3186e0b..dcb56319 100644 --- a/src/report.cc +++ b/src/report.cc @@ -274,10 +274,35 @@ 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 + 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); + pass_down_posts(handler, walker); + session.journal->clear_xdata(); } @@ -285,66 +310,121 @@ 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(HANDLER(seed_).value.to_long()) : 0, HANDLED(head_) ? static_cast(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 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 + 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 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(); } @@ -888,6 +968,8 @@ option_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_); diff --git a/src/report.h b/src/report.h index 78dc2165..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); @@ -596,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& 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() { -- cgit v1.2.3