diff options
Diffstat (limited to 'report.cc')
-rw-r--r-- | report.cc | 616 |
1 files changed, 341 insertions, 275 deletions
@@ -1,253 +1,64 @@ +/* + * Copyright (c) 2003-2007, 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 "report.h" +#include "reconcile.h" namespace ledger { -report_t::report_t() -{ - ledger::amount_expr = "@a"; - ledger::total_expr = "@O"; - - predicate = ""; - secondary_predicate = ""; - display_predicate = ""; - descend_expr = ""; - - budget_flags = BUDGET_NO_BUDGET; - - head_entries = 0; - tail_entries = 0; - - show_collapsed = false; - show_subtotal = false; - show_totals = false; - show_related = false; - show_all_related = false; - show_inverted = false; - show_empty = false; - days_of_the_week = false; - by_payee = false; - comm_as_payee = false; - code_as_payee = false; - show_revalued = false; - show_revalued_only = false; - keep_price = false; - keep_date = false; - keep_tag = false; - entry_sort = false; - sort_all = false; -} - -void -report_t::regexps_to_predicate(const std::string& command, - std::list<std::string>::const_iterator begin, - std::list<std::string>::const_iterator end, - const bool account_regexp, - const bool add_account_short_masks, - const bool logical_and) -{ - std::string regexps[2]; - - assert(begin != end); - - // Treat the remaining command-line arguments as regular - // expressions, used for refining report results. - - for (std::list<std::string>::const_iterator i = begin; - i != end; - i++) - if ((*i)[0] == '-') { - if (! regexps[1].empty()) - regexps[1] += "|"; - regexps[1] += (*i).substr(1); - } - else if ((*i)[0] == '+') { - if (! regexps[0].empty()) - regexps[0] += "|"; - regexps[0] += (*i).substr(1); - } - else { - if (! regexps[0].empty()) - regexps[0] += "|"; - regexps[0] += *i; - } - - for (int i = 0; i < 2; i++) { - if (regexps[i].empty()) - continue; - - if (! predicate.empty()) - predicate += logical_and ? "&" : "|"; - - int add_predicate = 0; // 1 adds /.../, 2 adds ///.../ - if (i == 1) { - predicate += "!"; - } - else if (add_account_short_masks) { - if (regexps[i].find(':') != std::string::npos || - regexps[i].find('.') != std::string::npos || - regexps[i].find('*') != std::string::npos || - regexps[i].find('+') != std::string::npos || - regexps[i].find('[') != std::string::npos || - regexps[i].find('(') != std::string::npos) { - show_subtotal = true; - add_predicate = 1; - } else { - add_predicate = 2; - } - } - else { - add_predicate = 1; - } - - if (i != 1 && command == "b" && account_regexp) { - if (! show_related && ! show_all_related) { - if (! display_predicate.empty()) - display_predicate += "&"; - if (! show_empty) - display_predicate += "T&"; - - if (add_predicate == 2) - display_predicate += "//"; - display_predicate += "/(?:"; - display_predicate += regexps[i]; - display_predicate += ")/"; - } - else if (! show_empty) { - if (! display_predicate.empty()) - display_predicate += "&"; - display_predicate += "T"; - } - } - - if (! account_regexp) - predicate += "/"; - predicate += "/(?:"; - predicate += regexps[i]; - predicate += ")/"; - } -} - -void report_t::process_options(const std::string& command, - strings_list::iterator arg, - strings_list::iterator args_end) -{ - // Configure some other options depending on report type - - if (command == "p" || command == "e" || command == "w") { - show_related = - show_all_related = true; - } - else if (command == "E") { - show_subtotal = true; - } - else if (show_related) { - if (command == "r") { - show_inverted = true; - } else { - show_subtotal = true; - show_all_related = true; - } - } - - if (command != "b" && command != "r") - amount_t::keep_base = true; - - // Process remaining command-line arguments - - if (command != "e") { - // Treat the remaining command-line arguments as regular - // expressions, used for refining report results. - - std::list<std::string>::iterator i = arg; - for (; i != args_end; i++) - if (*i == "--") - break; - - if (i != arg) - regexps_to_predicate(command, arg, i, true, - (command == "b" && ! show_subtotal && - display_predicate.empty())); - if (i != args_end && ++i != args_end) - regexps_to_predicate(command, i, args_end); - } - - // Setup the default value for the display predicate - - if (display_predicate.empty()) { - if (command == "b") { - if (! show_empty) - display_predicate = "T"; - if (! show_subtotal) { - if (! display_predicate.empty()) - display_predicate += "&"; - display_predicate += "l<=1"; - } - } - else if (command == "E") { - display_predicate = "t"; - } - else if (command == "r" && ! show_empty) { - display_predicate = "a"; - } - } - - DEBUG_PRINT("ledger.config.predicates", "Predicate: " << predicate); - DEBUG_PRINT("ledger.config.predicates", "Display P: " << display_predicate); - - // Setup the values of %t and %T, used in format strings - - if (! amount_expr.empty()) - ledger::amount_expr = amount_expr; - if (! total_expr.empty()) - ledger::total_expr = total_expr; - - // Now setup the various formatting strings - - if (! date_output_format.empty()) - date_t::output_format = date_output_format; - - amount_t::keep_price = keep_price; - amount_t::keep_date = keep_date; - amount_t::keep_tag = keep_tag; - - if (! report_period.empty() && ! sort_all) - entry_sort = true; -} - -item_handler<transaction_t> * -report_t::chain_xact_handlers(const std::string& command, - item_handler<transaction_t> * base_formatter, - journal_t * journal, - account_t * master, - std::list<item_handler<transaction_t> *>& ptrs) +xact_handler_ptr +report_t::chain_xact_handlers(xact_handler_ptr base_handler, + const bool handle_individual_transactions) { bool remember_components = false; - item_handler<transaction_t> * formatter = NULL; - - ptrs.push_back(formatter = base_formatter); + xact_handler_ptr handler(base_handler); // format_transactions write each transaction received to the // output stream. - if (! (command == "b" || command == "E")) { + if (handle_individual_transactions) { // truncate_entries cuts off a certain number of _entries_ from // being displayed. It does not affect calculation. if (head_entries || tail_entries) - ptrs.push_back(formatter = - new truncate_entries(formatter, - head_entries, tail_entries)); + handler.reset(new truncate_entries(handler, head_entries, tail_entries)); // filter_transactions will only pass through transactions // matching the `display_predicate'. if (! display_predicate.empty()) - ptrs.push_back(formatter = - new filter_transactions(formatter, - display_predicate)); + handler.reset(new filter_transactions(handler, display_predicate)); // calc_transactions computes the running total. When this // appears will determine, for example, whether filtered // transactions are included or excluded from the running total. - ptrs.push_back(formatter = new calc_transactions(formatter)); + handler.reset(new calc_transactions(handler)); // component_transactions looks for reported transaction that // match the given `descend_expr', and then reports the @@ -267,8 +78,7 @@ report_t::chain_xact_handlers(const std::string& command, descend_exprs.rbegin(); i != descend_exprs.rend(); i++) - ptrs.push_back(formatter = - new component_transactions(formatter, *i)); + handler.reset(new component_transactions(handler, *i)); remember_components = true; } @@ -277,45 +87,38 @@ report_t::chain_xact_handlers(const std::string& command, // transactions which can be reconciled to a given balance // (calculated against the transactions which it receives). if (! reconcile_balance.empty()) { - datetime_t cutoff = datetime_t::now; + datetime_t cutoff = current_moment; if (! reconcile_date.empty()) - cutoff = reconcile_date; - ptrs.push_back(formatter = - new reconcile_transactions - (formatter, value_t(reconcile_balance), cutoff)); + cutoff = parse_datetime(reconcile_date); + handler.reset(new reconcile_transactions + (handler, value_t(reconcile_balance), cutoff)); } // filter_transactions will only pass through transactions // matching the `secondary_predicate'. if (! secondary_predicate.empty()) - ptrs.push_back(formatter = - new filter_transactions(formatter, - secondary_predicate)); + handler.reset(new filter_transactions(handler, secondary_predicate)); // sort_transactions will sort all the transactions it sees, based // on the `sort_order' value expression. if (! sort_string.empty()) { if (entry_sort) - ptrs.push_back(formatter = - new sort_entries(formatter, sort_string)); + handler.reset(new sort_entries(handler, sort_string)); else - ptrs.push_back(formatter = - new sort_transactions(formatter, sort_string)); + handler.reset(new sort_transactions(handler, sort_string)); } // changed_value_transactions adds virtual transactions to the // list to account for changes in market value of commodities, // which otherwise would affect the running total unpredictably. if (show_revalued) - ptrs.push_back(formatter = - new changed_value_transactions(formatter, - show_revalued_only)); + handler.reset(new changed_value_transactions(handler, show_revalued_only)); // collapse_transactions causes entries with multiple transactions // to appear as entries with a subtotaled transaction for each // commodity used. if (show_collapsed) - ptrs.push_back(formatter = new collapse_transactions(formatter)); + handler.reset(new collapse_transactions(handler)); // subtotal_transactions combines all the transactions it receives // into one subtotal entry, which has one transaction for each @@ -329,30 +132,26 @@ report_t::chain_xact_handlers(const std::string& command, // reports all the transactions that fall on each subsequent day // of the week. if (show_subtotal) - ptrs.push_back(formatter = - new subtotal_transactions(formatter, remember_components)); + handler.reset(new subtotal_transactions(handler, remember_components)); if (days_of_the_week) - ptrs.push_back(formatter = - new dow_transactions(formatter, remember_components)); + handler.reset(new dow_transactions(handler, remember_components)); else if (by_payee) - ptrs.push_back(formatter = - new by_payee_transactions(formatter, remember_components)); + handler.reset(new by_payee_transactions(handler, remember_components)); // interval_transactions groups transactions together based on a // time period, such as weekly or monthly. if (! report_period.empty()) { - ptrs.push_back(formatter = - new interval_transactions(formatter, report_period, - remember_components)); - ptrs.push_back(formatter = new sort_transactions(formatter, "d")); + handler.reset(new interval_transactions(handler, report_period, + remember_components)); + handler.reset(new sort_transactions(handler, "d")); } } // invert_transactions inverts the value of the transactions it // receives. if (show_inverted) - ptrs.push_back(formatter = new invert_transactions(formatter)); + handler.reset(new invert_transactions(handler)); // related_transactions will pass along all transactions related // to the transaction received. If `show_all_related' is true, @@ -360,15 +159,14 @@ report_t::chain_xact_handlers(const std::string& command, // one transaction of an entry is to be printed, all the // transaction for that entry will be printed. if (show_related) - ptrs.push_back(formatter = - new related_transactions(formatter, - show_all_related)); + handler.reset(new related_transactions(handler, show_all_related)); // This filter_transactions will only pass through transactions // matching the `predicate'. if (! predicate.empty()) - ptrs.push_back(formatter = new filter_transactions(formatter, predicate)); + handler.reset(new filter_transactions(handler, predicate)); +#if 0 // budget_transactions takes a set of transactions from a data // file and uses them to generate "budget transactions" which // balance against the reported transactions. @@ -378,10 +176,10 @@ report_t::chain_xact_handlers(const std::string& command, // them against anything but the future balance. if (budget_flags) { - budget_transactions * handler - = new budget_transactions(formatter, budget_flags); - handler->add_period_entries(journal->period_entries); - ptrs.push_back(formatter = handler); + budget_transactions * budget_handler + = new budget_transactions(handler, budget_flags); + budget_handler->add_period_entries(journal->period_entries); + handler.reset(budget_handler; // Apply this before the budget handler, so that only matching // transactions are calculated toward the budget. The use of @@ -389,25 +187,293 @@ report_t::chain_xact_handlers(const std::string& command, // that no automated transactions that don't match the filter get // reported. if (! predicate.empty()) - ptrs.push_back(formatter = new filter_transactions(formatter, predicate)); + handler.reset(new filter_transactions(handler, predicate)); } else if (! forecast_limit.empty()) { - forecast_transactions * handler - = new forecast_transactions(formatter, forecast_limit); - handler->add_period_entries(journal->period_entries); - ptrs.push_back(formatter = handler); + forecast_transactions * forecast_handler + = new forecast_transactions(handler, forecast_limit); + forecast_handler->add_period_entries(journal->period_entries); + handler.reset(forecast_handler; // See above, under budget_transactions. if (! predicate.empty()) - ptrs.push_back(formatter = new filter_transactions(formatter, predicate)); + handler.reset(new filter_transactions(handler, predicate)); } +#endif if (comm_as_payee) - ptrs.push_back(formatter = new set_comm_as_payee(formatter)); + handler.reset(new set_comm_as_payee(handler)); else if (code_as_payee) - ptrs.push_back(formatter = new set_code_as_payee(formatter)); + handler.reset(new set_code_as_payee(handler)); + + return handler; +} + +void report_t::transactions_report(xact_handler_ptr handler) +{ + session_transactions_iterator walker(session); + pass_down_transactions(chain_xact_handlers(handler), walker); + handler->flush(); + + if (DO_VERIFY()) + clean_transactions(); +} + +void report_t::entry_report(xact_handler_ptr handler, entry_t& entry) +{ + entry_transactions_iterator walker(entry); + pass_down_transactions(chain_xact_handlers(handler), walker); + handler->flush(); + + if (DO_VERIFY()) + clean_transactions(entry); +} + +void report_t::sum_all_accounts() +{ + session_transactions_iterator walker(session); + pass_down_transactions + (chain_xact_handlers(xact_handler_ptr(new set_account_value), false), + walker); + // no flush() needed with set_account_value + sum_accounts(*session.master); +} + +void report_t::accounts_report(acct_handler_ptr handler, + const bool print_final_total) +{ + sum_all_accounts(); + + if (sort_string.empty()) { + accounts_iterator walker(*session.master); + pass_down_accounts<accounts_iterator>(handler, walker); + } else { + sorted_accounts_iterator walker(*session.master, sort_string); + pass_down_accounts<sorted_accounts_iterator>(handler, walker); + } + handler->flush(); + + if (print_final_total && account_has_xdata(*session.master)) { + account_xdata_t& xdata = account_xdata(*session.master); + if (! show_collapsed && xdata.total) { +#if 0 + *out << "--------------------\n"; + xdata.value = xdata.total; + handler->format.format(*out, details_t(*journal->master)); +#endif + } + } + + if (DO_VERIFY()) { + clean_transactions(); + clean_accounts(); + } +} + +void report_t::commodities_report(const string& format) +{ +} + +void report_t::entry_report(const entry_t& entry, const string& format) +{ +} + +void report_t::clean_transactions() +{ + session_transactions_iterator walker(session); + pass_down_transactions + (xact_handler_ptr(new clear_transaction_xdata), walker); +} + +void report_t::clean_transactions(entry_t& entry) +{ + entry_transactions_iterator walker(entry); + pass_down_transactions(xact_handler_ptr(new clear_transaction_xdata), walker); +} + +void report_t::clean_accounts() +{ + accounts_iterator acct_walker(*session.master); + pass_down_accounts<accounts_iterator> + (acct_handler_ptr(new clear_account_xdata), acct_walker); +} + +value_t report_t::abbrev(expr::call_scope_t& args) +{ + if (args.size() < 2) + throw_(std::logic_error, "usage: abbrev(STRING, WIDTH [, STYLE, ABBREV_LEN])"); + + string str = args[0].as_string(); +#if 0 + long wid = args[1]; + + elision_style_t style = session.elision_style; + if (args.size() == 3) + style = static_cast<elision_style_t>(args[2].as_long()); +#endif + + long abbrev_len = session.abbrev_length; + if (args.size() == 4) + abbrev_len = args[3].as_long(); + +#if 0 + return value_t(abbreviate(str, wid, style, true, + static_cast<int>(abbrev_len)), true); +#else + return NULL_VALUE; +#endif +} + +value_t report_t::ftime(expr::call_scope_t& args) +{ + if (args.size() < 1) + throw_(std::logic_error, "usage: ftime(DATE [, DATE_FORMAT])"); + + datetime_t date = args[0].as_datetime(); + + string date_format; + if (args.size() == 2) + date_format = args[1].as_string(); +#if 0 + // jww (2007-04-18): Need to setup an output facet here + else + date_format = moment_t::output_format; + + return value_t(date.as_string(date_format), true); +#else + return NULL_VALUE; +#endif +} + +#if 0 +optional<value_t> +report_t::resolve(const string& name, expr::call_scope_t& args) +{ + const char * p = name.c_str(); + switch (*p) { + case 'a': + if (name == "abbrev") { + return abbrev(args); + } + break; + + case 'f': + if (name == "ftime") { + return ftime(args); + } + break; + } + return expr::scope_t::resolve(name, args); +} +#endif + +expr::ptr_op_t report_t::lookup(const string& name) +{ + const char * p = name.c_str(); + switch (*p) { + case 'o': + if (std::strncmp(p, "option_", 7) == 0) { + p = p + 7; + switch (*p) { + case 'a': +#if 0 + if (std::strcmp(p, "accounts") == 0) + return MAKE_FUNCTOR(report_t::option_accounts); + else +#endif + if (std::strcmp(p, "amount") == 0) + return MAKE_FUNCTOR(report_t::option_amount); + break; + + case 'b': + if (std::strcmp(p, "bar") == 0) + return MAKE_FUNCTOR(report_t::option_bar); + break; + +#if 0 + case 'c': + if (std::strcmp(p, "clean") == 0) + return MAKE_FUNCTOR(report_t::option_clean); + else if (std::strcmp(p, "compact") == 0) + return MAKE_FUNCTOR(report_t::option_compact); + break; +#endif + + case 'e': +#if 0 + if (std::strcmp(p, "entries") == 0) + return MAKE_FUNCTOR(report_t::option_entries); + else if (std::strcmp(p, "eval") == 0) + return MAKE_FUNCTOR(report_t::option_eval); + else if (std::strcmp(p, "exclude") == 0) + return MAKE_FUNCTOR(report_t::option_remove); +#endif + break; + + case 'f': +#if 0 + if (std::strcmp(p, "foo") == 0) + return MAKE_FUNCTOR(report_t::option_foo); + else +#endif + if (std::strcmp(p, "format") == 0) + return MAKE_FUNCTOR(report_t::option_format); + break; + + case 'i': +#if 0 + if (std::strcmp(p, "include") == 0) + return MAKE_FUNCTOR(report_t::option_select); +#endif + break; + + case 'l': +#if 0 + if (! *(p + 1) || std::strcmp(p, "limit") == 0) + return MAKE_FUNCTOR(report_t::option_limit); +#endif + break; + +#if 0 + case 'm': + if (std::strcmp(p, "merge") == 0) + return MAKE_FUNCTOR(report_t::option_merge); + break; +#endif + + case 'r': +#if 0 + if (std::strcmp(p, "remove") == 0) + return MAKE_FUNCTOR(report_t::option_remove); +#endif + break; + +#if 0 + case 's': + if (std::strcmp(p, "select") == 0) + return MAKE_FUNCTOR(report_t::option_select); + else if (std::strcmp(p, "split") == 0) + return MAKE_FUNCTOR(report_t::option_split); + break; +#endif + + case 't': + if (! *(p + 1)) + return MAKE_FUNCTOR(report_t::option_amount); + else if (std::strcmp(p, "total") == 0) + return MAKE_FUNCTOR(report_t::option_total); + break; + + case 'T': + if (! *(p + 1)) + return MAKE_FUNCTOR(report_t::option_total); + break; + } + } + break; + } - return formatter; + return expr::symbol_scope_t::lookup(name); } } // namespace ledger |