/* * Copyright (c) 2003-2023, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of New Artisans LLC nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * @addtogroup report */ /** * @file report.h * @author John Wiegley * * @ingroup report */ #pragma once #include "expr.h" #include "query.h" #include "chain.h" #include "stream.h" #include "option.h" #include "commodity.h" #include "annotate.h" #include "session.h" #include "format.h" namespace ledger { class session_t; class xact_t; // These are the elements of any report: // // 1. Formatting string used for outputting the underlying ReportedType. // // 2. Handler object for the ReportedType. This is constructed using #1, or // else #1 is ignored completely. This handler object is also constructed // with the output stream that will be used during formatting. // // --- The details of #1 and #2 together represent the ItemHandler. // // 3. Mode of the report. Currently there are four modes: // // a. Posting or commodity iteration. In this mode, all the journal's // xacts, the postings of a specific xact, or all the journal's // commodities are walked. In the first two cases, it's the underlying // postings which are passed to #2; in the second case, each // commodity is passed to #2. // // b. Account iteration. This employs step 'a', but add a prologue and // epilogue to it. In the prologue it "sums" all account totals and // subtotals; in the epilogue it calls yet another handler whose job is // reporting (the handler used in 'a' is only for calculation). // // There is one variation on 'b' in which a "totals" line is also // displayed. // // c. Write journal. In this mode, a single function is called that output // the journal object as a textual file. #2 is used to print out each // posting in the journal. // // d. Dump binary file. This is just like 'c', except that it dumps out a // binary file and #2 is completely ignored. // // 4. For 'a' and 'b' in #3, there is a different iteration function called, // depending on whether we're iterating: // // a. The postings of an xact: walk_postings. // b. The xacts of a journal: walk_xacts. // c. The commodities of a journal: walk_commodities. // // 5. Finally, for the 'a' and 'b' reporting modes, there is a variant which // says that the formatter should be "flushed" after the entities are // iterated. This does not happen for the commodities iteration, however. class report_t : public scope_t { report_t(); public: session_t& session; output_stream_t output_stream; #define BUDGET_NO_BUDGET 0x00 #define BUDGET_BUDGETED 0x01 #define BUDGET_UNBUDGETED 0x02 #define BUDGET_WRAP_VALUES 0x04 datetime_t terminus; uint_least8_t budget_flags; explicit report_t(session_t& _session) : session(_session), terminus(CURRENT_TIME()), budget_flags(BUDGET_NO_BUDGET) { TRACE_CTOR(report_t, "session_t&"); } report_t(const report_t& report) : scope_t(report), session(report.session), output_stream(report.output_stream), terminus(report.terminus), budget_flags(report.budget_flags) { TRACE_CTOR(report_t, "copy"); } virtual ~report_t() { TRACE_DTOR(report_t); output_stream.close(); } void quick_close() { output_stream.close(); } virtual string description() { return _("current report"); } void normalize_options(const string& verb); void normalize_period(); void parse_query_args(const value_t& args, const string& whence); void posts_report(post_handler_ptr handler); void generate_report(post_handler_ptr handler); void xact_report(post_handler_ptr handler, xact_t& xact); void accounts_report(acct_handler_ptr handler); void commodities_report(post_handler_ptr handler); value_t display_value(const value_t& val); value_t fn_amount_expr(call_scope_t& scope); value_t fn_total_expr(call_scope_t& scope); value_t fn_display_amount(call_scope_t& scope); value_t fn_display_total(call_scope_t& scope); value_t fn_top_amount(call_scope_t& val); value_t fn_should_bold(call_scope_t& scope); value_t fn_market(call_scope_t& scope); value_t fn_get_at(call_scope_t& scope); value_t fn_is_seq(call_scope_t& scope); value_t fn_strip(call_scope_t& scope); value_t fn_trim(call_scope_t& scope); value_t fn_format(call_scope_t& scope); value_t fn_print(call_scope_t& scope); value_t fn_scrub(call_scope_t& scope); value_t fn_quantity(call_scope_t& scope); value_t fn_rounded(call_scope_t& scope); value_t fn_unrounded(call_scope_t& scope); value_t fn_truncated(call_scope_t& scope); value_t fn_floor(call_scope_t& scope); value_t fn_ceiling(call_scope_t& scope); value_t fn_clear_commodity(call_scope_t& scope); value_t fn_round(call_scope_t& scope); value_t fn_roundto(call_scope_t& scope); value_t fn_unround(call_scope_t& scope); value_t fn_abs(call_scope_t& scope); value_t fn_justify(call_scope_t& scope); value_t fn_quoted(call_scope_t& scope); value_t fn_quoted_rfc(call_scope_t& scope); value_t fn_join(call_scope_t& scope); value_t fn_format_date(call_scope_t& scope); value_t fn_format_datetime(call_scope_t& scope); value_t fn_ansify_if(call_scope_t& scope); value_t fn_percent(call_scope_t& scope); value_t fn_commodity(call_scope_t& scope); value_t fn_commodity_price(call_scope_t& scope); value_t fn_set_commodity_price(call_scope_t& scope); value_t fn_nail_down(call_scope_t& scope); value_t fn_lot_date(call_scope_t& scope); value_t fn_lot_price(call_scope_t& scope); value_t fn_lot_tag(call_scope_t& scope); value_t fn_to_boolean(call_scope_t& scope); value_t fn_to_int(call_scope_t& scope); value_t fn_to_datetime(call_scope_t& scope); value_t fn_to_date(call_scope_t& scope); value_t fn_to_amount(call_scope_t& scope); value_t fn_to_balance(call_scope_t& scope); value_t fn_to_string(call_scope_t& scope); value_t fn_to_mask(call_scope_t& scope); value_t fn_to_sequence(call_scope_t& scope); value_t fn_averaged_lots(call_scope_t& scope); value_t fn_now(call_scope_t&) { return terminus; } value_t fn_today(call_scope_t&) { return terminus.date(); } value_t fn_options(call_scope_t&) { return scope_value(this); } string report_format(option_t& option) { if (HANDLED(format_)) return HANDLER(format_).str(); return option.str(); } optional maybe_format(option_t& option) { if (option) return option.str(); return none; } value_t reload_command(call_scope_t&); value_t echo_command(call_scope_t& scope); value_t pricemap_command(call_scope_t& scope); keep_details_t what_to_keep() { bool lots = HANDLED(lots) || HANDLED(lots_actual); return keep_details_t(lots || HANDLED(lot_prices), lots || HANDLED(lot_dates), lots || HANDLED(lot_notes), HANDLED(lots_actual)); } void report_options(std::ostream& out) { HANDLER(abbrev_len_).report(out); HANDLER(account_).report(out); HANDLER(actual).report(out); HANDLER(add_budget).report(out); HANDLER(amount_).report(out); HANDLER(amount_data).report(out); HANDLER(anon).report(out); HANDLER(auto_match).report(out); HANDLER(aux_date).report(out); HANDLER(average).report(out); HANDLER(balance_format_).report(out); HANDLER(base).report(out); HANDLER(basis).report(out); HANDLER(begin_).report(out); HANDLER(budget).report(out); HANDLER(budget_format_).report(out); HANDLER(by_payee).report(out); HANDLER(cleared).report(out); HANDLER(cleared_format_).report(out); HANDLER(color).report(out); HANDLER(collapse).report(out); HANDLER(collapse_if_zero).report(out); HANDLER(columns_).report(out); HANDLER(csv_format_).report(out); HANDLER(current).report(out); HANDLER(daily).report(out); HANDLER(date_).report(out); HANDLER(date_format_).report(out); HANDLER(datetime_format_).report(out); HANDLER(dc).report(out); HANDLER(depth_).report(out); HANDLER(deviation).report(out); HANDLER(display_).report(out); HANDLER(display_amount_).report(out); HANDLER(display_total_).report(out); HANDLER(dow).report(out); HANDLER(empty).report(out); HANDLER(end_).report(out); HANDLER(equity).report(out); HANDLER(exact).report(out); HANDLER(exchange_).report(out); HANDLER(flat).report(out); HANDLER(force_color).report(out); HANDLER(force_pager).report(out); HANDLER(forecast_while_).report(out); HANDLER(forecast_years_).report(out); HANDLER(format_).report(out); HANDLER(gain).report(out); HANDLER(generated).report(out); HANDLER(group_by_).report(out); HANDLER(group_title_format_).report(out); HANDLER(head_).report(out); HANDLER(immediate).report(out); HANDLER(inject_).report(out); HANDLER(invert).report(out); HANDLER(limit_).report(out); HANDLER(lot_dates).report(out); HANDLER(lot_prices).report(out); HANDLER(average_lot_prices).report(out); HANDLER(lot_notes).report(out); HANDLER(lots).report(out); HANDLER(lots_actual).report(out); HANDLER(market).report(out); HANDLER(meta_).report(out); HANDLER(monthly).report(out); HANDLER(no_pager).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); HANDLER(output_).report(out); HANDLER(pager_).report(out); HANDLER(payee_).report(out); HANDLER(pending).report(out); HANDLER(percent).report(out); HANDLER(period_).report(out); HANDLER(pivot_).report(out); 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); HANDLER(primary_date).report(out); HANDLER(quantity).report(out); HANDLER(quarterly).report(out); HANDLER(raw).report(out); HANDLER(real).report(out); HANDLER(register_format_).report(out); HANDLER(related).report(out); HANDLER(related_all).report(out); HANDLER(revalued).report(out); HANDLER(revalued_only).report(out); HANDLER(revalued_total_).report(out); HANDLER(rich_data).report(out); HANDLER(seed_).report(out); HANDLER(sort_).report(out); HANDLER(sort_all_).report(out); HANDLER(sort_xacts_).report(out); HANDLER(start_of_week_).report(out); HANDLER(subtotal).report(out); HANDLER(tail_).report(out); HANDLER(time_report).report(out); HANDLER(total_).report(out); HANDLER(total_data).report(out); HANDLER(truncate_).report(out); HANDLER(unbudgeted).report(out); HANDLER(uncleared).report(out); HANDLER(unrealized).report(out); HANDLER(unrealized_gains_).report(out); HANDLER(unrealized_losses_).report(out); HANDLER(unround).report(out); HANDLER(weekly).report(out); HANDLER(wide).report(out); HANDLER(yearly).report(out); HANDLER(meta_width_).report(out); HANDLER(date_width_).report(out); HANDLER(payee_width_).report(out); HANDLER(account_width_).report(out); HANDLER(amount_width_).report(out); HANDLER(total_width_).report(out); HANDLER(values).report(out); } option_t * lookup_option(const char * p); virtual void define(const symbol_t::kind_t kind, const string& name, expr_t::ptr_op_t def); virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind, const string& name); /** * Option handlers */ OPTION__ (report_t, abbrev_len_, CTOR(report_t, abbrev_len_) { on(none, "2"); }); OPTION(report_t, account_); OPTION_(report_t, actual, DO() { // -L OTHER(limit_).on(whence, "actual"); }); OPTION_(report_t, add_budget, DO() { parent->budget_flags |= BUDGET_BUDGETED | BUDGET_UNBUDGETED; }); OPTION__ (report_t, amount_, // -t DECL1(report_t, amount_, merged_expr_t, expr, ("amount_expr", "amount")) {} DO_(str) { expr.append(str); }); OPTION(report_t, amount_data); // -j OPTION(report_t, anon); OPTION(report_t, auto_match); OPTION_(report_t, average, DO() { // -A OTHER(empty).on(whence); OTHER(display_total_) .on(whence, "count>0?(display_total/count):0"); }); OPTION__ (report_t, balance_format_, CTOR(report_t, balance_format_) { on(none, "%(ansify_if(" " justify(scrub(display_total), max(int(amount_width),20)," " max(int(amount_width),20) + int(prepend_width), true, color)," " bold if should_bold))" " %(!options.flat ? depth_spacer : \"\")" "%-(ansify_if(" " ansify_if(partial_account(options.flat), blue if color)," " bold if should_bold))\n%/" "%$1\n%/" "%(prepend_width ? \" \" * int(prepend_width) : \"\")" "%(\"-\" * max(int(amount_width),20))\n"); }); OPTION(report_t, base); OPTION_(report_t, basis, DO() { // -B OTHER(revalued).on(whence); OTHER(amount_).expr.set_base_expr("rounded(cost)"); }); OPTION_(report_t, begin_, DO_(str) { // -b date_interval_t interval(str); if (optional begin = interval.begin()) { string predicate = "date>=[" + to_iso_extended_string(*begin) + "]"; OTHER(limit_).on(whence, predicate); } else { throw_(std::invalid_argument, _f("Could not determine beginning of period '%1%'") % str); } }); OPTION_ (report_t, bold_if_, expr_t expr; DO_(str) { expr = str; }); OPTION_(report_t, budget, DO() { parent->budget_flags |= BUDGET_BUDGETED; }); OPTION__ (report_t, budget_format_, CTOR(report_t, budget_format_) { on(none, "%(justify(scrub(get_at(display_total, 0)), int(amount_width), -1, true, color))" " %(justify(-scrub(get_at(display_total, 1)), int(amount_width), " " int(amount_width) + 1 + int(amount_width), true, color))" " %(justify(scrub((get_at(display_total, 1) || 0) + " " (get_at(display_total, 0) || 0)), int(amount_width), " " int(amount_width) + 1 + int(amount_width) + 1 + int(amount_width), true, color))" " %(ansify_if(" " justify((get_at(display_total, 1) ? " " (100% * quantity(scrub(get_at(display_total, 0)))) / " " -quantity(scrub(get_at(display_total, 1))) : 0), " " 5, -1, true, false)," " magenta if (color and get_at(display_total, 1) and " " (abs(quantity(scrub(get_at(display_total, 0))) / " " quantity(scrub(get_at(display_total, 1)))) >= 1))))" " %(!options.flat ? depth_spacer : \"\")" "%-(ansify_if(partial_account(options.flat), blue if color))\n" "%/%$1 %$2 %$3 %$4\n%/" "%(prepend_width ? \" \" * int(prepend_width) : \"\")" "------------ ------------ ------------ -----\n"); }); OPTION(report_t, by_payee); // -P OPTION_(report_t, cleared, DO() { // -C OTHER(limit_).on(whence, "cleared"); }); OPTION__ (report_t, cleared_format_, CTOR(report_t, cleared_format_) { on(none, "%(justify(scrub(get_at(display_total, 0)), 16, 16 + int(prepend_width), " " true, color)) %(justify(scrub(get_at(display_total, 1)), 18, " " 36 + int(prepend_width), true, color))" " %(latest_cleared ? format_date(latest_cleared) : \" \")" " %(!options.flat ? depth_spacer : \"\")" "%-(ansify_if(partial_account(options.flat), blue if color))\n%/" "%$1 %$2 %$3\n%/" "%(prepend_width ? \" \" * int(prepend_width) : \"\")" "---------------- ---------------- ---------\n"); }); OPTION(report_t, color); OPTION_(report_t, collapse, DO() { // -n // Make sure that balance reports are collapsed too, but only apply it // to account xacts OTHER(display_).on(whence, "post|depth<=1"); }); OPTION_(report_t, collapse_if_zero, DO() { OTHER(collapse).on(whence); }); 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(display_account))," "%(quoted(commodity(scrub(display_amount))))," "%(quoted(quantity(scrub(display_amount))))," "%(quoted(cleared ? \"*\" : (pending ? \"!\" : \"\")))," "%(quoted(join(note | xact.note)))\n"); }); OPTION_(report_t, current, DO() { // -c OTHER(limit_).on(whence, "date<=today"); }); OPTION_(report_t, daily, DO() { // -D OTHER(period_).on(whence, "daily"); }); OPTION(report_t, date_); OPTION(report_t, date_format_); OPTION(report_t, datetime_format_); OPTION_(report_t, dc, DO() { OTHER(amount_).expr.set_base_expr ("(amount > 0 ? amount : 0, amount < 0 ? amount : 0)"); OTHER(register_format_) .on(none, "%(ansify_if(" " ansify_if(justify(format_date(date), int(date_width))," " green if color and date > today)," " bold if should_bold))" " %(ansify_if(" " ansify_if(justify(truncated(payee, int(payee_width)), int(payee_width)), " " bold if color and !cleared and actual)," " bold if should_bold))" " %(ansify_if(" " ansify_if(justify(truncated(display_account, int(account_width), " " int(abbrev_len)), int(account_width))," " blue if color)," " bold if should_bold))" " %(ansify_if(" " justify(scrub(abs(get_at(display_amount, 0))), int(amount_width), " " 3 + int(meta_width) + int(date_width) + int(payee_width)" " + int(account_width) + int(amount_width) + int(prepend_width)," " true, color)," " bold if should_bold))" " %(ansify_if(" " justify(scrub(abs(get_at(display_amount, 1))), int(amount_width), " " 4 + int(meta_width) + int(date_width) + int(payee_width)" " + int(account_width) + int(amount_width) + int(amount_width) + int(prepend_width)," " true, color)," " bold if should_bold))" " %(ansify_if(" " justify(scrub(get_at(display_total, 0) + get_at(display_total, 1)), int(total_width), " " 5 + int(meta_width) + int(date_width) + int(payee_width)" " + int(account_width) + int(amount_width) + int(amount_width) + int(total_width)" " + int(prepend_width), true, color)," " bold if should_bold))\n%/" "%(justify(\" \", int(date_width)))" " %(ansify_if(" " justify(truncated(has_tag(\"Payee\") ? payee : \" \", " " int(payee_width)), int(payee_width))," " bold if should_bold))" " %$3 %$4 %$5 %$6\n"); OTHER(balance_format_) .on(none, "%(ansify_if(" " justify(scrub(abs(get_at(display_total, 0))), 14," " 14 + int(prepend_width), true, color)," " bold if should_bold)) " "%(ansify_if(" " justify(scrub(abs(get_at(display_total, 1))), 14," " 14 + 1 + int(prepend_width) + int(total_width), true, color)," " bold if should_bold)) " "%(ansify_if(" " justify(scrub(get_at(display_total, 0) + get_at(display_total, 1)), 14," " 14 + 2 + int(prepend_width) + int(total_width) + int(total_width), true, color)," " bold if should_bold))" " %(!options.flat ? depth_spacer : \"\")" "%-(ansify_if(" " ansify_if(partial_account(options.flat), blue if color)," " bold if should_bold))\n%/" "%$1 %$2 %$3\n%/" "%(prepend_width ? \" \" * int(prepend_width) : \"\")" "--------------------------------------------\n"); }); OPTION_(report_t, depth_, DO_(str) { OTHER(display_).on(whence, string("depth<=") + str); }); OPTION_(report_t, deviation, DO() { OTHER(display_total_) .on(whence, "display_amount-display_total"); }); OPTION_ (report_t, display_, DO_(str) { // -d if (handled) value = string("(") + value + ")&(" + str + ")"; }); OPTION__ (report_t, display_amount_, DECL1(report_t, display_amount_, merged_expr_t, expr, ("display_amount", "amount_expr")) {} DO_(str) { expr.append(str); }); OPTION__ (report_t, display_total_, DECL1(report_t, display_total_, merged_expr_t, expr, ("display_total", "total_expr")) {} DO_(str) { expr.append(str); }); OPTION(report_t, dow); OPTION(report_t, aux_date); OPTION(report_t, empty); // -E OPTION_(report_t, end_, DO_(str) { // -e // Use begin() here so that if the user says --end=2008, we end on // 2008/01/01 instead of 2009/01/01 (which is what end() would // return). date_interval_t interval(str); if (optional end = interval.begin()) { string predicate = "date<[" + to_iso_extended_string(*end) + "]"; OTHER(limit_).on(whence, predicate); parent->terminus = datetime_t(*end); } else { throw_(std::invalid_argument, _f("Could not determine end of period '%1%'") % str); } }); OPTION(report_t, equity); OPTION(report_t, exact); OPTION_(report_t, exchange_, DO_(str) { // -X // Using -X implies -V. The main difference is that now // HANDLER(exchange_) contains the name of a commodity, which // is accessed via the "exchange" value expression function. OTHER(market).on(whence); }); OPTION(report_t, flat); OPTION(report_t, force_color); OPTION(report_t, force_pager); OPTION(report_t, forecast_while_); OPTION(report_t, forecast_years_); OPTION(report_t, format_); // -F OPTION_(report_t, gain, DO() { // -G OTHER(revalued).on(whence); OTHER(amount_).expr.set_base_expr("(amount, cost)"); // Since we are displaying the amounts of revalued postings, they // will end up being composite totals, and hence a pair of pairs. OTHER(display_amount_) .on(whence, "use_direct_amount ? amount :" " (is_seq(get_at(amount_expr, 0)) ?" " get_at(get_at(amount_expr, 0), 0) :" " market(get_at(amount_expr, 0), value_date, exchange)" " - get_at(amount_expr, 1))"); OTHER(revalued_total_) .on(whence, "(market(get_at(total_expr, 0), value_date, exchange), " "get_at(total_expr, 1))"); OTHER(display_total_) .on(whence, "use_direct_amount ? total_expr :" " market(get_at(total_expr, 0), value_date, exchange)" " - get_at(total_expr, 1)"); }); OPTION(report_t, generated); OPTION_ (report_t, group_by_, expr_t expr; DO_(str) { expr = str; }); OPTION__ (report_t, group_title_format_, CTOR(report_t, group_title_format_) { on(none, "%(value)\n"); }); OPTION(report_t, head_); OPTION_(report_t, historical, DO() { // -H OTHER(market).on(whence); OTHER(amount_) .on(whence, "nail_down(amount_expr, " "market(amount_expr, value_date, exchange))"); }); OPTION(report_t, immediate); OPTION(report_t, inject_); OPTION_(report_t, invert, DO() { OTHER(amount_).on(whence, "-amount_expr"); }); OPTION_ (report_t, limit_, DO_(str) { // -l if (handled) value = string("(") + value + ")&(" + str + ")"; }); OPTION(report_t, lot_dates); OPTION(report_t, lot_prices); OPTION_(report_t, average_lot_prices, DO() { OTHER(lot_prices).on(whence); OTHER(display_amount_) .on(whence, "averaged_lots(display_amount)"); OTHER(display_total_) .on(whence, "averaged_lots(display_total)"); }); OPTION(report_t, lot_notes); OPTION(report_t, lots); OPTION(report_t, lots_actual); OPTION_(report_t, market, DO() { // -V OTHER(revalued).on(whence); OTHER(display_amount_) .on(whence, "market(display_amount, value_date, exchange)"); OTHER(display_total_) .on(whence, "market(display_total, value_date, exchange)"); }); OPTION(report_t, meta_); OPTION_(report_t, monthly, DO() { // -M OTHER(period_).on(whence, "monthly"); }); OPTION_(report_t, no_color, DO() { OTHER(color).off(); }); OPTION_(report_t, no_revalued, DO() { OTHER(revalued).off(); }); OPTION(report_t, no_rounding); OPTION(report_t, no_titles); OPTION(report_t, no_total); OPTION_(report_t, now_, DO_(str) { date_interval_t interval(str); if (optional begin = interval.begin()) { ledger::epoch = parent->terminus = datetime_t(*begin); } else { throw_(std::invalid_argument, _f("Could not determine beginning of period '%1%'") % str); } }); OPTION_ (report_t, only_, DO_(str) { if (handled) value = string("(") + value + ")&(" + str + ")"; }); OPTION(report_t, output_); // -o // setenv() is not available on WIN32 #if HAVE_ISATTY and !defined(_WIN32) and !defined(__CYGWIN__) OPTION__ (report_t, pager_, CTOR(report_t, pager_) { if (isatty(STDOUT_FILENO)) { if (! std::getenv("PAGER")) { bool have_less = false; if (exists(path("/opt/local/bin/less")) || exists(path("/usr/local/bin/less")) || exists(path("/usr/bin/less"))) have_less = true; if (have_less) { on(none, "less"); setenv("LESS", "-FRSX", 0); // don't overwrite } } else { on(none, std::getenv("PAGER")); setenv("LESS", "-FRSX", 0); // don't overwrite } } }); #else // HAVE_ISATTY OPTION(report_t, pager_); #endif // HAVE_ISATTY OPTION_(report_t, no_pager, DO() { OTHER(pager_).off(); }); OPTION(report_t, payee_); OPTION_(report_t, pending, DO() { // -C OTHER(limit_).on(whence, "pending"); }); OPTION_(report_t, percent, DO() { // -% OTHER(total_) .on(whence, "((is_account&parent&parent.total)?" " percent(scrub(total), scrub(parent.total)):0)"); }); OPTION_ (report_t, period_, DO_(str) { // -p if (handled) value += string(" ") + str; }); OPTION(report_t, pivot_); OPTION__ (report_t, plot_amount_format_, CTOR(report_t, plot_amount_format_) { on(none, "%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_amount)))\n"); }); OPTION__ (report_t, plot_total_format_, CTOR(report_t, plot_total_format_) { on(none, "%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_total)))\n"); }); OPTION(report_t, prepend_format_); OPTION(report_t, prepend_width_); OPTION_(report_t, price, DO() { // -I OTHER(amount_).expr.set_base_expr("price"); }); OPTION__ (report_t, prices_format_, CTOR(report_t, prices_format_) { on(none, "%(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) %(display_account) %(scrub(display_amount))\n"); }); OPTION(report_t, primary_date); OPTION_(report_t, quantity, DO() { // -O OTHER(revalued).off(); OTHER(amount_).expr.set_base_expr("amount"); OTHER(total_).expr.set_base_expr("total"); }); OPTION_(report_t, quarterly, DO() { OTHER(period_).on(whence, "quarterly"); }); OPTION(report_t, raw); OPTION_(report_t, real, DO() { // -R OTHER(limit_).on(whence, "real"); }); OPTION__ (report_t, register_format_, CTOR(report_t, register_format_) { on(none, "%(ansify_if(" " ansify_if(justify(format_date(date), int(date_width))," " green if color and date > today)," " bold if should_bold))" " %(ansify_if(" " ansify_if(justify(truncated(payee, int(payee_width)), int(payee_width)), " " bold if color and !cleared and actual)," " bold if should_bold))" " %(ansify_if(" " ansify_if(justify(truncated(display_account, int(account_width), " " int(abbrev_len)), int(account_width))," " blue if color)," " bold if should_bold))" " %(ansify_if(" " justify(scrub(display_amount), int(amount_width), " " 3 + int(meta_width) + int(date_width) + int(payee_width)" " + int(account_width) + int(amount_width) + int(prepend_width)," " true, color)," " bold if should_bold))" " %(ansify_if(" " justify(scrub(display_total), int(total_width), " " 4 + int(meta_width) + int(date_width) + int(payee_width)" " + int(account_width) + int(amount_width) + int(total_width)" " + int(prepend_width), true, color)," " bold if should_bold))\n%/" "%(justify(\" \", int(date_width)))" " %(ansify_if(" " justify(truncated(has_tag(\"Payee\") ? payee : \" \", " " int(payee_width)), int(payee_width))," " bold if should_bold))" " %$3 %$4 %$5\n"); }); OPTION(report_t, related); // -r OPTION_(report_t, related_all, DO() { OTHER(related).on(whence); }); OPTION(report_t, revalued); OPTION(report_t, revalued_only); OPTION_ (report_t, revalued_total_, expr_t expr; DO_(str) { expr = str; }); OPTION(report_t, rich_data); OPTION(report_t, seed_); OPTION_(report_t, sort_, DO_(str) { // -S OTHER(sort_xacts_).off(); OTHER(sort_all_).off(); }); OPTION_(report_t, sort_all_, DO_(str) { OTHER(sort_).on(whence, str); OTHER(sort_xacts_).off(); }); OPTION_(report_t, sort_xacts_, DO_(str) { OTHER(sort_).on(whence, str); OTHER(sort_all_).off(); }); OPTION(report_t, start_of_week_); OPTION(report_t, subtotal); // -s OPTION(report_t, tail_); OPTION_(report_t, time_report, DO() { OTHER(balance_format_) .on(none, "%(ansify_if(justify(earliest_checkin ? " " format_datetime(earliest_checkin) : \"\", 19, -1, true)," " bold if latest_checkout_cleared)) " "%(ansify_if(justify(latest_checkout ? " " format_datetime(latest_checkout) : \"\", 19, -1, true), " " bold if latest_checkout_cleared)) " "%(latest_checkout_cleared ? \"*\" : \" \") " "%(ansify_if(" " justify(scrub(display_total), 8," " 8 + 4 + 19 * 2, true, color), bold if should_bold))" " %(!options.flat ? depth_spacer : \"\")" "%-(ansify_if(" " ansify_if(partial_account(options.flat), blue if color)," " bold if should_bold))\n%/" "%$1 %$2 %$3\n%/" "%(prepend_width ? \" \" * int(prepend_width) : \"\")" "--------------------------------------------------\n"); }); OPTION__ (report_t, total_, // -T DECL1(report_t, total_, merged_expr_t, expr, ("total_expr", "total")) {} DO_(str) { expr.append(str); }); OPTION(report_t, total_data); // -J OPTION_(report_t, truncate_, DO_(style) { if (style == "leading") format_t::default_style = format_t::TRUNCATE_LEADING; else if (style == "middle") format_t::default_style = format_t::TRUNCATE_MIDDLE; else if (style == "trailing") format_t::default_style = format_t::TRUNCATE_TRAILING; else throw_(std::invalid_argument, _f("Unrecognized truncation style: '%1%'") % style); format_t::default_style_changed = true; }); OPTION_(report_t, unbudgeted, DO() { parent->budget_flags |= BUDGET_UNBUDGETED; }); OPTION_(report_t, uncleared, DO() { // -U OTHER(limit_).on(whence, "uncleared|pending"); }); OPTION(report_t, unrealized); OPTION(report_t, unrealized_gains_); OPTION(report_t, unrealized_losses_); OPTION_(report_t, unround, DO() { OTHER(amount_).on(whence, "unrounded(amount_expr)"); OTHER(total_).on(whence, "unrounded(total_expr)"); }); OPTION_(report_t, weekly, DO() { // -W OTHER(period_).on(whence, "weekly"); }); OPTION_(report_t, wide, DO() { // -w OTHER(columns_).on(whence, "132"); }); OPTION_(report_t, yearly, DO() { // -Y OTHER(period_).on(whence, "yearly"); }); OPTION(report_t, meta_width_); OPTION(report_t, date_width_); OPTION(report_t, payee_width_); OPTION(report_t, account_width_); OPTION(report_t, amount_width_); OPTION(report_t, total_width_); OPTION(report_t, values); }; template class reporter { shared_ptr > handler; report_t& report; string whence; public: reporter(shared_ptr > _handler, report_t& _report, const string& _whence) : handler(_handler), report(_report), whence(_whence) { TRACE_CTOR(reporter, "item_handler, report_t&, string"); } reporter(const reporter& other) : handler(other.handler), report(other.report), whence(other.whence) { TRACE_CTOR(reporter, "copy"); } ~reporter() throw() { TRACE_DTOR(reporter); } value_t operator()(call_scope_t& args) { if (args.size() > 0) report.parse_query_args(args.value(), whence); (report.*report_method)(handler_ptr(handler)); return true; } }; } // namespace ledger