summaryrefslogtreecommitdiff
path: root/src/report.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/report.cc')
-rw-r--r--src/report.cc1307
1 files changed, 1307 insertions, 0 deletions
diff --git a/src/report.cc b/src/report.cc
new file mode 100644
index 00000000..2d9d7cc6
--- /dev/null
+++ b/src/report.cc
@@ -0,0 +1,1307 @@
+/*
+ * 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.
+ */
+
+#include <system.hh>
+
+#include "report.h"
+#include "session.h"
+#include "pool.h"
+#include "format.h"
+#include "query.h"
+#include "output.h"
+#include "iterators.h"
+#include "filters.h"
+#include "precmd.h"
+#include "stats.h"
+#include "generate.h"
+#include "draft.h"
+#include "xml.h"
+#include "emacs.h"
+
+namespace ledger {
+
+void report_t::normalize_options(const string& verb)
+{
+ // Patch up some of the reporting options based on what kind of
+ // command it was.
+
+#ifdef HAVE_ISATTY
+ if (! HANDLED(force_color)) {
+ if (! HANDLED(no_color) && isatty(STDOUT_FILENO))
+ HANDLER(color).on_only(string("?normalize"));
+ if (HANDLED(color) && ! isatty(STDOUT_FILENO))
+ HANDLER(color).off();
+ }
+ if (! HANDLED(force_pager)) {
+ if (HANDLED(pager_) && ! isatty(STDOUT_FILENO))
+ HANDLER(pager_).off();
+ }
+#endif
+
+ item_t::use_effective_date = (HANDLED(effective) &&
+ ! HANDLED(actual_dates));
+
+ commodity_pool_t::current_pool->keep_base = HANDLED(base);
+ commodity_pool_t::current_pool->get_quotes = session.HANDLED(download);
+
+ if (session.HANDLED(price_exp_))
+ commodity_pool_t::current_pool->quote_leeway =
+ session.HANDLER(price_exp_).value.as_long();
+
+ if (session.HANDLED(price_db_))
+ commodity_pool_t::current_pool->price_db = session.HANDLER(price_db_).str();
+ else
+ commodity_pool_t::current_pool->price_db = none;
+
+ if (HANDLED(date_format_))
+ set_date_format(HANDLER(date_format_).str().c_str());
+ if (HANDLED(datetime_format_))
+ set_datetime_format(HANDLER(datetime_format_).str().c_str());
+ if (HANDLED(start_of_week_)) {
+ if (optional<date_time::weekdays> weekday =
+ string_to_day_of_week(HANDLER(start_of_week_).str()))
+ start_of_week = *weekday;
+ }
+
+ if (verb == "print" || verb == "xact" || verb == "dump") {
+ HANDLER(related).on_only(string("?normalize"));
+ HANDLER(related_all).on_only(string("?normalize"));
+ }
+ else if (verb == "equity") {
+ HANDLER(equity).on_only(string("?normalize"));
+ }
+
+ if (verb == "print")
+ HANDLER(limit_).on(string("?normalize"), "actual");
+
+ if (! HANDLED(empty))
+ HANDLER(display_).on(string("?normalize"), "amount|(!post&total)");
+
+ if (verb[0] != 'b' && verb[0] != 'r')
+ HANDLER(base).on_only(string("?normalize"));
+
+ // If a time period was specified with -p, check whether it also gave a
+ // begin and/or end to the report period (though these can be overridden
+ // using -b or -e). Then, if no _duration_ was specified (such as monthly),
+ // then ignore the period since the begin/end are the only interesting
+ // details.
+ if (HANDLED(period_)) {
+ if (! HANDLED(sort_all_))
+ HANDLER(sort_xacts_).on_only(string("?normalize"));
+
+ date_interval_t interval(HANDLER(period_).str());
+
+ optional<date_t> begin = interval.begin(session.current_year);
+ optional<date_t> end = interval.end(session.current_year);
+
+ if (! HANDLED(begin_) && begin) {
+ string predicate = "date>=[" + to_iso_extended_string(*begin) + "]";
+ HANDLER(limit_).on(string("?normalize"), predicate);
+ }
+ if (! HANDLED(end_) && end) {
+ string predicate = "date<[" + to_iso_extended_string(*end) + "]";
+ HANDLER(limit_).on(string("?normalize"), predicate);
+ }
+
+ if (! interval.duration)
+ HANDLER(period_).off();
+ }
+
+ // If -j or -J were specified, set the appropriate format string now so as
+ // to avoid option ordering issues were we to have done it during the
+ // initial parsing of the options.
+ if (HANDLED(amount_data)) {
+ HANDLER(format_)
+ .on_with(string("?normalize"), HANDLER(plot_amount_format_).value);
+ }
+ else if (HANDLED(total_data)) {
+ HANDLER(format_)
+ .on_with(string("?normalize"), HANDLER(plot_total_format_).value);
+ }
+
+ // If the --exchange (-X) option was used, parse out any final price
+ // settings that may be there.
+ if (HANDLED(exchange_) &&
+ HANDLER(exchange_).str().find('=') != string::npos) {
+ value_t(0L).exchange_commodities(HANDLER(exchange_).str(), true,
+ terminus);
+ }
+
+ long cols = 0;
+ if (HANDLED(columns_))
+ cols = HANDLER(columns_).value.to_long();
+ else if (const char * columns = std::getenv("COLUMNS"))
+ cols = lexical_cast<long>(columns);
+ else
+ cols = 80L;
+
+ if (cols > 0) {
+ DEBUG("auto.columns", "cols = " << cols);
+
+ if (! HANDLER(date_width_).specified)
+ HANDLER(date_width_)
+ .on_with(none, static_cast<long>(format_date(CURRENT_DATE(),
+ FMT_PRINTED).length()));
+
+ long date_width = HANDLER(date_width_).value.to_long();
+ long payee_width = (HANDLER(payee_width_).specified ?
+ HANDLER(payee_width_).value.to_long() :
+ int(double(cols) * 0.263157));
+ long account_width = (HANDLER(account_width_).specified ?
+ HANDLER(account_width_).value.to_long() :
+ int(double(cols) * 0.302631));
+ long amount_width = (HANDLER(amount_width_).specified ?
+ HANDLER(amount_width_).value.to_long() :
+ int(double(cols) * 0.157894));
+ long total_width = (HANDLER(total_width_).specified ?
+ HANDLER(total_width_).value.to_long() :
+ amount_width);
+
+ DEBUG("auto.columns", "date_width = " << date_width);
+ DEBUG("auto.columns", "payee_width = " << payee_width);
+ DEBUG("auto.columns", "account_width = " << account_width);
+ DEBUG("auto.columns", "amount_width = " << amount_width);
+ DEBUG("auto.columns", "total_width = " << total_width);
+
+ if (! HANDLER(date_width_).specified &&
+ ! HANDLER(payee_width_).specified &&
+ ! HANDLER(account_width_).specified &&
+ ! HANDLER(amount_width_).specified &&
+ ! HANDLER(total_width_).specified) {
+ long total = (4 /* the spaces between */ + date_width + payee_width +
+ account_width + amount_width + total_width);
+ if (total > cols) {
+ DEBUG("auto.columns", "adjusting account down");
+ account_width -= total - cols;
+ DEBUG("auto.columns", "account_width now = " << account_width);
+ }
+ }
+
+ if (! HANDLER(date_width_).specified)
+ HANDLER(date_width_).on_with(string("?normalize"), date_width);
+ if (! HANDLER(payee_width_).specified)
+ HANDLER(payee_width_).on_with(string("?normalize"), payee_width);
+ if (! HANDLER(account_width_).specified)
+ HANDLER(account_width_).on_with(string("?normalize"), account_width);
+ if (! HANDLER(amount_width_).specified)
+ HANDLER(amount_width_).on_with(string("?normalize"), amount_width);
+ if (! HANDLER(total_width_).specified)
+ HANDLER(total_width_).on_with(string("?normalize"), total_width);
+ }
+}
+
+void report_t::parse_query_args(const value_t& args, const string& whence)
+{
+ query_t query(args, what_to_keep());
+ if (query) {
+ HANDLER(limit_).on(whence, query.text());
+
+ DEBUG("report.predicate",
+ "Predicate = " << HANDLER(limit_).str());
+ }
+
+ if (query.tokens_remaining()) {
+ query.parse_again();
+ if (query) {
+ HANDLER(display_).on(whence, query.text());
+
+ DEBUG("report.predicate",
+ "Display predicate = " << HANDLER(display_).str());
+ }
+ }
+}
+
+void report_t::posts_report(post_handler_ptr handler)
+{
+ journal_posts_iterator walker(*session.journal.get());
+ pass_down_posts(chain_post_handlers(*this, handler), walker);
+ session.journal->clear_xdata();
+}
+
+void report_t::generate_report(post_handler_ptr handler)
+{
+ HANDLER(limit_).on(string("#generate"), "actual");
+
+ 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);
+}
+
+void report_t::xact_report(post_handler_ptr handler, xact_t& xact)
+{
+ xact_posts_iterator walker(xact);
+ pass_down_posts(chain_post_handlers(*this, handler), walker);
+ xact.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);
+
+ 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_))
+ 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();
+}
+
+void report_t::commodities_report(post_handler_ptr handler)
+{
+ posts_commodities_iterator walker(*session.journal.get());
+ pass_down_posts(chain_post_handlers(*this, handler), walker);
+ session.journal->clear_xdata();
+}
+
+value_t report_t::fn_amount_expr(call_scope_t& scope)
+{
+ return HANDLER(amount_).expr.calc(scope);
+}
+
+value_t report_t::fn_total_expr(call_scope_t& scope)
+{
+ return HANDLER(total_).expr.calc(scope);
+}
+
+value_t report_t::fn_display_amount(call_scope_t& scope)
+{
+ return HANDLER(display_amount_).expr.calc(scope);
+}
+
+value_t report_t::fn_display_total(call_scope_t& scope)
+{
+ return HANDLER(display_total_).expr.calc(scope);
+}
+
+value_t report_t::fn_market(call_scope_t& scope)
+{
+ interactive_t args(scope, "a&ts");
+
+ value_t result;
+ optional<datetime_t> moment = (args.has(1) ?
+ args.get<datetime_t>(1) :
+ optional<datetime_t>());
+ if (args.has(2))
+ result = args.value_at(0).exchange_commodities(args.get<string>(2),
+ /* add_prices= */ false,
+ moment);
+ else
+ result = args.value_at(0).value(true, moment);
+
+ if (! result.is_null())
+ return result;
+
+ return args.value_at(0);
+}
+
+value_t report_t::fn_get_at(call_scope_t& scope)
+{
+ interactive_t args(scope, "Sl");
+
+ DEBUG("report.get_at", "get_at[0] = " << args.value_at(0));
+ DEBUG("report.get_at", "get_at[1] = " << args.value_at(1));
+
+ if (args.get<long>(1) == 0) {
+ if (! args.value_at(0).is_sequence())
+ return args.value_at(0);
+ } else {
+ if (! args.value_at(0).is_sequence())
+ throw_(std::runtime_error,
+ _("Attempting to get argument at index %1 from %2")
+ << args.get<long>(1) << args.value_at(0).label());
+ }
+ return args.get<const value_t::sequence_t&>(0)[args.get<long>(1)];
+}
+
+value_t report_t::fn_is_seq(call_scope_t& scope)
+{
+ return scope.value().is_sequence();
+}
+
+value_t report_t::fn_strip(call_scope_t& args)
+{
+ return args.value().strip_annotations(what_to_keep());
+}
+
+value_t report_t::fn_trim(call_scope_t& args)
+{
+ string temp(args.value().to_string());
+ scoped_array<char> buf(new char[temp.length() + 1]);
+ std::strcpy(buf.get(), temp.c_str());
+
+ const char * p = buf.get();
+ while (*p && std::isspace(*p))
+ p++;
+
+ const char * e = buf.get() + temp.length();
+ while (e > p && std::isspace(*e))
+ e--;
+
+ if (e == p) {
+ return string_value(empty_string);
+ }
+ else if (e < p) {
+ assert(false);
+ return string_value(empty_string);
+ }
+ else {
+ return string_value(string(p, e - p));
+ }
+}
+
+value_t report_t::fn_scrub(call_scope_t& args)
+{
+ value_t temp(args.value().strip_annotations(what_to_keep()));
+ if (HANDLED(base))
+ return temp;
+ else
+ return temp.unreduced();
+}
+
+value_t report_t::fn_rounded(call_scope_t& args)
+{
+ return args.value().rounded();
+}
+
+value_t report_t::fn_unrounded(call_scope_t& args)
+{
+ return args.value().unrounded();
+}
+
+value_t report_t::fn_quantity(call_scope_t& scope)
+{
+ interactive_t args(scope, "a");
+ return args.get<amount_t>(0).number();
+}
+
+value_t report_t::fn_floor(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ return args.value_at(0).floored();
+}
+
+value_t report_t::fn_abs(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ return args.value_at(0).abs();
+}
+
+value_t report_t::fn_truncated(call_scope_t& scope)
+{
+ interactive_t args(scope, "v&ll");
+ return string_value(format_t::truncate
+ (args.get<string>(0),
+ args.has(1) && args.get<int>(1) > 0 ? args.get<int>(1) : 0,
+ args.has(2) ? args.get<int>(2) : 0));
+}
+
+value_t report_t::fn_justify(call_scope_t& scope)
+{
+ interactive_t args(scope, "vl&lbb");
+ std::ostringstream out;
+ args.value_at(0)
+ .print(out, args.get<int>(1),
+ args.has(2) ? args.get<int>(2) : -1,
+ args.has(3) ? args.get<bool>(3) : false,
+ args.has(4) ? args.get<bool>(4) : false);
+ return string_value(out.str());
+}
+
+value_t report_t::fn_quoted(call_scope_t& scope)
+{
+ interactive_t args(scope, "s");
+ std::ostringstream out;
+
+ out << '"';
+ foreach (const char ch, args.get<string>(0)) {
+ if (ch == '"')
+ out << "\\\"";
+ else
+ out << ch;
+ }
+ out << '"';
+
+ return string_value(out.str());
+}
+
+value_t report_t::fn_join(call_scope_t& scope)
+{
+ interactive_t args(scope, "s");
+ std::ostringstream out;
+
+ foreach (const char ch, args.get<string>(0)) {
+ if (ch != '\n')
+ out << ch;
+ else
+ out << "\\n";
+ }
+ return string_value(out.str());
+}
+
+value_t report_t::fn_format_date(call_scope_t& scope)
+{
+ interactive_t args(scope, "d&s");
+ if (args.has(1))
+ return string_value(format_date(args.get<date_t>(0), FMT_CUSTOM,
+ args.get<string>(1).c_str()));
+ else
+ return string_value(format_date(args.get<date_t>(0), FMT_PRINTED));
+}
+
+value_t report_t::fn_ansify_if(call_scope_t& scope)
+{
+ interactive_t args(scope, "v&s");
+
+ if (args.has(1)) {
+ string color = args.get<string>(1);
+ std::ostringstream buf;
+ if (color == "black") buf << "\033[30m";
+ else if (color == "red") buf << "\033[31m";
+ else if (color == "green") buf << "\033[32m";
+ else if (color == "yellow") buf << "\033[33m";
+ else if (color == "blue") buf << "\033[34m";
+ else if (color == "magenta") buf << "\033[35m";
+ else if (color == "cyan") buf << "\033[36m";
+ else if (color == "white") buf << "\033[37m";
+ else if (color == "bold") buf << "\033[1m";
+ else if (color == "underline") buf << "\033[4m";
+ else if (color == "blink") buf << "\033[5m";
+ buf << args.value_at(0);
+ buf << "\033[0m";
+ return string_value(buf.str());
+ } else {
+ return args.value_at(0);
+ }
+}
+
+value_t report_t::fn_percent(call_scope_t& scope)
+{
+ interactive_t args(scope, "aa");
+ return (amount_t("100.00%") *
+ (args.get<amount_t>(0) / args.get<amount_t>(1)).number());
+}
+
+value_t report_t::fn_price(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ return args.value_at(0).price();
+}
+
+value_t report_t::fn_lot_date(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ if (args.value_at(0).has_annotation()) {
+ const annotation_t& details(args.value_at(0).annotation());
+ if (details.date)
+ return *details.date;
+ }
+ return NULL_VALUE;
+}
+
+value_t report_t::fn_lot_price(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ if (args.value_at(0).has_annotation()) {
+ const annotation_t& details(args.value_at(0).annotation());
+ if (details.price)
+ return *details.price;
+ }
+ return NULL_VALUE;
+}
+
+value_t report_t::fn_lot_tag(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ if (args.value_at(0).has_annotation()) {
+ const annotation_t& details(args.value_at(0).annotation());
+ if (details.tag)
+ return string_value(*details.tag);
+ }
+ return NULL_VALUE;
+}
+
+value_t report_t::fn_to_boolean(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ args.value_at(0).in_place_cast(value_t::BOOLEAN);
+ return args.value_at(0);
+}
+
+value_t report_t::fn_to_int(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ args.value_at(0).in_place_cast(value_t::INTEGER);
+ return args.value_at(0);
+}
+
+value_t report_t::fn_to_datetime(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ args.value_at(0).in_place_cast(value_t::DATETIME);
+ return args.value_at(0);
+}
+
+value_t report_t::fn_to_date(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ args.value_at(0).in_place_cast(value_t::DATE);
+ return args.value_at(0);
+}
+
+value_t report_t::fn_to_amount(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ args.value_at(0).in_place_cast(value_t::AMOUNT);
+ return args.value_at(0);
+}
+
+value_t report_t::fn_to_balance(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ args.value_at(0).in_place_cast(value_t::BALANCE);
+ return args.value_at(0);
+}
+
+value_t report_t::fn_to_string(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ args.value_at(0).in_place_cast(value_t::STRING);
+ return args.value_at(0);
+}
+
+value_t report_t::fn_to_mask(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ args.value_at(0).in_place_cast(value_t::MASK);
+ return args.value_at(0);
+}
+
+value_t report_t::fn_to_sequence(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ args.value_at(0).in_place_cast(value_t::SEQUENCE);
+ return args.value_at(0);
+}
+
+namespace {
+ value_t fn_black(call_scope_t&) {
+ return string_value("black");
+ }
+ value_t fn_blink(call_scope_t&) {
+ return string_value("blink");
+ }
+ value_t fn_blue(call_scope_t&) {
+ return string_value("blue");
+ }
+ value_t fn_bold(call_scope_t&) {
+ return string_value("bold");
+ }
+ value_t fn_cyan(call_scope_t&) {
+ return string_value("cyan");
+ }
+ value_t fn_green(call_scope_t&) {
+ return string_value("green");
+ }
+ value_t fn_magenta(call_scope_t&) {
+ return string_value("magenta");
+ }
+ value_t fn_red(call_scope_t&) {
+ return string_value("red");
+ }
+ value_t fn_underline(call_scope_t&) {
+ return string_value("underline");
+ }
+ value_t fn_white(call_scope_t&) {
+ return string_value("white");
+ }
+ value_t fn_yellow(call_scope_t&) {
+ return string_value("yellow");
+ }
+ value_t fn_false(call_scope_t&) {
+ return false;
+ }
+ value_t fn_null(call_scope_t&) {
+ return NULL_VALUE;
+ }
+}
+
+value_t report_t::reload_command(call_scope_t&)
+{
+ session.close_journal_files();
+ session.read_journal_files();
+ return true;
+}
+
+value_t report_t::echo_command(call_scope_t& scope)
+{
+ interactive_t args(scope, "s");
+ std::ostream& out(output_stream);
+ out << args.get<string>(0) << std::endl;
+ return true;
+}
+
+option_t<report_t> * report_t::lookup_option(const char * p)
+{
+ switch (*p) {
+ case '%':
+ OPT_CH(percent);
+ break;
+ case 'A':
+ OPT_CH(average);
+ break;
+ case 'B':
+ OPT_CH(basis);
+ break;
+ case 'C':
+ OPT_CH(cleared);
+ break;
+ case 'D':
+ OPT_CH(daily);
+ break;
+ case 'E':
+ OPT_CH(empty);
+ break;
+ case 'F':
+ OPT_CH(format_);
+ break;
+ case 'G':
+ OPT_CH(gain);
+ break;
+ case 'I':
+ OPT_CH(price);
+ break;
+ case 'J':
+ OPT_CH(total_data);
+ break;
+ case 'L':
+ OPT_CH(actual);
+ break;
+ case 'M':
+ OPT_CH(monthly);
+ break;
+ case 'O':
+ OPT_CH(quantity);
+ break;
+ case 'P':
+ OPT_CH(by_payee);
+ break;
+ case 'R':
+ OPT_CH(real);
+ break;
+ case 'S':
+ OPT_CH(sort_);
+ break;
+ case 'T':
+ OPT_CH(total_);
+ break;
+ case 'U':
+ OPT_CH(uncleared);
+ break;
+ case 'V':
+ OPT_CH(market);
+ break;
+ case 'W':
+ OPT_CH(weekly);
+ break;
+ case 'X':
+ OPT_CH(exchange_);
+ break;
+ case 'Y':
+ OPT_CH(yearly);
+ break;
+ case 'a':
+ OPT(abbrev_len_);
+ else OPT_(account_);
+ else OPT(actual);
+ else OPT(actual_dates);
+ else OPT(add_budget);
+ else OPT(amount_);
+ else OPT(amount_data);
+ else OPT(anon);
+ else OPT_ALT(color, ansi);
+ else OPT(average);
+ else OPT(account_width_);
+ else OPT(amount_width_);
+ break;
+ case 'b':
+ OPT(balance_format_);
+ else OPT(base);
+ else OPT_ALT(basis, cost);
+ else OPT_(begin_);
+ else OPT(budget);
+ else OPT(by_payee);
+ break;
+ case 'c':
+ OPT(csv_format_);
+ else OPT(cleared);
+ else OPT(collapse);
+ else OPT(collapse_if_zero);
+ else OPT(color);
+ else OPT(columns_);
+ else OPT_ALT(basis, cost);
+ else OPT_(current);
+ break;
+ case 'd':
+ OPT(daily);
+ else OPT(date_);
+ else OPT(date_format_);
+ else OPT(datetime_format_);
+ else OPT(depth_);
+ else OPT(deviation);
+ else OPT_(display_);
+ else OPT(display_amount_);
+ else OPT(display_total_);
+ else OPT_ALT(dow, days-of-week);
+ else OPT(date_width_);
+ break;
+ case 'e':
+ OPT(effective);
+ else OPT(empty);
+ else OPT_(end_);
+ else OPT(equity);
+ else OPT(exact);
+ else OPT(exchange_);
+ break;
+ case 'f':
+ OPT(flat);
+ else OPT_ALT(forecast_while_, forecast_);
+ else OPT(forecast_years_);
+ else OPT(format_);
+ else OPT(force_color);
+ else OPT(force_pager);
+ else OPT_ALT(head_, first_);
+ break;
+ case 'g':
+ OPT(gain);
+ break;
+ case 'h':
+ OPT(head_);
+ break;
+ case 'i':
+ OPT(invert);
+ break;
+ case 'j':
+ OPT_CH(amount_data);
+ break;
+ case 'l':
+ OPT_(limit_);
+ else OPT(lot_dates);
+ else OPT(lot_prices);
+ else OPT(lot_tags);
+ else OPT(lots);
+ else OPT(lots_actual);
+ else OPT_ALT(tail_, last_);
+ break;
+ case 'm':
+ OPT(market);
+ else OPT(monthly);
+ break;
+ case 'n':
+ OPT_CH(collapse);
+ else OPT(no_color);
+ else OPT(no_total);
+ else OPT(now_);
+ break;
+ case 'o':
+ OPT(only_);
+ else OPT_(output_);
+ break;
+ case 'p':
+ OPT(pager_);
+ else OPT(payee_);
+ else OPT(pending);
+ else OPT(percent);
+ else OPT_(period_);
+ else OPT_ALT(sort_xacts_, period_sort_);
+ else OPT(plot_amount_format_);
+ else OPT(plot_total_format_);
+ else OPT(price);
+ else OPT(prices_format_);
+ else OPT(pricedb_format_);
+ else OPT(print_format_);
+ else OPT(payee_width_);
+ else OPT(prepend_format_);
+ break;
+ case 'q':
+ OPT(quantity);
+ else OPT(quarterly);
+ break;
+ case 'r':
+ OPT(raw);
+ else OPT(real);
+ else OPT(register_format_);
+ else OPT_(related);
+ else OPT(related_all);
+ else OPT(revalued);
+ else OPT(revalued_only);
+ else OPT(revalued_total_);
+ break;
+ case 's':
+ OPT(sort_);
+ else OPT(sort_all_);
+ else OPT(sort_xacts_);
+ else OPT_(subtotal);
+ else OPT(start_of_week_);
+ else OPT(seed_);
+ break;
+ case 't':
+ OPT_CH(amount_);
+ else OPT(tail_);
+ else OPT(total_);
+ else OPT(total_data);
+ else OPT(truncate_);
+ else OPT(total_width_);
+ break;
+ case 'u':
+ OPT(unbudgeted);
+ else OPT(uncleared);
+ else OPT(unrealized);
+ else OPT(unrealized_gains_);
+ else OPT(unrealized_losses_);
+ else OPT(unround);
+ else OPT(unsorted);
+ break;
+ case 'w':
+ OPT(weekly);
+ else OPT_(wide);
+ break;
+ case 'y':
+ OPT_CH(date_format_);
+ else OPT(yearly);
+ break;
+ }
+ return NULL;
+}
+
+void report_t::define(const symbol_t::kind_t kind, const string& name,
+ expr_t::ptr_op_t def)
+{
+ session.define(kind, name, def);
+}
+
+expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
+ const string& name)
+{
+ if (expr_t::ptr_op_t def = session.lookup(kind, name))
+ return def;
+
+ const char * p = name.c_str();
+
+ switch (kind) {
+ case symbol_t::FUNCTION:
+ // Support 2.x's single-letter value expression names.
+ if (*(p + 1) == '\0') {
+ switch (*p) {
+ case 'd':
+ case 'm':
+ return MAKE_FUNCTOR(report_t::fn_now);
+ case 'P':
+ return MAKE_FUNCTOR(report_t::fn_market);
+ case 't':
+ return MAKE_FUNCTOR(report_t::fn_display_amount);
+ case 'T':
+ return MAKE_FUNCTOR(report_t::fn_display_total);
+ case 'U':
+ return MAKE_FUNCTOR(report_t::fn_abs);
+ case 'S':
+ return MAKE_FUNCTOR(report_t::fn_strip);
+ case 'i':
+ throw_(std::runtime_error,
+ _("The i value expression variable is no longer supported"));
+ case 'A':
+ throw_(std::runtime_error,
+ _("The A value expression variable is no longer supported"));
+ case 'v':
+ case 'V':
+ throw_(std::runtime_error,
+ _("The V and v value expression variables are no longer supported"));
+ case 'I':
+ case 'B':
+ throw_(std::runtime_error,
+ _("The I and B value expression variables are no longer supported"));
+ case 'g':
+ case 'G':
+ throw_(std::runtime_error,
+ _("The G and g value expression variables are no longer supported"));
+ default:
+ return NULL;
+ }
+ }
+
+ switch (*p) {
+ case 'a':
+ if (is_eq(p, "amount_expr"))
+ return MAKE_FUNCTOR(report_t::fn_amount_expr);
+ else if (is_eq(p, "ansify_if"))
+ return MAKE_FUNCTOR(report_t::fn_ansify_if);
+ else if (is_eq(p, "abs"))
+ return MAKE_FUNCTOR(report_t::fn_abs);
+ break;
+
+ case 'b':
+ if (is_eq(p, "black"))
+ return WRAP_FUNCTOR(fn_black);
+ else if (is_eq(p, "blink"))
+ return WRAP_FUNCTOR(fn_blink);
+ else if (is_eq(p, "blue"))
+ return WRAP_FUNCTOR(fn_blue);
+ else if (is_eq(p, "bold"))
+ return WRAP_FUNCTOR(fn_bold);
+ break;
+
+ case 'c':
+ if (is_eq(p, "cyan"))
+ return WRAP_FUNCTOR(fn_cyan);
+ break;
+
+ case 'd':
+ if (is_eq(p, "display_amount"))
+ return MAKE_FUNCTOR(report_t::fn_display_amount);
+ else if (is_eq(p, "display_total"))
+ return MAKE_FUNCTOR(report_t::fn_display_total);
+ else if (is_eq(p, "date"))
+ return MAKE_FUNCTOR(report_t::fn_now);
+ break;
+
+ case 'f':
+ if (is_eq(p, "format_date"))
+ return MAKE_FUNCTOR(report_t::fn_format_date);
+ else if (is_eq(p, "floor"))
+ return MAKE_FUNCTOR(report_t::fn_floor);
+ break;
+
+ case 'g':
+ if (is_eq(p, "get_at"))
+ return MAKE_FUNCTOR(report_t::fn_get_at);
+ else if (is_eq(p, "green"))
+ return WRAP_FUNCTOR(fn_green);
+ break;
+
+ case 'i':
+ if (is_eq(p, "is_seq"))
+ return MAKE_FUNCTOR(report_t::fn_is_seq);
+ break;
+
+ case 'j':
+ if (is_eq(p, "justify"))
+ return MAKE_FUNCTOR(report_t::fn_justify);
+ else if (is_eq(p, "join"))
+ return MAKE_FUNCTOR(report_t::fn_join);
+ break;
+
+ case 'm':
+ if (is_eq(p, "market"))
+ return MAKE_FUNCTOR(report_t::fn_market);
+ else if (is_eq(p, "magenta"))
+ return WRAP_FUNCTOR(fn_magenta);
+ break;
+
+ case 'n':
+ if (is_eq(p, "null"))
+ return WRAP_FUNCTOR(fn_null);
+ else if (is_eq(p, "now"))
+ return MAKE_FUNCTOR(report_t::fn_now);
+ break;
+
+ case 'o':
+ if (is_eq(p, "options"))
+ return MAKE_FUNCTOR(report_t::fn_options);
+ break;
+
+ case 'p':
+ if (is_eq(p, "post"))
+ return WRAP_FUNCTOR(fn_false);
+ else if (is_eq(p, "percent"))
+ return MAKE_FUNCTOR(report_t::fn_percent);
+ else if (is_eq(p, "price"))
+ return MAKE_FUNCTOR(report_t::fn_price);
+ break;
+
+ case 'q':
+ if (is_eq(p, "quoted"))
+ return MAKE_FUNCTOR(report_t::fn_quoted);
+ else if (is_eq(p, "quantity"))
+ return MAKE_FUNCTOR(report_t::fn_quantity);
+ break;
+
+ case 'r':
+ if (is_eq(p, "rounded"))
+ return MAKE_FUNCTOR(report_t::fn_rounded);
+ else if (is_eq(p, "red"))
+ return WRAP_FUNCTOR(fn_red);
+ break;
+
+ case 's':
+ if (is_eq(p, "scrub"))
+ return MAKE_FUNCTOR(report_t::fn_scrub);
+ else if (is_eq(p, "strip"))
+ return MAKE_FUNCTOR(report_t::fn_strip);
+ break;
+
+ case 't':
+ if (is_eq(p, "truncated"))
+ return MAKE_FUNCTOR(report_t::fn_truncated);
+ else if (is_eq(p, "total_expr"))
+ return MAKE_FUNCTOR(report_t::fn_total_expr);
+ else if (is_eq(p, "today"))
+ return MAKE_FUNCTOR(report_t::fn_today);
+ else if (is_eq(p, "t"))
+ return MAKE_FUNCTOR(report_t::fn_display_amount);
+ else if (is_eq(p, "trim"))
+ return MAKE_FUNCTOR(report_t::fn_trim);
+ else if (is_eq(p, "to_boolean"))
+ return MAKE_FUNCTOR(report_t::fn_to_boolean);
+ else if (is_eq(p, "to_int"))
+ return MAKE_FUNCTOR(report_t::fn_to_int);
+ else if (is_eq(p, "to_datetime"))
+ return MAKE_FUNCTOR(report_t::fn_to_datetime);
+ else if (is_eq(p, "to_date"))
+ return MAKE_FUNCTOR(report_t::fn_to_date);
+ else if (is_eq(p, "to_amount"))
+ return MAKE_FUNCTOR(report_t::fn_to_amount);
+ else if (is_eq(p, "to_balance"))
+ return MAKE_FUNCTOR(report_t::fn_to_balance);
+ else if (is_eq(p, "to_string"))
+ return MAKE_FUNCTOR(report_t::fn_to_string);
+ else if (is_eq(p, "to_mask"))
+ return MAKE_FUNCTOR(report_t::fn_to_mask);
+ else if (is_eq(p, "to_sequence"))
+ return MAKE_FUNCTOR(report_t::fn_to_sequence);
+ break;
+
+ case 'T':
+ if (is_eq(p, "T"))
+ return MAKE_FUNCTOR(report_t::fn_display_total);
+ break;
+
+ case 'u':
+ if (is_eq(p, "underline"))
+ return WRAP_FUNCTOR(fn_underline);
+ else if (is_eq(p, "unrounded"))
+ return MAKE_FUNCTOR(report_t::fn_unrounded);
+ break;
+
+ case 'w':
+ if (is_eq(p, "white"))
+ return WRAP_FUNCTOR(fn_white);
+ break;
+
+ case 'y':
+ if (is_eq(p, "yellow"))
+ return WRAP_FUNCTOR(fn_yellow);
+ break;
+ }
+
+ // Check if they are trying to access an option's setting or value.
+ if (option_t<report_t> * handler = lookup_option(p))
+ return MAKE_OPT_FUNCTOR(report_t, handler);
+ break;
+
+ case symbol_t::OPTION:
+ if (option_t<report_t> * handler = lookup_option(p))
+ return MAKE_OPT_HANDLER(report_t, handler);
+ break;
+
+ case symbol_t::COMMAND:
+ switch (*p) {
+ case 'b':
+ if (*(p + 1) == '\0' || is_eq(p, "bal") || is_eq(p, "balance")) {
+ return expr_t::op_t::wrap_functor
+ (reporter<account_t, acct_handler_ptr, &report_t::accounts_report>
+ (new format_accounts(*this, report_format(HANDLER(balance_format_)),
+ maybe_format(HANDLER(prepend_format_))),
+ *this, "#balance"));
+ }
+ else if (is_eq(p, "budget")) {
+ HANDLER(amount_).set_expr(string("#budget"), "(amount, 0)");
+
+ budget_flags |= BUDGET_WRAP_VALUES;
+ if (! (budget_flags & ~BUDGET_WRAP_VALUES))
+ budget_flags |= BUDGET_BUDGETED;
+
+ return expr_t::op_t::wrap_functor
+ (reporter<account_t, acct_handler_ptr, &report_t::accounts_report>
+ (new format_accounts(*this, report_format(HANDLER(budget_format_)),
+ maybe_format(HANDLER(prepend_format_))),
+ *this, "#budget"));
+ }
+ break;
+
+ case 'c':
+ if (is_eq(p, "csv")) {
+ return WRAP_FUNCTOR
+ (reporter<>
+ (new format_posts(*this, report_format(HANDLER(csv_format_)),
+ maybe_format(HANDLER(prepend_format_))),
+ *this, "#csv"));
+ }
+ else if (is_eq(p, "cleared")) {
+ HANDLER(amount_).set_expr(string("#cleared"),
+ "(amount, cleared ? amount : 0)");
+
+ return expr_t::op_t::wrap_functor
+ (reporter<account_t, acct_handler_ptr, &report_t::accounts_report>
+ (new format_accounts(*this, report_format(HANDLER(cleared_format_)),
+ maybe_format(HANDLER(prepend_format_))),
+ *this, "#cleared"));
+ }
+ break;
+
+ case 'e':
+ if (is_eq(p, "equity"))
+ return WRAP_FUNCTOR
+ (reporter<>
+ (new format_posts(*this, report_format(HANDLER(print_format_))),
+ *this, "#equity"));
+ else if (is_eq(p, "entry"))
+ return WRAP_FUNCTOR(xact_command);
+ else if (is_eq(p, "emacs"))
+ return WRAP_FUNCTOR
+ (reporter<>(new format_emacs_posts(output_stream), *this, "#emacs"));
+ else if (is_eq(p, "echo"))
+ return MAKE_FUNCTOR(report_t::echo_command);
+ break;
+
+ case 'p':
+ if (*(p + 1) == '\0' || is_eq(p, "print"))
+ return WRAP_FUNCTOR
+ (reporter<>
+ (new format_posts(*this, report_format(HANDLER(print_format_)),
+ HANDLED(raw)), *this, "#print"));
+ else if (is_eq(p, "prices"))
+ return expr_t::op_t::wrap_functor
+ (reporter<post_t, post_handler_ptr, &report_t::commodities_report>
+ (new format_posts(*this, report_format(HANDLER(prices_format_)),
+ maybe_format(HANDLER(prepend_format_))),
+ *this, "#prices"));
+ else if (is_eq(p, "pricedb"))
+ return expr_t::op_t::wrap_functor
+ (reporter<post_t, post_handler_ptr, &report_t::commodities_report>
+ (new format_posts(*this, report_format(HANDLER(pricedb_format_)),
+ maybe_format(HANDLER(prepend_format_))),
+ *this, "#pricedb"));
+ break;
+
+ case 'r':
+ if (*(p + 1) == '\0' || is_eq(p, "reg") || is_eq(p, "register"))
+ return WRAP_FUNCTOR
+ (reporter<>
+ (new format_posts(*this, report_format(HANDLER(register_format_)),
+ false, maybe_format(HANDLER(prepend_format_))),
+ *this, "#register"));
+ else if (is_eq(p, "reload"))
+ return MAKE_FUNCTOR(report_t::reload_command);
+ break;
+
+ case 's':
+ if (is_eq(p, "stats") || is_eq(p, "stat"))
+ return WRAP_FUNCTOR(report_statistics);
+ break;
+
+ case 'x':
+ if (is_eq(p, "xact"))
+ return WRAP_FUNCTOR(xact_command);
+ else if (is_eq(p, "xml"))
+ return WRAP_FUNCTOR(reporter<>(new format_xml(*this), *this, "#xml"));
+ break;
+ }
+ break;
+
+ case symbol_t::PRECOMMAND:
+ switch (*p) {
+ case 'a':
+ if (is_eq(p, "args"))
+ return WRAP_FUNCTOR(args_command);
+ break;
+ case 'e':
+ if (is_eq(p, "eval"))
+ return WRAP_FUNCTOR(eval_command);
+ break;
+ case 'f':
+ if (is_eq(p, "format"))
+ return WRAP_FUNCTOR(format_command);
+ break;
+ case 'g':
+ if (is_eq(p, "generate"))
+ return expr_t::op_t::wrap_functor
+ (reporter<post_t, post_handler_ptr, &report_t::generate_report>
+ (new format_posts(*this, report_format(HANDLER(print_format_)),
+ false), *this, "#generate"));
+ case 'p':
+ if (is_eq(p, "parse"))
+ return WRAP_FUNCTOR(parse_command);
+ else if (is_eq(p, "period"))
+ return WRAP_FUNCTOR(period_command);
+ break;
+ case 't':
+ if (is_eq(p, "template"))
+ return WRAP_FUNCTOR(template_command);
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+} // namespace ledger