summaryrefslogtreecommitdiff
path: root/src/report.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/report.h')
-rw-r--r--src/report.h958
1 files changed, 958 insertions, 0 deletions
diff --git a/src/report.h b/src/report.h
new file mode 100644
index 00000000..94d39215
--- /dev/null
+++ b/src/report.h
@@ -0,0 +1,958 @@
+/*
+ * Copyright (c) 2003-2009, 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
+ */
+#ifndef _REPORT_H
+#define _REPORT_H
+
+#include "interactive.h"
+#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) {}
+
+ virtual ~report_t() {
+ output_stream.close();
+ }
+
+ void normalize_options(const string& verb);
+ 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 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_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_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_abs(call_scope_t& scope);
+ value_t fn_justify(call_scope_t& scope);
+ value_t fn_quoted(call_scope_t& scope);
+ value_t fn_join(call_scope_t& scope);
+ value_t fn_format_date(call_scope_t& scope);
+ value_t fn_ansify_if(call_scope_t& scope);
+ value_t fn_percent(call_scope_t& scope);
+ value_t fn_price(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_now(call_scope_t&) {
+ return terminus;
+ }
+ value_t fn_today(call_scope_t&) {
+ return terminus.date();
+ }
+
+ value_t fn_options(call_scope_t&) {
+ return value_t(static_cast<scope_t *>(this));
+ }
+
+ string report_format(option_t<report_t>& option) {
+ if (HANDLED(format_))
+ return HANDLER(format_).str();
+ return option.str();
+ }
+
+ optional<string> maybe_format(option_t<report_t>& option) {
+ if (option)
+ return option.str();
+ return none;
+ }
+
+ value_t reload_command(call_scope_t&);
+ value_t echo_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_tags),
+ HANDLED(lots_actual));
+ }
+
+ void report_options(std::ostream& out)
+ {
+ HANDLER(abbrev_len_).report(out);
+ HANDLER(account_).report(out);
+ HANDLER(actual).report(out);
+ HANDLER(actual_dates).report(out);
+ HANDLER(add_budget).report(out);
+ HANDLER(amount_).report(out);
+ HANDLER(amount_data).report(out);
+ HANDLER(anon).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(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(effective).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(head_).report(out);
+ HANDLER(invert).report(out);
+ HANDLER(limit_).report(out);
+ HANDLER(lot_dates).report(out);
+ HANDLER(lot_prices).report(out);
+ HANDLER(lot_tags).report(out);
+ HANDLER(lots).report(out);
+ HANDLER(lots_actual).report(out);
+ HANDLER(market).report(out);
+ HANDLER(monthly).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(plot_amount_format_).report(out);
+ HANDLER(plot_total_format_).report(out);
+ HANDLER(prepend_format_).report(out);
+ HANDLER(price).report(out);
+ HANDLER(prices_format_).report(out);
+ HANDLER(pricedb_format_).report(out);
+ HANDLER(print_format_).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(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(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(unsorted).report(out);
+ HANDLER(weekly).report(out);
+ HANDLER(wide).report(out);
+ HANDLER(yearly).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);
+ }
+
+ option_t<report_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_with(none, 2L); });
+ OPTION(report_t, account_);
+
+ OPTION_(report_t, actual, DO() { // -L
+ parent->HANDLER(limit_).on(string("--actual"), "actual");
+ });
+
+ OPTION(report_t, actual_dates);
+
+ OPTION_(report_t, add_budget, DO() {
+ parent->budget_flags |= BUDGET_BUDGETED | BUDGET_UNBUDGETED;
+ });
+
+ OPTION__
+ (report_t, amount_, // -t
+ expr_t expr;
+ CTOR(report_t, amount_) {
+ set_expr(none, "amount");
+ }
+ 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, amount_data); // -j
+ OPTION(report_t, anon);
+
+ OPTION_(report_t, average, DO() { // -A
+ parent->HANDLER(display_total_)
+ .set_expr(string("--average"), "total_expr/count");
+ });
+
+ OPTION__(report_t, balance_format_, CTOR(report_t, balance_format_) {
+ on(none,
+ "%(justify(scrub(display_total), 20, -1, true, color))"
+ " %(!options.flat ? depth_spacer : \"\")"
+ "%-(ansify_if(partial_account(options.flat), blue if color))\n%/"
+ "%$1\n%/"
+ "--------------------\n");
+ });
+
+ OPTION(report_t, base);
+
+ OPTION_(report_t, basis, DO() { // -B
+ parent->HANDLER(revalued).on_only(string("--basis"));
+ parent->HANDLER(amount_).set_expr(string("--basis"), "rounded(cost)");
+ });
+
+ OPTION_(report_t, begin_, DO_(args) { // -b
+ date_interval_t interval(args[1].to_string());
+ optional<date_t> begin = interval.begin(parent->session.current_year);
+ if (! begin)
+ throw_(std::invalid_argument,
+ _("Could not determine beginning of period '%1'")
+ << args[1].to_string());
+
+ string predicate = "date>=[" + to_iso_extended_string(*begin) + "]";
+ parent->HANDLER(limit_).on(string("--begin"), predicate);
+ });
+
+ 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(total_expr, 0)), 12, -1, true, color))"
+ " %(justify(scrub(- get_at(total_expr, 1)), 12, "
+ " 12 + 1 + 12, true, color))"
+ " %(justify(scrub(get_at(total_expr, 1) + "
+ " get_at(total_expr, 0)), 12, "
+ " 12 + 1 + 12 + 1 + 12, true, color))"
+ " %(ansify_if("
+ " justify((get_at(total_expr, 1) ? "
+ " scrub((100% * get_at(total_expr, 0)) / "
+ " - get_at(total_expr, 1)) : 0), "
+ " 5, -1, true, false),"
+ " magenta if (color and get_at(total_expr, 1) and "
+ " (abs(quantity(get_at(total_expr, 0)) / "
+ " quantity(get_at(total_expr, 1))) >= 1))))"
+ " %(!options.flat ? depth_spacer : \"\")"
+ "%-(ansify_if(partial_account(options.flat), blue if color))\n"
+ "%/%$1 %$2 %$3 %$4\n%/"
+ "------------ ------------ ------------ -----\n");
+ });
+
+ OPTION(report_t, by_payee); // -P
+
+ OPTION_(report_t, cleared, DO() { // -C
+ parent->HANDLER(limit_).on(string("--cleared"), "cleared");
+ });
+
+ 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))"
+ " %(latest_cleared ? format_date(latest_cleared) : \" \")"
+ " %(!options.flat ? depth_spacer : \"\")"
+ "%-(ansify_if(partial_account(options.flat), blue if color))\n%/"
+ "%$1 %$2 %$3\n%/"
+ "---------------- ---------------- ---------\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
+ parent->HANDLER(display_).on(string("--collapse"), "post|depth<=1");
+ });
+
+ OPTION_(report_t, collapse_if_zero, DO() {
+ parent->HANDLER(collapse).on_only(string("--collapse-if-zero"));
+ });
+
+ OPTION(report_t, columns_);
+
+ OPTION__(report_t, csv_format_, CTOR(report_t, csv_format_) {
+ on(none,
+ "%(quoted(date)),"
+ "%(quoted(payee)),"
+ "%(quoted(account)),"
+ "%(quoted(scrub(display_amount))),"
+ "%(quoted(cleared ? \"*\" : (pending ? \"!\" : \"\"))),"
+ "%(quoted(code)),"
+ "%(quoted(join(note | xact.note)))\n");
+ });
+
+ OPTION_(report_t, current, DO() { // -c
+ parent->HANDLER(limit_).on(string("--current"), "date<=today");
+ });
+
+ OPTION_(report_t, daily, DO() { // -D
+ parent->HANDLER(period_).on(string("--daily"), "daily");
+ });
+
+ OPTION(report_t, date_);
+ OPTION(report_t, date_format_);
+ OPTION(report_t, datetime_format_);
+
+ OPTION_(report_t, depth_, DO_(scope) {
+ interactive_t args(scope, "sl");
+ parent->HANDLER(display_).on(string("--depth"),
+ string("depth<=") + args.get<string>(1));
+ });
+
+ OPTION_(report_t, deviation, DO() {
+ parent->HANDLER(display_total_)
+ .set_expr(string("--deviation"), "amount_expr-total_expr/count");
+ });
+
+ OPTION__
+ (report_t, display_, // -d
+ CTOR(report_t, display_) {}
+ virtual void on_with(const optional<string>& whence, const value_t& text) {
+ if (! handled)
+ option_t<report_t>::on_with(whence, text);
+ else
+ option_t<report_t>::on_with(whence,
+ string_value(string("(") + str() + ")&(" +
+ text.as_string() + ")"));
+ });
+
+ OPTION__
+ (report_t, display_amount_,
+ expr_t expr;
+ CTOR(report_t, display_amount_) {
+ set_expr(none, "amount_expr");
+ }
+ 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, display_total_,
+ expr_t expr;
+ CTOR(report_t, display_total_) {
+ set_expr(none, "total_expr");
+ }
+ 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, dow);
+ OPTION(report_t, effective);
+ OPTION(report_t, empty); // -E
+
+ OPTION_(report_t, end_, DO_(args) { // -e
+ date_interval_t interval(args[1].to_string());
+ // 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).
+ optional<date_t> end = interval.begin(parent->session.current_year);
+ if (! end)
+ throw_(std::invalid_argument,
+ _("Could not determine end of period '%1'")
+ << args[1].to_string());
+
+ string predicate = "date<[" + to_iso_extended_string(*end) + "]";
+ parent->HANDLER(limit_).on(string("--end"), predicate);
+
+ parent->terminus = datetime_t(*end);
+ });
+
+ OPTION(report_t, equity);
+ OPTION(report_t, exact);
+
+ OPTION_(report_t, exchange_, DO_(args) { // -X
+ on_with(args[0].as_string(), args[1]);
+ call_scope_t no_args(*parent);
+ no_args.push_back(args[0]);
+ parent->HANDLER(market).parent = parent;
+ parent->HANDLER(market).handler(no_args);
+ });
+
+ 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
+ parent->HANDLER(revalued).on_only(string("--gain"));
+ parent->HANDLER(amount_).set_expr(string("--gain"), "(amount, cost)");
+ // Since we are displaying the amounts of revalued postings, they
+ // will end up being composite totals, and hence a pair of pairs.
+ parent->HANDLER(display_amount_)
+ .set_expr(string("--gain"),
+ "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), date, exchange)"
+ " - get_at(amount_expr, 1))");
+ parent->HANDLER(revalued_total_)
+ .set_expr(string("--gain"),
+ "(market(get_at(total_expr, 0), date, exchange), "
+ "get_at(total_expr, 1))");
+ parent->HANDLER(display_total_)
+ .set_expr(string("--gain"),
+ "use_direct_amount ? total_expr :"
+ " market(get_at(total_expr, 0), date, exchange)"
+ " - get_at(total_expr, 1)");
+ });
+
+ OPTION(report_t, head_);
+
+ OPTION_(report_t, invert, DO() {
+ parent->HANDLER(amount_).set_expr(string("--invert"), "-amount");
+ });
+
+ OPTION__
+ (report_t, limit_, // -l
+ CTOR(report_t, limit_) {}
+ virtual void on_with(const optional<string>& whence, const value_t& text) {
+ if (! handled)
+ option_t<report_t>::on_with(whence, text);
+ else
+ option_t<report_t>::on_with(whence,
+ string_value(string("(") + str() + ")&(" +
+ text.as_string() + ")"));
+ });
+
+ OPTION(report_t, lot_dates);
+ OPTION(report_t, lot_prices);
+ OPTION(report_t, lot_tags);
+ OPTION(report_t, lots);
+ OPTION(report_t, lots_actual);
+
+ OPTION_(report_t, market, DO() { // -V
+ parent->HANDLER(revalued).on_only(string("--market"));
+ parent->HANDLER(display_amount_)
+ .set_expr(string("--market"), "market(amount_expr, date, exchange)");
+ parent->HANDLER(display_total_)
+ .set_expr(string("--market"), "market(total_expr, date, exchange)");
+ });
+
+ OPTION_(report_t, monthly, DO() { // -M
+ parent->HANDLER(period_).on(string("--monthly"), "monthly");
+ });
+
+ OPTION_(report_t, no_color, DO() {
+ parent->HANDLER(color).off();
+ });
+
+ OPTION(report_t, no_total);
+
+ OPTION_(report_t, now_, DO_(args) {
+ date_interval_t interval(args[1].to_string());
+ optional<date_t> begin = interval.begin(parent->session.current_year);
+ if (! begin)
+ throw_(std::invalid_argument,
+ _("Could not determine beginning of period '%1'")
+ << args[1].to_string());
+ ledger::epoch = parent->terminus = datetime_t(*begin);
+ parent->session.current_year = ledger::epoch->date().year();
+ });
+
+ OPTION__
+ (report_t, only_,
+ CTOR(report_t, only_) {}
+ virtual void on_with(const optional<string>& whence, const value_t& text) {
+ if (! handled)
+ option_t<report_t>::on_with(whence, text);
+ else
+ option_t<report_t>::on_with(whence,
+ string_value(string("(") + str() + ")&(" +
+ text.as_string() + ")"));
+ });
+
+ OPTION(report_t, output_); // -o
+
+#ifdef HAVE_ISATTY
+ OPTION__
+ (report_t, pager_,
+ CTOR(report_t, pager_) {
+ if (! std::getenv("PAGER") && isatty(STDOUT_FILENO)) {
+ 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
+ }
+ }
+ }
+ virtual void on_with(const optional<string>& whence, const value_t& text) {
+ string cmd(text.to_string());
+ if (cmd == "" || cmd == "false" || cmd == "off" ||
+ cmd == "none" || cmd == "no" || cmd == "disable")
+ option_t<report_t>::off();
+ else
+ option_t<report_t>::on_with(whence, text);
+ });
+#else // HAVE_ISATTY
+ OPTION__
+ (report_t, pager_,
+ CTOR(report_t, pager_) {
+ }
+ virtual void on_with(const optional<string>& whence, const value_t& text) {
+ string cmd(text.to_string());
+ if (cmd == "" || cmd == "false" || cmd == "off" ||
+ cmd == "none" || cmd == "no" || cmd == "disable")
+ option_t<report_t>::off();
+ else
+ option_t<report_t>::on_with(whence, text);
+ });
+#endif // HAVE_ISATTY
+
+ OPTION(report_t, payee_);
+
+ OPTION_(report_t, pending, DO() { // -C
+ parent->HANDLER(limit_).on(string("--pending"), "pending");
+ });
+
+ OPTION_(report_t, percent, DO() { // -%
+ parent->HANDLER(total_)
+ .set_expr(string("--percent"),
+ "is_account&parent&parent.total&percent(total, parent.total)");
+ });
+
+ OPTION__
+ (report_t, period_, // -p
+ CTOR(report_t, period_) {}
+ virtual void on_with(const optional<string>& whence, const value_t& text) {
+ if (! handled)
+ option_t<report_t>::on_with(whence, text);
+ else
+ option_t<report_t>::on_with(whence,
+ string_value(text.as_string() + " " + str()));
+ });
+
+ 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, price, DO() { // -I
+ parent->HANDLER(display_amount_)
+ .set_expr(string("--price"), "price(amount_expr)");
+ parent->HANDLER(display_total_)
+ .set_expr(string("--price"), "price(total_expr)");
+ });
+
+ OPTION__(report_t, prices_format_, CTOR(report_t, prices_format_) {
+ on(none,
+ "%(date) %-8(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");
+ });
+
+ OPTION__(report_t, print_format_, CTOR(report_t, print_format_) {
+ on(none,
+ "%(xact.date)"
+ "%(!effective & xact.effective_date ?"
+ " \"=\" + xact.effective_date : \"\")"
+ "%(xact.cleared ? \" *\" : (xact.pending ? \" !\" : \"\"))"
+ "%(code ? \" (\" + code + \")\" :"
+ " \"\") %(payee)%(xact.comment)\n"
+ " %(xact.uncleared ?"
+ " (cleared ? \"* \" : (pending ? \"! \" : \"\")) : \"\")"
+ "%(calculated ? account : justify(account, 34, -1, false))"
+ "%(calculated ? \"\" : \" \" + justify(scrub(amount), 12, -1, true))"
+ "%(has_cost & !cost_calculated ?"
+ " \" @ \" + justify(scrub(abs(cost / amount)), 0) : \"\")"
+ "%(comment)\n%/"
+ " %$7%$8%$9%$A%$B\n%/\n");
+ });
+
+ OPTION_(report_t, quantity, DO() { // -O
+ parent->HANDLER(revalued).off();
+ parent->HANDLER(amount_).set_expr(string("--quantity"), "amount");
+ parent->HANDLER(total_).set_expr(string("--quantity"), "total");
+ });
+
+ OPTION_(report_t, quarterly, DO() {
+ parent->HANDLER(period_).on(string("--quarterly"), "quarterly");
+ });
+
+ OPTION(report_t, raw);
+
+ OPTION_(report_t, real, DO() { // -R
+ parent->HANDLER(limit_).on(string("--real"), "real");
+ });
+
+ OPTION__(report_t, register_format_, CTOR(report_t, register_format_) {
+ on(none,
+ "%(ansify_if(justify(format_date(date), date_width), green "
+ " 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))"
+ " %(justify(scrub(display_amount), amount_width, "
+ " 3 + date_width + payee_width + account_width + amount_width, "
+ " true, color))"
+ " %(justify(scrub(display_total), total_width, "
+ " 4 + date_width + payee_width + account_width + amount_width "
+ " + total_width, true, color))\n%/"
+ "%(justify(\" \", 2 + date_width + payee_width))%$3 %$4 %$5\n");
+ });
+
+ OPTION(report_t, related); // -r
+
+ OPTION_(report_t, related_all, DO() {
+ parent->HANDLER(related).on_only(string("--related-all"));
+ });
+
+ OPTION(report_t, revalued);
+ OPTION(report_t, revalued_only);
+
+ OPTION__
+ (report_t, revalued_total_,
+ expr_t expr;
+ CTOR(report_t, revalued_total_) {}
+ 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, seed_);
+
+ OPTION_(report_t, sort_, DO_(args) { // -S
+ on_with(args[0].as_string(), args[1]);
+ parent->HANDLER(sort_xacts_).off();
+ parent->HANDLER(sort_all_).off();
+ });
+
+ OPTION_(report_t, sort_all_, DO_(args) {
+ parent->HANDLER(sort_).on_with(string("--sort-all"), args[1]);
+ parent->HANDLER(sort_xacts_).off();
+ });
+
+ OPTION_(report_t, sort_xacts_, DO_(args) {
+ parent->HANDLER(sort_).on_with(string("--sort-xacts"), args[1]);
+ parent->HANDLER(sort_all_).off();
+ });
+
+ OPTION(report_t, start_of_week_);
+ OPTION(report_t, subtotal); // -s
+ OPTION(report_t, tail_);
+
+ OPTION__
+ (report_t, total_, // -T
+ expr_t expr;
+ CTOR(report_t, total_) {
+ set_expr(none, "total");
+ }
+ 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, total_data); // -J
+
+ OPTION_(report_t, truncate_, DO_(args) {
+ string style(args[1].to_string());
+ 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,
+ _("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
+ parent->HANDLER(limit_).on(string("--uncleared"), "uncleared|pending");
+ });
+
+ OPTION(report_t, unrealized);
+
+ OPTION(report_t, unrealized_gains_);
+ OPTION(report_t, unrealized_losses_);
+
+ OPTION_(report_t, unround, DO() {
+ parent->HANDLER(display_amount_)
+ .set_expr(string("--unround"), "unrounded(amount_expr)");
+ parent->HANDLER(display_total_)
+ .set_expr(string("--unround"), "unrounded(total_expr)");
+ });
+
+ OPTION(report_t, unsorted);
+
+ OPTION_(report_t, weekly, DO() { // -W
+ parent->HANDLER(period_).on(string("--weekly"), "weekly");
+ });
+
+ OPTION_(report_t, wide, DO() { // -w
+ parent->HANDLER(columns_).on_with(string("--wide"), 132L);
+ });
+
+ OPTION_(report_t, yearly, DO() { // -Y
+ parent->HANDLER(period_).on(string("--yearly"), "yearly");
+ });
+
+ OPTION__(report_t, date_width_,
+ bool specified;
+ CTOR(report_t, date_width_) { specified = false; }
+ DO_(args) { value = args[1].to_long(); specified = true; });
+ OPTION__(report_t, payee_width_,
+ bool specified;
+ CTOR(report_t, payee_width_) { specified = false; }
+ DO_(args) { value = args[1].to_long(); specified = true; });
+ OPTION__(report_t, account_width_,
+ bool specified;
+ CTOR(report_t, account_width_) { specified = false; }
+ DO_(args) { value = args[1].to_long(); specified = true; });
+ OPTION__(report_t, amount_width_,
+ bool specified;
+ CTOR(report_t, amount_width_) { specified = false; }
+ DO_(args) { value = args[1].to_long(); specified = true; });
+ OPTION__(report_t, total_width_,
+ bool specified;
+ CTOR(report_t, total_width_) { specified = false; }
+ DO_(args) { value = args[1].to_long(); specified = true; });
+};
+
+
+template <class Type = post_t,
+ class handler_ptr = post_handler_ptr,
+ void (report_t::*report_method)(handler_ptr) =
+ &report_t::posts_report>
+class reporter
+{
+ shared_ptr<item_handler<Type> > handler;
+
+ report_t& report;
+ string whence;
+
+public:
+ reporter(item_handler<Type> * _handler,
+ report_t& _report, const string& _whence)
+ : handler(_handler), report(_report), whence(_whence) {}
+
+ 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
+
+#endif // _REPORT_H