/* * 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 #include "report.h" #include "session.h" #include "unistring.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::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(HANDLER(seed_).value.to_long()) : 0, HANDLED(head_) ? static_cast(HANDLER(head_).value.to_long()) : 50); pass_down_posts(chain_post_handlers(*this, handler), walker); } 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); scoped_ptr iter; if (! HANDLED(sort_)) { iter.reset(new basic_accounts_iterator(*session.journal->master)); } else { expr_t sort_expr(HANDLER(sort_).str()); sort_expr.set_context(this); iter.reset(new sorted_accounts_iterator(*session.journal->master, sort_expr, HANDLED(flat))); } 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 moment = (args.has(1) ? args.get(1) : optional()); if (args.has(2)) result = args.value_at(0).exchange_commodities(args.get(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(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(1) << args.value_at(0).label()); } return args.get(0)[args.get(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_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(0).number(); } 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(0), args.has(1) && args.get(1) > 0 ? args.get(1) : 0, args.has(2) ? args.get(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(1), args.has(2) ? args.get(2) : -1, args.has(3) ? args.get(3) : false, args.has(4) ? args.get(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(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(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(0), FMT_CUSTOM, args.get(1).c_str())); else return string_value(format_date(args.get(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(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(0) / args.get(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_account_total(call_scope_t& args) { account_t * acct = NULL; string name; if (args[0].is_string()) { name = args[0].as_string(); acct = session.journal->find_account(name, false); } else if (args[0].is_mask()) { name = args[0].as_mask().str(); acct = session.journal->find_account_re(name); } else { throw_(std::runtime_error, _("Expected string or mask for argument 1, but received %1") << args[0].label()); } if (! acct) throw_(std::runtime_error, _("Could not find an account matching ") << name); return acct->amount(); } value_t report_t::fn_lot_date(call_scope_t& scope) { interactive_t args(scope, "v"); if (args.value_at(0).is_annotated()) { 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).is_annotated()) { 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).is_annotated()) { const annotation_t& details(args.value_at(0).annotation()); if (details.tag) return string_value(*details.tag); } return NULL_VALUE; } 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; } template class reporter { shared_ptr > handler; report_t& report; string whence; public: reporter(item_handler * _handler, report_t& _report, const string& _whence) : handler(_handler), report(_report), whence(_whence) {} value_t operator()(call_scope_t& args) { if (args.size() > 0) { query_t query(args.value(), report.what_to_keep()); if (! query) throw_(std::runtime_error, _("Invalid query predicate: %1") << query.text()); report.HANDLER(limit_).on(whence, query.text()); DEBUG("report.predicate", "Predicate = " << report.HANDLER(limit_).str()); if (query.tokens_remaining()) { query.parse_again(); if (! query) throw_(std::runtime_error, _("Invalid display predicate: %1") << query.text()); report.HANDLER(display_).on(whence, query.text()); DEBUG("report.predicate", "Display predicate = " << report.HANDLER(display_).str()); } } (report.*report_method)(handler_ptr(handler)); return true; } }; } 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(0) << std::endl; return true; } bool report_t::maybe_import(const string& module) { if (lookup(symbol_t::OPTION, "import_")) { expr_t(string("import_(\"") + module + "\")").calc(*this); return true; } return false; } option_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_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(pricesdb_format_); else OPT(print_format_); else OPT(payee_width_); 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(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: 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); else if (is_eq(p, "account_total")) return MAKE_FUNCTOR(report_t::fn_account_total); 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); 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); 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 * handler = lookup_option(p)) return MAKE_OPT_FUNCTOR(report_t, handler); break; case symbol_t::OPTION: if (option_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 (new format_accounts(*this, report_format(HANDLER(balance_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 (new format_accounts(*this, report_format(HANDLER(budget_format_))), *this, "#budget")); } break; case 'c': if (is_eq(p, "csv")) { return WRAP_FUNCTOR (reporter<> (new format_posts(*this, report_format(HANDLER(csv_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 (new format_accounts(*this, report_format(HANDLER(cleared_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 (new format_posts(*this, report_format(HANDLER(prices_format_))), *this, "#prices")); else if (is_eq(p, "pricesdb")) return expr_t::op_t::wrap_functor (reporter (new format_posts(*this, report_format(HANDLER(pricesdb_format_))), *this, "#pricesdb")); else if (is_eq(p, "python") && maybe_import("ledger.interp")) return session.lookup(symbol_t::COMMAND, "python"); 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_))), *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); else if (is_eq(p, "server") && maybe_import("ledger.server")) return session.lookup(symbol_t::COMMAND, "server"); 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 (new format_posts(*this, report_format(HANDLER(print_format_)), false), *this, "#generate")); case 'h': if (is_eq(p, "hello") && maybe_import("ledger.hello")) return session.lookup(symbol_t::PRECOMMAND, "hello"); break; 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