If set, the COLUMNS environment variable will take precedence over terminal width. However, please note that COLUMNS is usually *not* exported by shells to child processes, so in most cases COLUMNS will be undefined for ledger---hence the motivation for this change. Terminal width is queried using ioctl() on stdin. For the sake of portability the querying is done only on platform where ioctl() is detected as supported at compile-time.
1749 lines
49 KiB
C++
1749 lines
49 KiB
C++
/*
|
|
* Copyright (c) 2003-2014, 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 "print.h"
|
|
#include "iterators.h"
|
|
#include "filters.h"
|
|
#include "precmd.h"
|
|
#include "select.h"
|
|
#include "stats.h"
|
|
#include "generate.h"
|
|
#include "draft.h"
|
|
#include "convert.h"
|
|
#include "ptree.h"
|
|
#include "emacs.h"
|
|
#include "org.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.
|
|
|
|
#if HAVE_ISATTY
|
|
if (! HANDLED(force_color)) {
|
|
if (! HANDLED(no_color) && isatty(STDOUT_FILENO))
|
|
HANDLER(color).on("?normalize");
|
|
if (HANDLED(color) && ! isatty(STDOUT_FILENO))
|
|
HANDLER(color).off();
|
|
}
|
|
if (! HANDLED(force_pager)) {
|
|
if (HANDLED(pager_) && ! isatty(STDOUT_FILENO))
|
|
HANDLER(pager_).off();
|
|
}
|
|
#endif
|
|
|
|
if (HANDLED(output_)) {
|
|
if (HANDLED(color) && ! HANDLED(force_color))
|
|
HANDLER(color).off();
|
|
if (HANDLED(pager_) && ! HANDLED(force_pager))
|
|
HANDLER(pager_).off();
|
|
}
|
|
|
|
item_t::use_aux_date = (HANDLED(aux_date) && ! HANDLED(primary_date));
|
|
|
|
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 =
|
|
lexical_cast<long>(session.HANDLER(price_exp_).value) * 3600L;
|
|
|
|
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;
|
|
}
|
|
|
|
long meta_width = -1;
|
|
|
|
if (! HANDLED(prepend_format_) && HANDLED(meta_)) {
|
|
if (! HANDLED(meta_width_)) {
|
|
string::size_type i = HANDLER(meta_).str().find(':');
|
|
if (i != string::npos) {
|
|
HANDLED(meta_width_).on("?normalize",
|
|
string(HANDLER(meta_).str(), i + 1));
|
|
HANDLED(meta_).on("?normalize",
|
|
string(HANDLER(meta_).str(), 0, i));
|
|
}
|
|
}
|
|
if (HANDLED(meta_width_)) {
|
|
HANDLER(prepend_format_)
|
|
.on("?normalize", string("%(justify(truncated(tag(\"") +
|
|
HANDLER(meta_).str() + "\"), " +
|
|
HANDLED(meta_width_).value + " - 1), " +
|
|
HANDLED(meta_width_).value + "))");
|
|
meta_width = lexical_cast<long>(HANDLED(meta_width_).value);
|
|
} else {
|
|
HANDLER(prepend_format_)
|
|
.on("?normalize", string("%(tag(\"") + HANDLER(meta_).str() + "\"))");
|
|
}
|
|
}
|
|
|
|
if (verb == "print" || verb == "xact" || verb == "dump") {
|
|
HANDLER(related_all).parent = this;
|
|
HANDLER(related_all).on("?normalize");
|
|
}
|
|
else if (verb == "equity") {
|
|
HANDLER(equity).on("?normalize");
|
|
}
|
|
|
|
if (verb[0] != 'b' && verb[0] != 'r')
|
|
HANDLER(base).on("?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_))
|
|
normalize_period();
|
|
|
|
// 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("?normalize", HANDLER(plot_amount_format_).value);
|
|
}
|
|
else if (HANDLED(total_data)) {
|
|
HANDLER(format_).on("?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);
|
|
}
|
|
|
|
if (HANDLED(percent)) {
|
|
commodity_t::decimal_comma_by_default = false;
|
|
if (HANDLED(market)) {
|
|
HANDLER(total_)
|
|
.on("?normalize",
|
|
"(__tmp = market(parent.total, value_date, exchange);"
|
|
" ((is_account & parent & __tmp) ?"
|
|
" percent(scrub(market(total, value_date, exchange)), "
|
|
" scrub(__tmp)) : 0))");
|
|
}
|
|
}
|
|
|
|
if (HANDLED(immediate) && HANDLED(market)) {
|
|
HANDLER(amount_)
|
|
.on("?normalize", "market(amount_expr, value_date, exchange)");
|
|
}
|
|
|
|
long cols = 0;
|
|
#if HAVE_IOCTL
|
|
struct winsize ws;
|
|
#endif
|
|
if (HANDLED(columns_))
|
|
cols = lexical_cast<long>(HANDLER(columns_).value);
|
|
else if (const char * columns = std::getenv("COLUMNS"))
|
|
cols = lexical_cast<long>(columns);
|
|
#if HAVE_IOCTL
|
|
else if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) != -1)
|
|
cols = ws.ws_col;
|
|
#endif
|
|
else
|
|
cols = 80L;
|
|
|
|
if (meta_width > 0)
|
|
cols -= meta_width;
|
|
|
|
if (cols > 0) {
|
|
DEBUG("auto.columns", "cols = " << cols);
|
|
|
|
long date_width = (HANDLED(date_width_) ?
|
|
lexical_cast<long>(HANDLER(date_width_).str()) :
|
|
static_cast<long>
|
|
(format_date(CURRENT_DATE(),FMT_PRINTED).length()));
|
|
long payee_width = (HANDLED(payee_width_) ?
|
|
lexical_cast<long>(HANDLER(payee_width_).str()) :
|
|
long(double(cols) * 0.263157));
|
|
long account_width = (HANDLED(account_width_) ?
|
|
lexical_cast<long>(HANDLER(account_width_).str()) :
|
|
long(double(cols) * 0.302631));
|
|
long amount_width = (HANDLED(amount_width_) ?
|
|
lexical_cast<long>(HANDLER(amount_width_).str()) :
|
|
long(double(cols) * 0.157894));
|
|
long total_width = (HANDLED(total_width_) ?
|
|
lexical_cast<long>(HANDLER(total_width_).str()) :
|
|
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 (! HANDLED(date_width_) &&
|
|
! HANDLED(payee_width_) &&
|
|
! HANDLED(account_width_) &&
|
|
! HANDLED(amount_width_) &&
|
|
! HANDLED(total_width_)) {
|
|
long total = (4 /* the spaces between */ + date_width + payee_width +
|
|
account_width + amount_width + total_width +
|
|
(HANDLED(dc) ? 1 + amount_width : 0));
|
|
while (total > cols && account_width > 5 && payee_width > 5) {
|
|
DEBUG("auto.columns", "adjusting account down");
|
|
if (total > cols) {
|
|
--account_width;
|
|
--total;
|
|
if (total > cols) {
|
|
--account_width;
|
|
--total;
|
|
}
|
|
}
|
|
if (total > cols) {
|
|
--payee_width;
|
|
--total;
|
|
}
|
|
DEBUG("auto.columns", "account_width now = " << account_width);
|
|
}
|
|
}
|
|
|
|
if (! HANDLED(meta_width_))
|
|
HANDLER(meta_width_).value = "0";
|
|
if (! HANDLED(prepend_width_))
|
|
HANDLER(prepend_width_).value = "0";
|
|
if (! HANDLED(date_width_))
|
|
HANDLER(date_width_).value = to_string(date_width);
|
|
if (! HANDLED(payee_width_))
|
|
HANDLER(payee_width_).value = to_string(payee_width);
|
|
if (! HANDLED(account_width_))
|
|
HANDLER(account_width_).value = to_string(account_width);
|
|
if (! HANDLED(amount_width_))
|
|
HANDLER(amount_width_).value = to_string(amount_width);
|
|
if (! HANDLED(total_width_))
|
|
HANDLER(total_width_).value = to_string(total_width);
|
|
}
|
|
}
|
|
|
|
void report_t::normalize_period()
|
|
{
|
|
date_interval_t interval(HANDLER(period_).str());
|
|
|
|
optional<date_t> begin = interval.begin();
|
|
optional<date_t> end = interval.end();
|
|
|
|
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();
|
|
else if (! HANDLED(sort_all_))
|
|
HANDLER(sort_xacts_).on("?normalize");
|
|
}
|
|
|
|
void report_t::parse_query_args(const value_t& args, const string& whence)
|
|
{
|
|
query_t query(args, what_to_keep());
|
|
|
|
if (query.has_query(query_t::QUERY_LIMIT)) {
|
|
HANDLER(limit_).on(whence, query.get_query(query_t::QUERY_LIMIT));
|
|
DEBUG("report.predicate", "Limit predicate = " << HANDLER(limit_).str());
|
|
}
|
|
|
|
if (query.has_query(query_t::QUERY_ONLY)) {
|
|
HANDLER(only_).on(whence, query.get_query(query_t::QUERY_ONLY));
|
|
DEBUG("report.predicate", "Only predicate = " << HANDLER(only_).str());
|
|
}
|
|
|
|
if (query.has_query(query_t::QUERY_SHOW)) {
|
|
HANDLER(display_).on(whence, query.get_query(query_t::QUERY_SHOW));
|
|
DEBUG("report.predicate", "Display predicate = " << HANDLER(display_).str());
|
|
}
|
|
|
|
if (query.has_query(query_t::QUERY_BOLD)) {
|
|
HANDLER(bold_if_).on(whence, query.get_query(query_t::QUERY_BOLD));
|
|
DEBUG("report.predicate", "Bolding predicate = " << HANDLER(bold_if_).str());
|
|
}
|
|
|
|
if (query.has_query(query_t::QUERY_FOR)) {
|
|
HANDLER(period_).on(whence, query.get_query(query_t::QUERY_FOR));
|
|
DEBUG("report.predicate", "Report period = " << HANDLER(period_).str());
|
|
|
|
normalize_period(); // it needs normalization
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
struct posts_flusher
|
|
{
|
|
post_handler_ptr handler;
|
|
report_t& report;
|
|
|
|
posts_flusher(post_handler_ptr _handler, report_t& _report)
|
|
: handler(_handler), report(_report) {
|
|
TRACE_CTOR(posts_flusher, "post_handler_ptr, report_t&");
|
|
}
|
|
~posts_flusher() throw() {
|
|
TRACE_DTOR(posts_flusher);
|
|
}
|
|
|
|
void operator()(const value_t&) {
|
|
report.session.journal->clear_xdata();
|
|
}
|
|
};
|
|
}
|
|
|
|
void report_t::posts_report(post_handler_ptr handler)
|
|
{
|
|
handler = chain_post_handlers(handler, *this);
|
|
if (HANDLED(group_by_)) {
|
|
unique_ptr<post_splitter>
|
|
splitter(new post_splitter(handler, *this, HANDLER(group_by_).expr));
|
|
splitter->set_postflush_func(posts_flusher(handler, *this));
|
|
handler = post_handler_ptr(splitter.release());
|
|
}
|
|
handler = chain_pre_post_handlers(handler, *this);
|
|
|
|
journal_posts_iterator walker(*session.journal.get());
|
|
pass_down_posts<journal_posts_iterator>(handler, walker);
|
|
|
|
if (! HANDLED(group_by_))
|
|
posts_flusher(handler, *this)(value_t());
|
|
}
|
|
|
|
void report_t::generate_report(post_handler_ptr handler)
|
|
{
|
|
handler = chain_handlers(handler, *this);
|
|
|
|
generate_posts_iterator walker
|
|
(session, HANDLED(seed_) ?
|
|
lexical_cast<unsigned int>(HANDLER(seed_).str()) : 0,
|
|
HANDLED(head_) ?
|
|
lexical_cast<unsigned int>(HANDLER(head_).str()) : 50);
|
|
|
|
pass_down_posts<generate_posts_iterator>(handler, walker);
|
|
}
|
|
|
|
void report_t::xact_report(post_handler_ptr handler, xact_t& xact)
|
|
{
|
|
handler = chain_handlers(handler, *this);
|
|
|
|
xact_posts_iterator walker(xact);
|
|
pass_down_posts<xact_posts_iterator>(handler, walker);
|
|
|
|
xact.clear_xdata();
|
|
}
|
|
|
|
namespace {
|
|
struct accounts_title_printer
|
|
{
|
|
acct_handler_ptr handler;
|
|
report_t& report;
|
|
|
|
accounts_title_printer(acct_handler_ptr _handler, report_t& _report)
|
|
: handler(_handler), report(_report) {}
|
|
|
|
void operator()(const value_t& val)
|
|
{
|
|
if (! report.HANDLED(no_titles)) {
|
|
std::ostringstream buf;
|
|
val.print(buf);
|
|
handler->title(buf.str());
|
|
}
|
|
}
|
|
};
|
|
|
|
struct accounts_flusher
|
|
{
|
|
acct_handler_ptr handler;
|
|
report_t& report;
|
|
|
|
accounts_flusher(acct_handler_ptr _handler, report_t& _report)
|
|
: handler(_handler), report(_report) {}
|
|
|
|
void operator()(const value_t&)
|
|
{
|
|
report.HANDLER(amount_).expr.mark_uncompiled();
|
|
report.HANDLER(total_).expr.mark_uncompiled();
|
|
report.HANDLER(display_amount_).expr.mark_uncompiled();
|
|
report.HANDLER(display_total_).expr.mark_uncompiled();
|
|
report.HANDLER(revalued_total_).expr.mark_uncompiled();
|
|
|
|
if (report.HANDLED(display_)) {
|
|
DEBUG("report.predicate",
|
|
"Display predicate = " << report.HANDLER(display_).str());
|
|
if (! report.HANDLED(sort_)) {
|
|
basic_accounts_iterator iter(*report.session.journal->master);
|
|
pass_down_accounts<basic_accounts_iterator>
|
|
(handler, iter, predicate_t(report.HANDLER(display_).str(),
|
|
report.what_to_keep()), report);
|
|
} else {
|
|
expr_t sort_expr(report.HANDLER(sort_).str());
|
|
sort_expr.set_context(&report);
|
|
sorted_accounts_iterator iter(*report.session.journal->master,
|
|
sort_expr, report.HANDLED(flat));
|
|
pass_down_accounts<sorted_accounts_iterator>
|
|
(handler, iter, predicate_t(report.HANDLER(display_).str(),
|
|
report.what_to_keep()), report);
|
|
}
|
|
} else {
|
|
if (! report.HANDLED(sort_)) {
|
|
basic_accounts_iterator iter(*report.session.journal->master);
|
|
pass_down_accounts<basic_accounts_iterator>(handler, iter);
|
|
} else {
|
|
expr_t sort_expr(report.HANDLER(sort_).str());
|
|
sort_expr.set_context(&report);
|
|
sorted_accounts_iterator iter(*report.session.journal->master,
|
|
sort_expr, report.HANDLED(flat));
|
|
pass_down_accounts<sorted_accounts_iterator>(handler, iter);
|
|
}
|
|
}
|
|
|
|
report.session.journal->clear_xdata();
|
|
}
|
|
};
|
|
}
|
|
|
|
void report_t::accounts_report(acct_handler_ptr handler)
|
|
{
|
|
post_handler_ptr chain =
|
|
chain_post_handlers(post_handler_ptr(new ignore_posts), *this,
|
|
/* for_accounts_report= */ true);
|
|
if (HANDLED(group_by_)) {
|
|
unique_ptr<post_splitter>
|
|
splitter(new post_splitter(chain, *this, HANDLER(group_by_).expr));
|
|
|
|
splitter->set_preflush_func(accounts_title_printer(handler, *this));
|
|
splitter->set_postflush_func(accounts_flusher(handler, *this));
|
|
|
|
chain = post_handler_ptr(splitter.release());
|
|
}
|
|
chain = chain_pre_post_handlers(chain, *this);
|
|
|
|
// 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.
|
|
journal_posts_iterator walker(*session.journal.get());
|
|
pass_down_posts<journal_posts_iterator>(chain, walker);
|
|
|
|
if (! HANDLED(group_by_))
|
|
accounts_flusher(handler, *this)(value_t());
|
|
}
|
|
|
|
void report_t::commodities_report(post_handler_ptr handler)
|
|
{
|
|
handler = chain_handlers(handler, *this);
|
|
|
|
posts_commodities_iterator * walker(new posts_commodities_iterator(*session.journal.get()));
|
|
try {
|
|
pass_down_posts<posts_commodities_iterator>(handler, *walker);
|
|
}
|
|
catch (...) {
|
|
#if VERIFY_ON
|
|
IF_VERIFY() {
|
|
// If --verify was used, clean up the posts_commodities_iterator.
|
|
// Otherwise, just leak like a sieve.
|
|
checked_delete(walker);
|
|
}
|
|
#endif
|
|
throw;
|
|
}
|
|
|
|
session.journal->clear_xdata();
|
|
}
|
|
|
|
value_t report_t::display_value(const value_t& val)
|
|
{
|
|
value_t temp(val.strip_annotations(what_to_keep()));
|
|
if (HANDLED(base))
|
|
return temp;
|
|
else
|
|
return temp.unreduced();
|
|
}
|
|
|
|
namespace {
|
|
value_t top_amount(const value_t& val)
|
|
{
|
|
switch (val.type()) {
|
|
case value_t::BALANCE:
|
|
return (*val.as_balance().amounts.begin()).second;
|
|
|
|
case value_t::SEQUENCE: {
|
|
return top_amount(*val.as_sequence().begin());
|
|
}
|
|
|
|
default:
|
|
return val;
|
|
}
|
|
}
|
|
}
|
|
|
|
value_t report_t::fn_top_amount(call_scope_t& args)
|
|
{
|
|
return top_amount(args[0]);
|
|
}
|
|
|
|
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_should_bold(call_scope_t& scope)
|
|
{
|
|
if (HANDLED(bold_if_))
|
|
return HANDLER(bold_if_).expr.calc(scope);
|
|
else
|
|
return false;
|
|
}
|
|
|
|
value_t report_t::fn_market(call_scope_t& args)
|
|
{
|
|
value_t result;
|
|
value_t arg0 = args[0];
|
|
|
|
datetime_t moment;
|
|
if (args.has<datetime_t>(1))
|
|
moment = args.get<datetime_t>(1);
|
|
|
|
if (arg0.is_string()) {
|
|
amount_t tmp(1L);
|
|
commodity_t * commodity =
|
|
commodity_pool_t::current_pool->find_or_create(arg0.as_string());
|
|
tmp.set_commodity(*commodity);
|
|
arg0 = tmp;
|
|
}
|
|
|
|
string target_commodity;
|
|
if (args.has<string>(2))
|
|
target_commodity = args.get<string>(2);
|
|
|
|
if (! target_commodity.empty())
|
|
result = arg0.exchange_commodities(target_commodity,
|
|
/* add_prices= */ false, moment);
|
|
else
|
|
result = arg0.value(moment);
|
|
|
|
return ! result.is_null() ? result : arg0;
|
|
}
|
|
|
|
value_t report_t::fn_get_at(call_scope_t& args)
|
|
{
|
|
std::size_t index = static_cast<std::size_t>(args.get<long>(1));
|
|
if (index == 0) {
|
|
if (! args[0].is_sequence())
|
|
return args[0];
|
|
}
|
|
else if (! args[0].is_sequence()) {
|
|
throw_(std::runtime_error,
|
|
_f("Attempting to get argument at index %1% from %2%")
|
|
% index % args[0].label());
|
|
}
|
|
|
|
value_t::sequence_t& seq(args[0].as_sequence_lval());
|
|
if (index >= seq.size())
|
|
throw_(std::runtime_error,
|
|
_f("Attempting to get index %1% from %2% with %3% elements")
|
|
% index % args[0].label() % seq.size());
|
|
|
|
return seq[index];
|
|
}
|
|
|
|
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, static_cast<std::string::size_type>(e - p)));
|
|
}
|
|
}
|
|
|
|
value_t report_t::fn_format(call_scope_t& args)
|
|
{
|
|
format_t format(args.get<string>(0));
|
|
std::ostringstream out;
|
|
out << format(args);
|
|
return string_value(out.str());
|
|
}
|
|
|
|
value_t report_t::fn_print(call_scope_t& args)
|
|
{
|
|
for (std::size_t i = 0; i < args.size(); i++)
|
|
args[i].print(output_stream);
|
|
static_cast<std::ostream&>(output_stream) << std::endl;
|
|
return true;
|
|
}
|
|
|
|
value_t report_t::fn_scrub(call_scope_t& args)
|
|
{
|
|
return display_value(args.value());
|
|
}
|
|
|
|
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& args)
|
|
{
|
|
return args.get<amount_t>(0).number();
|
|
}
|
|
|
|
value_t report_t::fn_floor(call_scope_t& args)
|
|
{
|
|
return args[0].floored();
|
|
}
|
|
|
|
value_t report_t::fn_ceiling(call_scope_t& args)
|
|
{
|
|
return args[0].ceilinged();
|
|
}
|
|
|
|
value_t report_t::fn_round(call_scope_t& args)
|
|
{
|
|
return args[0].rounded();
|
|
}
|
|
|
|
value_t report_t::fn_roundto(call_scope_t& args)
|
|
{
|
|
return args[0].roundto(args.get<int>(1));
|
|
}
|
|
|
|
value_t report_t::fn_unround(call_scope_t& args)
|
|
{
|
|
return args[0].unrounded();
|
|
}
|
|
|
|
value_t report_t::fn_abs(call_scope_t& args)
|
|
{
|
|
return args[0].abs();
|
|
}
|
|
|
|
value_t report_t::fn_truncated(call_scope_t& args)
|
|
{
|
|
return string_value(format_t::truncate
|
|
(args.get<string>(0),
|
|
(args.has<int>(1) && args.get<int>(1) > 0) ?
|
|
static_cast<std::size_t>(args.get<int>(1)) : 0,
|
|
args.has<int>(2) ?
|
|
static_cast<std::size_t>(args.get<int>(2)) : 0));
|
|
}
|
|
|
|
value_t report_t::fn_justify(call_scope_t& args)
|
|
{
|
|
uint_least8_t flags(AMOUNT_PRINT_ELIDE_COMMODITY_QUOTES);
|
|
|
|
if (args.has<bool>(3) && args.get<bool>(3))
|
|
flags |= AMOUNT_PRINT_RIGHT_JUSTIFY;
|
|
if (args.has<bool>(4) && args.get<bool>(4))
|
|
flags |= AMOUNT_PRINT_COLORIZE;
|
|
|
|
std::ostringstream out;
|
|
args[0].print(out, args.get<int>(1),
|
|
args.has<int>(2) ? args.get<int>(2) : -1, flags);
|
|
|
|
return string_value(out.str());
|
|
}
|
|
|
|
value_t report_t::fn_quoted(call_scope_t& args)
|
|
{
|
|
std::ostringstream out;
|
|
|
|
out << '"';
|
|
string arg(args.get<string>(0));
|
|
foreach (const char ch, arg) {
|
|
if (ch == '"')
|
|
out << "\\\"";
|
|
else
|
|
out << ch;
|
|
}
|
|
out << '"';
|
|
|
|
return string_value(out.str());
|
|
}
|
|
|
|
value_t report_t::fn_join(call_scope_t& args)
|
|
{
|
|
std::ostringstream out;
|
|
|
|
string arg(args.get<string>(0));
|
|
foreach (const char ch, arg) {
|
|
if (ch != '\n')
|
|
out << ch;
|
|
else
|
|
out << "\\n";
|
|
}
|
|
return string_value(out.str());
|
|
}
|
|
|
|
value_t report_t::fn_format_date(call_scope_t& args)
|
|
{
|
|
if (args.has<string>(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_format_datetime(call_scope_t& args)
|
|
{
|
|
if (args.has<string>(1))
|
|
return string_value(format_datetime(args.get<datetime_t>(0), FMT_CUSTOM,
|
|
args.get<string>(1).c_str()));
|
|
else
|
|
return string_value(format_datetime(args.get<datetime_t>(0), FMT_PRINTED));
|
|
}
|
|
|
|
value_t report_t::fn_ansify_if(call_scope_t& args)
|
|
{
|
|
if (args.has<string>(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[0];
|
|
buf << "\033[0m";
|
|
return string_value(buf.str());
|
|
}
|
|
return args[0];
|
|
}
|
|
|
|
value_t report_t::fn_percent(call_scope_t& args)
|
|
{
|
|
return (amount_t("100.00%") *
|
|
(args.get<amount_t>(0) / args.get<amount_t>(1)).number());
|
|
}
|
|
|
|
value_t report_t::fn_commodity(call_scope_t& args)
|
|
{
|
|
return string_value(args.get<amount_t>(0).commodity().symbol());
|
|
}
|
|
|
|
value_t report_t::fn_nail_down(call_scope_t& args)
|
|
{
|
|
value_t arg0(args[0]);
|
|
value_t arg1(args[1]);
|
|
|
|
switch (arg0.type()) {
|
|
case value_t::AMOUNT: {
|
|
amount_t tmp(arg0.as_amount());
|
|
if (tmp.has_commodity() && ! tmp.is_null() && ! tmp.is_realzero()) {
|
|
arg1 = arg1.strip_annotations(keep_details_t()).to_amount();
|
|
expr_t value_expr(is_expr(arg1) ?
|
|
as_expr(arg1) :
|
|
expr_t::op_t::wrap_value(arg1.unrounded() /
|
|
arg0.number()));
|
|
std::ostringstream buf;
|
|
value_expr.print(buf);
|
|
value_expr.set_text(buf.str());
|
|
|
|
tmp.set_commodity(tmp.commodity().nail_down(value_expr));
|
|
}
|
|
return tmp;
|
|
}
|
|
|
|
case value_t::BALANCE: {
|
|
balance_t tmp;
|
|
foreach (const balance_t::amounts_map::value_type& pair,
|
|
arg0.as_balance_lval().amounts) {
|
|
call_scope_t inner_args(*args.parent);
|
|
inner_args.push_back(pair.second);
|
|
inner_args.push_back(arg1);
|
|
tmp += fn_nail_down(inner_args).as_amount();
|
|
}
|
|
return tmp;
|
|
}
|
|
|
|
case value_t::SEQUENCE: {
|
|
value_t tmp;
|
|
foreach (value_t& value, arg0.as_sequence_lval()) {
|
|
call_scope_t inner_args(*args.parent);
|
|
inner_args.push_back(value);
|
|
inner_args.push_back(arg1);
|
|
tmp.push_back(fn_nail_down(inner_args));
|
|
}
|
|
return tmp;
|
|
}
|
|
|
|
default:
|
|
throw_(std::runtime_error, _f("Attempting to nail down %1%")
|
|
% args[0].label());
|
|
}
|
|
return arg0;
|
|
}
|
|
|
|
value_t report_t::fn_lot_date(call_scope_t& args)
|
|
{
|
|
if (args[0].has_annotation()) {
|
|
const annotation_t& details(args[0].annotation());
|
|
if (details.date)
|
|
return *details.date;
|
|
}
|
|
return NULL_VALUE;
|
|
}
|
|
|
|
value_t report_t::fn_lot_price(call_scope_t& args)
|
|
{
|
|
if (args[0].has_annotation()) {
|
|
const annotation_t& details(args[0].annotation());
|
|
if (details.price)
|
|
return *details.price;
|
|
}
|
|
return NULL_VALUE;
|
|
}
|
|
|
|
value_t report_t::fn_lot_tag(call_scope_t& args)
|
|
{
|
|
if (args[0].has_annotation()) {
|
|
const annotation_t& details(args[0].annotation());
|
|
if (details.tag)
|
|
return string_value(*details.tag);
|
|
}
|
|
return NULL_VALUE;
|
|
}
|
|
|
|
value_t report_t::fn_to_boolean(call_scope_t& args)
|
|
{
|
|
return args.get<bool>(0);
|
|
}
|
|
|
|
value_t report_t::fn_to_int(call_scope_t& args)
|
|
{
|
|
// This method is not called fn_to_long, because that would be
|
|
// confusing to users who don't care about the distinction between
|
|
// integer and long.
|
|
return args.get<long>(0);
|
|
}
|
|
|
|
value_t report_t::fn_to_datetime(call_scope_t& args)
|
|
{
|
|
return args.get<datetime_t>(0);
|
|
}
|
|
|
|
value_t report_t::fn_to_date(call_scope_t& args)
|
|
{
|
|
return args.get<date_t>(0);
|
|
}
|
|
|
|
value_t report_t::fn_to_amount(call_scope_t& args)
|
|
{
|
|
return args.get<amount_t>(0);
|
|
}
|
|
|
|
value_t report_t::fn_to_balance(call_scope_t& args)
|
|
{
|
|
return args.get<balance_t>(0);
|
|
}
|
|
|
|
value_t report_t::fn_to_string(call_scope_t& args)
|
|
{
|
|
return string_value(args.get<string>(0));
|
|
}
|
|
|
|
value_t report_t::fn_to_mask(call_scope_t& args)
|
|
{
|
|
return args.get<mask_t>(0);
|
|
}
|
|
|
|
value_t report_t::fn_to_sequence(call_scope_t& args)
|
|
{
|
|
return args[0].to_sequence();
|
|
}
|
|
|
|
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& args)
|
|
{
|
|
std::ostream& out(output_stream);
|
|
out << args.get<string>(0) << std::endl;
|
|
return true;
|
|
}
|
|
|
|
value_t report_t::pricemap_command(call_scope_t& args)
|
|
{
|
|
std::ostream& out(output_stream);
|
|
commodity_pool_t::current_pool->commodity_price_history.print_map
|
|
(out, args.has<string>(0) ?
|
|
datetime_t(parse_date(args.get<string>(0))) : datetime_t());
|
|
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 'H':
|
|
OPT_CH(historical);
|
|
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(add_budget);
|
|
else OPT(amount_);
|
|
else OPT(amount_data);
|
|
else OPT_ALT(primary_date, actual_dates);
|
|
else OPT(anon);
|
|
else OPT_ALT(color, ansi);
|
|
else OPT(auto_match);
|
|
else OPT(aux_date);
|
|
else OPT(average);
|
|
else OPT(account_width_);
|
|
else OPT(amount_width_);
|
|
break;
|
|
case 'b':
|
|
OPT(balance_format_);
|
|
else OPT(base);
|
|
else OPT(basis);
|
|
else OPT_(begin_);
|
|
else OPT(bold_if_);
|
|
else OPT(budget);
|
|
else OPT(budget_format_);
|
|
else OPT(by_payee);
|
|
break;
|
|
case 'c':
|
|
OPT(csv_format_);
|
|
else OPT_ALT(gain, change);
|
|
else OPT(cleared);
|
|
else OPT(cleared_format_);
|
|
else OPT(collapse);
|
|
else OPT(collapse_if_zero);
|
|
else OPT(color);
|
|
else OPT(columns_);
|
|
else OPT_ALT(basis, cost);
|
|
else OPT_(current);
|
|
else OPT(count);
|
|
break;
|
|
case 'd':
|
|
OPT(daily);
|
|
else OPT(date_);
|
|
else OPT(date_format_);
|
|
else OPT(datetime_format_);
|
|
else OPT(dc);
|
|
else OPT(depth_);
|
|
else OPT(deviation);
|
|
else OPT_ALT(rich_data, detail);
|
|
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(empty);
|
|
else OPT_(end_);
|
|
else OPT(equity);
|
|
else OPT(exact);
|
|
else OPT(exchange_);
|
|
else OPT_ALT(aux_date, effective);
|
|
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);
|
|
else OPT(group_by_);
|
|
else OPT(group_title_format_);
|
|
else OPT(generated);
|
|
break;
|
|
case 'h':
|
|
OPT(head_);
|
|
else OPT(historical);
|
|
break;
|
|
case 'i':
|
|
OPT(invert);
|
|
else OPT(inject_);
|
|
else OPT(immediate);
|
|
break;
|
|
case 'j':
|
|
OPT_CH(amount_data);
|
|
break;
|
|
case 'l':
|
|
OPT_(limit_);
|
|
else OPT(lot_dates);
|
|
else OPT(lot_prices);
|
|
else OPT_ALT(lot_notes, lot_tags);
|
|
else OPT(lots);
|
|
else OPT(lots_actual);
|
|
else OPT_ALT(tail_, last_);
|
|
break;
|
|
case 'm':
|
|
OPT(market);
|
|
else OPT(monthly);
|
|
else OPT(meta_);
|
|
else OPT(meta_width_);
|
|
break;
|
|
case 'n':
|
|
OPT_CH(collapse);
|
|
else OPT(no_color);
|
|
else OPT(no_pager);
|
|
else OPT(no_rounding);
|
|
else OPT(no_titles);
|
|
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(pivot_);
|
|
else OPT(plot_amount_format_);
|
|
else OPT(plot_total_format_);
|
|
else OPT(price);
|
|
else OPT(prices_format_);
|
|
else OPT(pricedb_format_);
|
|
else OPT(primary_date);
|
|
else OPT(payee_width_);
|
|
else OPT(prepend_format_);
|
|
else OPT(prepend_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_);
|
|
else OPT(rich_data);
|
|
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_);
|
|
else OPT(time_report);
|
|
break;
|
|
case 'u':
|
|
OPT(unbudgeted);
|
|
else OPT(uncleared);
|
|
else OPT(unrealized);
|
|
else OPT(unrealized_gains_);
|
|
else OPT(unrealized_losses_);
|
|
else OPT(unround);
|
|
break;
|
|
case 'v':
|
|
OPT_ALT(market, value);
|
|
else OPT(values);
|
|
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);
|
|
else if (is_eq(p, "commodity"))
|
|
return MAKE_FUNCTOR(report_t::fn_commodity);
|
|
else if (is_eq(p, "ceiling"))
|
|
return MAKE_FUNCTOR(report_t::fn_ceiling);
|
|
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_today);
|
|
break;
|
|
|
|
case 'f':
|
|
if (is_eq(p, "format_date"))
|
|
return MAKE_FUNCTOR(report_t::fn_format_date);
|
|
else if (is_eq(p, "format_datetime"))
|
|
return MAKE_FUNCTOR(report_t::fn_format_datetime);
|
|
else if (is_eq(p, "format"))
|
|
return MAKE_FUNCTOR(report_t::fn_format);
|
|
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);
|
|
else if (is_eq(p, "nail_down"))
|
|
return MAKE_FUNCTOR(report_t::fn_nail_down);
|
|
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, "print"))
|
|
return MAKE_FUNCTOR(report_t::fn_print);
|
|
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);
|
|
else if (is_eq(p, "round"))
|
|
return MAKE_FUNCTOR(report_t::fn_round);
|
|
else if (is_eq(p, "roundto"))
|
|
return MAKE_FUNCTOR(report_t::fn_roundto);
|
|
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);
|
|
else if (is_eq(p, "should_bold"))
|
|
return MAKE_FUNCTOR(report_t::fn_should_bold);
|
|
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, "top_amount"))
|
|
return MAKE_FUNCTOR(report_t::fn_top_amount);
|
|
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, "unround"))
|
|
return MAKE_FUNCTOR(report_t::fn_unround);
|
|
else if (is_eq(p, "unrounded"))
|
|
return MAKE_FUNCTOR(report_t::fn_unrounded);
|
|
break;
|
|
|
|
case 'v':
|
|
if (is_eq(p, "value_date"))
|
|
return MAKE_FUNCTOR(report_t::fn_now);
|
|
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;
|
|
|
|
#define POSTS_REPORTER(formatter) \
|
|
WRAP_FUNCTOR(reporter<>(post_handler_ptr(formatter), *this, \
|
|
string("#") + p))
|
|
|
|
// Can't use WRAP_FUNCTOR here because the template arguments
|
|
// confuse the parser
|
|
#define POSTS_REPORTER_(method, formatter) \
|
|
expr_t::op_t::wrap_functor \
|
|
(reporter<post_t, post_handler_ptr, method> \
|
|
(post_handler_ptr(formatter), *this, string("#") + p))
|
|
|
|
#define FORMATTED_POSTS_REPORTER(format) \
|
|
POSTS_REPORTER \
|
|
(new format_posts \
|
|
(*this, report_format(HANDLER(format)), \
|
|
maybe_format(HANDLER(prepend_format_)), \
|
|
HANDLED(prepend_width_) ? \
|
|
lexical_cast<std::size_t>(HANDLER(prepend_width_).str()) : 0))
|
|
|
|
#define FORMATTED_COMMODITIES_REPORTER(format) \
|
|
POSTS_REPORTER_ \
|
|
(&report_t::commodities_report, \
|
|
new format_posts \
|
|
(*this, report_format(HANDLER(format)), \
|
|
maybe_format(HANDLER(prepend_format_)), \
|
|
HANDLED(prepend_width_) ? \
|
|
lexical_cast<std::size_t>(HANDLER(prepend_width_).str()) : 0))
|
|
|
|
#define ACCOUNTS_REPORTER(formatter) \
|
|
expr_t::op_t::wrap_functor(reporter<account_t, acct_handler_ptr, \
|
|
&report_t::accounts_report> \
|
|
(acct_handler_ptr(formatter), *this, \
|
|
string("#") + p))
|
|
|
|
#define FORMATTED_ACCOUNTS_REPORTER(format) \
|
|
ACCOUNTS_REPORTER \
|
|
(new format_accounts \
|
|
(*this, report_format(HANDLER(format)), \
|
|
maybe_format(HANDLER(prepend_format_)), \
|
|
HANDLED(prepend_width_) ? \
|
|
lexical_cast<std::size_t>(HANDLER(prepend_width_).str()) : 0))
|
|
|
|
case symbol_t::COMMAND:
|
|
switch (*p) {
|
|
case 'a':
|
|
if (is_eq(p, "accounts")) {
|
|
return POSTS_REPORTER(new report_accounts(*this));
|
|
}
|
|
break;
|
|
|
|
case 'b':
|
|
if (*(p + 1) == '\0' || is_eq(p, "bal") || is_eq(p, "balance")) {
|
|
return FORMATTED_ACCOUNTS_REPORTER(balance_format_);
|
|
}
|
|
else if (is_eq(p, "budget")) {
|
|
HANDLER(amount_).on(string("#budget"), "(amount, 0)");
|
|
|
|
budget_flags |= BUDGET_WRAP_VALUES;
|
|
if (! (budget_flags & ~BUDGET_WRAP_VALUES))
|
|
budget_flags |= BUDGET_BUDGETED;
|
|
|
|
return FORMATTED_ACCOUNTS_REPORTER(budget_format_);
|
|
}
|
|
break;
|
|
|
|
case 'c':
|
|
if (is_eq(p, "csv")) {
|
|
return FORMATTED_POSTS_REPORTER(csv_format_);
|
|
}
|
|
else if (is_eq(p, "cleared")) {
|
|
HANDLER(amount_).on(string("#cleared"),
|
|
"(amount, cleared ? amount : 0)");
|
|
return FORMATTED_ACCOUNTS_REPORTER(cleared_format_);
|
|
}
|
|
else if (is_eq(p, "convert")) {
|
|
return WRAP_FUNCTOR(convert_command);
|
|
}
|
|
else if (is_eq(p, "commodities")) {
|
|
return POSTS_REPORTER(new report_commodities(*this));
|
|
}
|
|
break;
|
|
case 'd':
|
|
if (is_eq(p, "draft")) {
|
|
return WRAP_FUNCTOR(xact_command);
|
|
}
|
|
break;
|
|
case 'e':
|
|
if (is_eq(p, "equity")) {
|
|
HANDLER(generated).on("#equity");
|
|
return POSTS_REPORTER(new print_xacts(*this));
|
|
}
|
|
else if (is_eq(p, "entry")) {
|
|
return WRAP_FUNCTOR(xact_command);
|
|
}
|
|
else if (is_eq(p, "emacs")) {
|
|
return POSTS_REPORTER(new format_emacs_posts(output_stream));
|
|
}
|
|
else if (is_eq(p, "echo")) {
|
|
return MAKE_FUNCTOR(report_t::echo_command);
|
|
}
|
|
break;
|
|
|
|
case 'l':
|
|
if (is_eq(p, "lisp"))
|
|
return POSTS_REPORTER(new format_emacs_posts(output_stream));
|
|
break;
|
|
case 'o':
|
|
if (is_eq(p, "org"))
|
|
return POSTS_REPORTER(new posts_to_org_table
|
|
(*this, maybe_format(HANDLER(prepend_format_))));
|
|
break;
|
|
|
|
case 'p':
|
|
if (*(p + 1) == '\0' || is_eq(p, "print")) {
|
|
return POSTS_REPORTER(new print_xacts(*this, HANDLED(raw)));
|
|
}
|
|
else if (is_eq(p, "prices")) {
|
|
return FORMATTED_COMMODITIES_REPORTER(prices_format_);
|
|
}
|
|
else if (is_eq(p, "pricedb") || is_eq(p, "pricesdb")) {
|
|
return FORMATTED_COMMODITIES_REPORTER(pricedb_format_);
|
|
}
|
|
else if (is_eq(p, "pricemap")) {
|
|
return MAKE_FUNCTOR(report_t::pricemap_command);
|
|
}
|
|
else if (is_eq(p, "payees")) {
|
|
return POSTS_REPORTER(new report_payees(*this));
|
|
}
|
|
break;
|
|
|
|
case 'r':
|
|
if (*(p + 1) == '\0' || is_eq(p, "reg") || is_eq(p, "register")) {
|
|
return FORMATTED_POSTS_REPORTER(register_format_);
|
|
}
|
|
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, "source"))
|
|
return WRAP_FUNCTOR(source_command);
|
|
else if (is_eq(p, "select"))
|
|
return WRAP_FUNCTOR(select_command);
|
|
break;
|
|
case 't':
|
|
if (is_eq(p, "tags")) {
|
|
return POSTS_REPORTER(new report_tags(*this));
|
|
}
|
|
break;
|
|
case 'x':
|
|
if (is_eq(p, "xact"))
|
|
return WRAP_FUNCTOR(xact_command);
|
|
else if (is_eq(p, "xml"))
|
|
return POSTS_REPORTER(new format_ptree(*this,
|
|
format_ptree::FORMAT_XML));
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case symbol_t::PRECOMMAND:
|
|
switch (*p) {
|
|
case 'a':
|
|
if (is_eq(p, "args"))
|
|
return WRAP_FUNCTOR(query_command);
|
|
break;
|
|
case 'e':
|
|
if (is_eq(p, "eval"))
|
|
return WRAP_FUNCTOR(eval_command);
|
|
else if (is_eq(p, "expr"))
|
|
return WRAP_FUNCTOR(parse_command);
|
|
break;
|
|
case 'f':
|
|
if (is_eq(p, "format"))
|
|
return WRAP_FUNCTOR(format_command);
|
|
break;
|
|
case 'g':
|
|
if (is_eq(p, "generate"))
|
|
return POSTS_REPORTER_(&report_t::generate_report,
|
|
new print_xacts(*this));
|
|
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 'q':
|
|
if (is_eq(p, "query"))
|
|
return WRAP_FUNCTOR(query_command);
|
|
break;
|
|
case 's':
|
|
if (is_eq(p, "script"))
|
|
return WRAP_FUNCTOR(source_command);
|
|
break;
|
|
case 't':
|
|
if (is_eq(p, "template"))
|
|
return WRAP_FUNCTOR(template_command);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
} // namespace ledger
|