summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2006-03-24 16:49:50 +0000
committerJohn Wiegley <johnw@newartisans.com>2008-04-13 02:41:32 -0400
commitf42d76f85f84eb5ad82bbabec804322989d22185 (patch)
tree5ce4edd61095f7ffdf73a4a0346caadcd035058f
parented7e16230175a0b8b256ab47ec1fb6f9de79d43a (diff)
downloadfork-ledger-f42d76f85f84eb5ad82bbabec804322989d22185.tar.gz
fork-ledger-f42d76f85f84eb5ad82bbabec804322989d22185.tar.bz2
fork-ledger-f42d76f85f84eb5ad82bbabec804322989d22185.zip
Added a new script that enables use of Ledger as a smart "du".
-rw-r--r--report.cc414
-rw-r--r--report.h80
2 files changed, 494 insertions, 0 deletions
diff --git a/report.cc b/report.cc
new file mode 100644
index 00000000..bd6338d2
--- /dev/null
+++ b/report.cc
@@ -0,0 +1,414 @@
+#include "report.h"
+
+namespace ledger {
+
+report_t::report_t()
+{
+ ledger::amount_expr = "@a";
+ ledger::total_expr = "@O";
+
+ predicate = "";
+ secondary_predicate = "";
+ display_predicate = "";
+ descend_expr = "";
+
+ pricing_leeway = 24 * 3600;
+ 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_format.empty())
+ datetime_t::date_format = date_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)
+{
+ bool remember_components = false;
+
+ item_handler<transaction_t> * formatter = NULL;
+
+ ptrs.push_back(formatter = base_formatter);
+
+ // format_transactions write each transaction received to the
+ // output stream.
+ if (! (command == "b" || command == "E")) {
+ // 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));
+
+ // 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));
+
+ // 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));
+
+ // component_transactions looks for reported transaction that
+ // match the given `descend_expr', and then reports the
+ // transactions which made up the total for that reported
+ // transaction.
+ if (! descend_expr.empty()) {
+ std::list<std::string> descend_exprs;
+
+ std::string::size_type beg = 0;
+ for (std::string::size_type pos = descend_expr.find(';');
+ pos != std::string::npos;
+ beg = pos + 1, pos = descend_expr.find(';', beg))
+ descend_exprs.push_back(std::string(descend_expr, beg, pos));
+ descend_exprs.push_back(std::string(descend_expr, beg));
+
+ for (std::list<std::string>::reverse_iterator i =
+ descend_exprs.rbegin();
+ i != descend_exprs.rend();
+ i++)
+ ptrs.push_back(formatter =
+ new component_transactions(formatter, *i));
+
+ remember_components = true;
+ }
+
+ // reconcile_transactions will pass through only those
+ // transactions which can be reconciled to a given balance
+ // (calculated against the transactions which it receives).
+ if (! reconcile_balance.empty()) {
+ std::time_t cutoff = now;
+ if (! reconcile_date.empty())
+ parse_date(reconcile_date.c_str(), &cutoff);
+ ptrs.push_back(formatter =
+ new reconcile_transactions
+ (formatter, 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));
+
+ // 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));
+ else
+ ptrs.push_back(formatter =
+ new sort_transactions(formatter, 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));
+
+ // 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));
+
+ // subtotal_transactions combines all the transactions it receives
+ // into one subtotal entry, which has one transaction for each
+ // commodity in each account.
+ //
+ // period_transactions is like subtotal_transactions, but it
+ // subtotals according to time periods rather than totalling
+ // everything.
+ //
+ // dow_transactions is like period_transactions, except that it
+ // 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));
+
+ if (days_of_the_week)
+ ptrs.push_back(formatter =
+ new dow_transactions(formatter, remember_components));
+ else if (by_payee)
+ ptrs.push_back(formatter =
+ new by_payee_transactions(formatter, 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"));
+ }
+ }
+
+ // invert_transactions inverts the value of the transactions it
+ // receives.
+ if (show_inverted)
+ ptrs.push_back(formatter = new invert_transactions(formatter));
+
+ // related_transactions will pass along all transactions related
+ // to the transaction received. If `show_all_related' is true,
+ // then all the entry's transactions are passed; meaning that if
+ // 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));
+
+ // This filter_transactions will only pass through transactions
+ // matching the `predicate'.
+ if (! predicate.empty())
+ ptrs.push_back(formatter = new filter_transactions(formatter, predicate));
+
+ // budget_transactions takes a set of transactions from a data
+ // file and uses them to generate "budget transactions" which
+ // balance against the reported transactions.
+ //
+ // forecast_transactions is a lot like budget_transactions, except
+ // that it adds entries only for the future, and does not balance
+ // 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);
+
+ // Apply this before the budget handler, so that only matching
+ // transactions are calculated toward the budget. The use of
+ // filter_transactions above will further clean the results so
+ // that no automated transactions that don't match the filter get
+ // reported.
+ if (! predicate.empty())
+ ptrs.push_back(formatter = new filter_transactions(formatter, 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);
+
+ // See above, under budget_transactions.
+ if (! predicate.empty())
+ ptrs.push_back(formatter = new filter_transactions(formatter, predicate));
+ }
+
+ if (comm_as_payee)
+ ptrs.push_back(formatter = new set_comm_as_payee(formatter));
+ else if (code_as_payee)
+ ptrs.push_back(formatter = new set_code_as_payee(formatter));
+
+ return formatter;
+}
+
+} // namespace ledger
diff --git a/report.h b/report.h
new file mode 100644
index 00000000..14923ab5
--- /dev/null
+++ b/report.h
@@ -0,0 +1,80 @@
+#ifndef _REPORT_H
+#define _REPORT_H
+
+#include "ledger.h"
+#include "timing.h"
+
+#include <iostream>
+#include <memory>
+#include <list>
+
+namespace ledger {
+
+class report_t
+{
+ public:
+ std::string output_file;
+ std::string predicate;
+ std::string secondary_predicate;
+ std::string display_predicate;
+ std::string report_period;
+ std::string report_period_sort;
+ std::string format_string;
+ std::string sort_string;
+ std::string amount_expr;
+ std::string total_expr;
+ std::string descend_expr;
+ std::string forecast_limit;
+ std::string reconcile_balance;
+ std::string reconcile_date;
+ std::string date_format;
+
+ unsigned long budget_flags;
+ unsigned long pricing_leeway;
+
+ int head_entries;
+ int tail_entries;
+
+ bool show_collapsed;
+ bool show_subtotal;
+ bool show_totals;
+ bool show_related;
+ bool show_all_related;
+ bool show_inverted;
+ bool show_empty;
+ bool days_of_the_week;
+ bool by_payee;
+ bool comm_as_payee;
+ bool code_as_payee;
+ bool show_revalued;
+ bool show_revalued_only;
+ bool keep_price;
+ bool keep_date;
+ bool keep_tag;
+ bool entry_sort;
+ bool sort_all;
+
+ report_t();
+
+ void 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 = false,
+ const bool add_account_short_masks = false,
+ const bool logical_and = true);
+
+ void process_options(const std::string& command,
+ strings_list::iterator arg,
+ strings_list::iterator args_end);
+
+ item_handler<transaction_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);
+};
+
+} // namespace ledger
+
+#endif // _REPORT_H