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.
This commit is contained in:
parent
a41d33fba3
commit
647d4aac2f
8 changed files with 299 additions and 104 deletions
126
src/chain.cc
126
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)
|
||||
|
|
@ -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<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;
|
||||
}
|
||||
|
||||
|
|
|
|||
14
src/chain.h
14
src/chain.h
|
|
@ -91,11 +91,25 @@ 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
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ format_posts::format_posts(report_t& _report,
|
|||
const optional<string>& _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<string>& _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 (prepend_format) {
|
||||
static_cast<std::ostream&>(report.output_stream).width(prepend_width);
|
||||
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());
|
||||
|
||||
out << group_title_format(inner_scope);
|
||||
|
||||
report_title = "";
|
||||
}
|
||||
|
||||
static_cast<std::ostream&>(report.output_stream)
|
||||
<< account_line_format(bound_scope);
|
||||
if (prepend_format) {
|
||||
out.width(prepend_width);
|
||||
out << prepend_format(bound_scope);
|
||||
}
|
||||
|
||||
out << account_line_format(bound_scope);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
|
|
|||
16
src/output.h
16
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<post_t>::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<account_t *> posted_accounts;
|
||||
|
||||
|
|
@ -108,6 +118,10 @@ 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();
|
||||
|
||||
|
|
@ -117,6 +131,8 @@ public:
|
|||
disp_pred.mark_uncompiled();
|
||||
posted_accounts.clear();
|
||||
|
||||
report_title = "";
|
||||
|
||||
item_handler<account_t>::clear();
|
||||
}
|
||||
};
|
||||
|
|
|
|||
12
src/print.cc
12
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);
|
||||
|
|
|
|||
10
src/print.h
10
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();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
150
src/report.cc
150
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<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);
|
||||
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<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());
|
||||
post_handler_ptr chain =
|
||||
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));
|
||||
|
||||
splitter->set_preflush_func(accounts_title_printer(*this, handler));
|
||||
splitter->set_postflush_func(accounts_flusher(*this, handler));
|
||||
|
||||
chain = post_handler_ptr(splitter.release());
|
||||
}
|
||||
chain = chain_pre_post_handlers(*this, chain);
|
||||
|
||||
// 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);
|
||||
journal_posts_iterator walker(*session.journal.get());
|
||||
pass_down_posts(chain, walker);
|
||||
|
||||
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();
|
||||
|
||||
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)));
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
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> * 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_);
|
||||
|
|
|
|||
18
src/report.h
18
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<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() {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue