summaryrefslogtreecommitdiff
path: root/filters.cc
diff options
context:
space:
mode:
Diffstat (limited to 'filters.cc')
-rw-r--r--filters.cc755
1 files changed, 755 insertions, 0 deletions
diff --git a/filters.cc b/filters.cc
new file mode 100644
index 00000000..e5455423
--- /dev/null
+++ b/filters.cc
@@ -0,0 +1,755 @@
+/*
+ * Copyright (c) 2003-2008, 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 "filters.h"
+#include "iterators.h"
+#include "compare.h"
+#include "session.h"
+#include "format.h"
+#include "textual.h"
+
+namespace ledger {
+
+pass_down_xacts::pass_down_xacts(xact_handler_ptr handler,
+ xacts_iterator& iter)
+ : item_handler<xact_t>(handler)
+{
+ TRACE_CTOR(pass_down_xacts, "xact_handler_ptr, xacts_iterator");
+
+ for (xact_t * xact = iter(); xact; xact = iter())
+ item_handler<xact_t>::operator()(*xact);
+}
+
+void truncate_entries::flush()
+{
+ if (! xacts.size())
+ return;
+
+ entry_t * last_entry = (*xacts.begin())->entry;
+
+ int l = 0;
+ foreach (xact_t * xact, xacts)
+ if (last_entry != xact->entry) {
+ l++;
+ last_entry = xact->entry;
+ }
+ l++;
+
+ last_entry = (*xacts.begin())->entry;
+
+ int i = 0;
+ foreach (xact_t * xact, xacts) {
+ if (last_entry != xact->entry) {
+ last_entry = xact->entry;
+ i++;
+ }
+
+ bool print = false;
+ if (head_count) {
+ if (head_count > 0 && i < head_count)
+ print = true;
+ else if (head_count < 0 && i >= - head_count)
+ print = true;
+ }
+
+ if (! print && tail_count) {
+ if (tail_count > 0 && l - i <= tail_count)
+ print = true;
+ else if (tail_count < 0 && l - i > - tail_count)
+ print = true;
+ }
+
+ if (print)
+ item_handler<xact_t>::operator()(*xact);
+ }
+ xacts.clear();
+
+ item_handler<xact_t>::flush();
+}
+
+void set_account_value::operator()(xact_t& xact)
+{
+ account_t * acct = xact.reported_account();
+
+ account_t::xdata_t& xdata(acct->xdata());
+ xact.add_to_value(xdata.value);
+
+ xdata.count++;
+ if (xact.has_flags(XACT_VIRTUAL))
+ xdata.virtuals++;
+
+ item_handler<xact_t>::operator()(xact);
+}
+
+void sort_xacts::post_accumulated_xacts()
+{
+ std::stable_sort(xacts.begin(), xacts.end(),
+ compare_items<xact_t>(sort_order));
+
+ foreach (xact_t * xact, xacts) {
+ xact->xdata().drop_flags(XACT_EXT_SORT_CALC);
+ item_handler<xact_t>::operator()(*xact);
+ }
+
+ xacts.clear();
+}
+
+void calc_xacts::operator()(xact_t& xact)
+{
+ try {
+ xact_t::xdata_t& xdata(xact.xdata());
+
+ if (last_xact && last_xact->has_xdata()) {
+ if (xdata.total.is_null())
+ xdata.total = last_xact->xdata().total;
+ else
+ xdata.total += last_xact->xdata().total;
+ xdata.index = last_xact->xdata().index + 1;
+ } else {
+ xdata.index = 0;
+ }
+
+ if (! xdata.has_flags(XACT_EXT_NO_TOTAL))
+ xact.add_to_value(xdata.total);
+
+ item_handler<xact_t>::operator()(xact);
+
+ last_xact = &xact;
+ }
+ catch (const std::exception& err) {
+ add_error_context("Calculating transaction at");
+#if 0
+ add_error_context(xact_context(xact));
+#endif
+ throw err;
+ }
+}
+
+void invert_xacts::operator()(xact_t& xact)
+{
+ if (xact.has_xdata() &&
+ xact.xdata().has_flags(XACT_EXT_COMPOUND)) {
+ xact.xdata().value.negate();
+ } else {
+ xact.amount.negate();
+ if (xact.cost)
+ xact.cost->negate();
+ }
+
+ item_handler<xact_t>::operator()(xact);
+}
+
+
+static inline
+void handle_value(const value_t& value,
+ account_t * account,
+ entry_t * entry,
+ unsigned int flags,
+ std::list<xact_t>& temps,
+ item_handler<xact_t>& handler,
+ const date_t& date = date_t(),
+ xacts_list * component_xacts = NULL)
+{
+ temps.push_back(xact_t(account));
+ xact_t& xact(temps.back());
+ xact.entry = entry;
+ xact.add_flags(XACT_TEMP);
+ entry->add_xact(&xact);
+
+ // If there are component xacts to associate with this
+ // temporary, do so now.
+
+ if (component_xacts)
+ xact.xdata().copy_component_xacts(*component_xacts);
+
+ // If the account for this xact is all virtual, then report
+ // the xact as such. This allows subtotal reports to show
+ // "(Account)" for accounts that contain only virtual xacts.
+
+ if (account && account->has_xdata())
+ if (! account->xdata().has_flags(ACCOUNT_EXT_HAS_NON_VIRTUALS)) {
+ xact.add_flags(XACT_VIRTUAL);
+ if (! account->xdata().has_flags(ACCOUNT_EXT_HAS_UNB_VIRTUALS))
+ xact.add_flags(XACT_BALANCE);
+ }
+
+ xact_t::xdata_t& xdata(xact.xdata());
+
+ if (is_valid(date))
+ xdata.date = date;
+
+ value_t temp(value);
+
+ switch (value.type()) {
+ case value_t::BOOLEAN:
+ case value_t::DATETIME:
+ case value_t::DATE:
+ case value_t::INTEGER:
+ temp.cast(value_t::AMOUNT);
+ // fall through...
+
+ case value_t::AMOUNT:
+ xact.amount = temp.as_amount();
+ break;
+
+ case value_t::BALANCE:
+ case value_t::BALANCE_PAIR:
+ xdata.value = temp;
+ flags |= XACT_EXT_COMPOUND;
+ break;
+
+ default:
+ assert(false); // jww (2008-04-24): What to do here?
+ break;
+ }
+
+ if (flags)
+ xdata.add_flags(flags);
+
+ handler(xact);
+}
+
+void collapse_xacts::report_subtotal()
+{
+ assert(count >= 1);
+
+ if (count == 1) {
+ item_handler<xact_t>::operator()(*last_xact);
+ } else {
+ entry_temps.push_back(entry_t());
+ entry_t& entry = entry_temps.back();
+ entry.payee = last_entry->payee;
+ entry._date = last_entry->_date;
+
+ handle_value(subtotal, &totals_account, last_entry, 0, xact_temps,
+ *handler);
+ }
+
+ last_entry = NULL;
+ last_xact = NULL;
+ subtotal = 0L;
+ count = 0;
+}
+
+void collapse_xacts::operator()(xact_t& xact)
+{
+ // If we've reached a new entry, report on the subtotal
+ // accumulated thus far.
+
+ if (last_entry && last_entry != xact.entry && count > 0)
+ report_subtotal();
+
+ xact.add_to_value(subtotal);
+ count++;
+
+ last_entry = xact.entry;
+ last_xact = &xact;
+}
+
+void related_xacts::flush()
+{
+ if (xacts.size() > 0) {
+ foreach (xact_t * xact, xacts) {
+ if (xact->entry) {
+ foreach (xact_t * r_xact, xact->entry->xacts) {
+ xact_t::xdata_t& xdata(r_xact->xdata());
+ if (! xdata.has_flags(XACT_EXT_HANDLED) &&
+ (! xdata.has_flags(XACT_EXT_RECEIVED) ?
+ ! r_xact->has_flags(XACT_AUTO | XACT_VIRTUAL) :
+ also_matching)) {
+ xdata.add_flags(XACT_EXT_HANDLED);
+ item_handler<xact_t>::operator()(*r_xact);
+ }
+ }
+ } else {
+ // This code should only be reachable from the "output"
+ // command, since that is the only command which attempts to
+ // output auto or period entries.
+ xact_t::xdata_t& xdata(xact->xdata());
+ if (! xdata.has_flags(XACT_EXT_HANDLED) &&
+ ! xact->has_flags(XACT_AUTO)) {
+ xdata.add_flags(XACT_EXT_HANDLED);
+ item_handler<xact_t>::operator()(*xact);
+ }
+ }
+ }
+ }
+
+ item_handler<xact_t>::flush();
+}
+
+void changed_value_xacts::output_diff(const date_t& date)
+{
+ value_t cur_bal;
+
+ last_xact->xdata().date = date;
+#if 0
+ compute_total(cur_bal, details_t(*last_xact));
+#endif
+ cur_bal.round();
+
+#if 0
+ // jww (2008-04-24): What does this do?
+ last_xact->xdata().date = 0;
+#endif
+
+ if (value_t diff = cur_bal - last_balance) {
+ entry_temps.push_back(entry_t());
+ entry_t& entry = entry_temps.back();
+ entry.payee = "Commodities revalued";
+ entry._date = date;
+
+ handle_value(diff, NULL, &entry, XACT_EXT_NO_TOTAL, xact_temps,
+ *handler);
+ }
+}
+
+void changed_value_xacts::operator()(xact_t& xact)
+{
+ if (last_xact)
+ output_diff(last_xact->reported_date());
+
+ if (changed_values_only)
+ xact.xdata().add_flags(XACT_EXT_DISPLAYED);
+
+ item_handler<xact_t>::operator()(xact);
+
+#if 0
+ compute_total(last_balance, details_t(xact));
+#endif
+ last_balance.round();
+
+ last_xact = &xact;
+}
+
+void component_xacts::operator()(xact_t& xact)
+{
+ if (handler && pred(xact)) {
+ if (xact.has_xdata() &&
+ xact.xdata().has_component_xacts())
+#if 0
+ xact.xdata().walk_component_xacts(*handler);
+#else
+ ;
+#endif
+ else
+ (*handler)(xact);
+ }
+}
+
+void subtotal_xacts::report_subtotal(const char * spec_fmt)
+{
+ std::ostringstream out_date;
+ if (! spec_fmt) {
+ string fmt = "- ";
+ fmt += output_date_format;
+ out_date << format_date(finish, string(fmt));
+ } else {
+ out_date << format_date(finish, string(spec_fmt));
+ }
+
+ entry_temps.push_back(entry_t());
+ entry_t& entry = entry_temps.back();
+ entry.payee = out_date.str();
+ entry._date = start;
+
+ foreach (values_map::value_type& pair, values)
+ handle_value(pair.second.value, pair.second.account, &entry, 0,
+ xact_temps, *handler, finish, &pair.second.components);
+
+ values.clear();
+}
+
+void subtotal_xacts::operator()(xact_t& xact)
+{
+ if (! is_valid(start) || xact.date() < start)
+ start = xact.date();
+ if (! is_valid(finish) || xact.date() > finish)
+ finish = xact.date();
+
+ account_t * acct = xact.reported_account();
+ assert(acct);
+
+ values_map::iterator i = values.find(acct->fullname());
+ if (i == values.end()) {
+ value_t temp;
+ xact.add_to_value(temp);
+ std::pair<values_map::iterator, bool> result
+ = values.insert(values_pair(acct->fullname(), acct_value_t(acct, temp)));
+ assert(result.second);
+
+ if (remember_components)
+ (*result.first).second.components.push_back(&xact);
+ } else {
+ xact.add_to_value((*i).second.value);
+
+ if (remember_components)
+ (*i).second.components.push_back(&xact);
+ }
+
+ // If the account for this xact is all virtual, mark it as
+ // such, so that `handle_value' can show "(Account)" for accounts
+ // that contain only virtual xacts.
+
+ if (! xact.has_flags(XACT_VIRTUAL))
+ xact.reported_account()->xdata().add_flags(ACCOUNT_EXT_HAS_NON_VIRTUALS);
+ else if (! xact.has_flags(XACT_BALANCE))
+ xact.reported_account()->xdata().add_flags(ACCOUNT_EXT_HAS_UNB_VIRTUALS);
+}
+
+void interval_xacts::report_subtotal(const date_t& date)
+{
+ assert(last_xact);
+
+ start = interval.begin;
+ if (is_valid(date))
+ finish = date - gregorian::days(1);
+ else
+ finish = last_xact->date();
+
+ subtotal_xacts::report_subtotal();
+
+ last_xact = NULL;
+}
+
+void interval_xacts::operator()(xact_t& xact)
+{
+ const date_t& date(xact.date());
+
+ if ((is_valid(interval.begin) && date < interval.begin) ||
+ (is_valid(interval.end) && date >= interval.end))
+ return;
+
+ if (interval) {
+ if (! started) {
+ if (! is_valid(interval.begin))
+ interval.start(date);
+ start = interval.begin;
+ started = true;
+ }
+
+ date_t quant = interval.increment(interval.begin);
+ if (date >= quant) {
+ if (last_xact)
+ report_subtotal(quant);
+
+ date_t temp;
+ while (date >= (temp = interval.increment(quant))) {
+ if (quant == temp)
+ break;
+ quant = temp;
+ }
+ start = interval.begin = quant;
+ }
+
+ subtotal_xacts::operator()(xact);
+ } else {
+ item_handler<xact_t>::operator()(xact);
+ }
+
+ last_xact = &xact;
+}
+
+by_payee_xacts::~by_payee_xacts()
+{
+ TRACE_DTOR(by_payee_xacts);
+
+ foreach (payee_subtotals_map::value_type& pair, payee_subtotals)
+ checked_delete(pair.second);
+}
+
+void by_payee_xacts::flush()
+{
+ foreach (payee_subtotals_map::value_type& pair, payee_subtotals)
+ pair.second->report_subtotal(pair.first.c_str());
+
+ item_handler<xact_t>::flush();
+
+ payee_subtotals.clear();
+}
+
+void by_payee_xacts::operator()(xact_t& xact)
+{
+ payee_subtotals_map::iterator i = payee_subtotals.find(xact.entry->payee);
+ if (i == payee_subtotals.end()) {
+ payee_subtotals_pair
+ temp(xact.entry->payee,
+ new subtotal_xacts(handler, remember_components));
+ std::pair<payee_subtotals_map::iterator, bool> result
+ = payee_subtotals.insert(temp);
+
+ assert(result.second);
+ if (! result.second)
+ return;
+ i = result.first;
+ }
+
+ if (xact.date() > (*i).second->start)
+ (*i).second->start = xact.date();
+
+ (*(*i).second)(xact);
+}
+
+void set_comm_as_payee::operator()(xact_t& xact)
+{
+ entry_temps.push_back(*xact.entry);
+ entry_t& entry = entry_temps.back();
+ entry._date = xact.date();
+ entry.code = xact.entry->code;
+
+ if (xact.amount.commodity())
+ entry.payee = xact.amount.commodity().symbol();
+ else
+ entry.payee = "<none>";
+
+ xact_temps.push_back(xact);
+ xact_t& temp = xact_temps.back();
+ temp.entry = &entry;
+ temp.state = xact.state;
+ temp.add_flags(XACT_TEMP);
+
+ entry.add_xact(&temp);
+
+ item_handler<xact_t>::operator()(temp);
+}
+
+void set_code_as_payee::operator()(xact_t& xact)
+{
+ entry_temps.push_back(*xact.entry);
+ entry_t& entry = entry_temps.back();
+ entry._date = xact.date();
+
+ if (xact.entry->code)
+ entry.payee = *xact.entry->code;
+ else
+ entry.payee = "<none>";
+
+ xact_temps.push_back(xact);
+ xact_t& temp = xact_temps.back();
+ temp.entry = &entry;
+ temp.state = xact.state;
+ temp.add_flags(XACT_TEMP);
+
+ entry.add_xact(&temp);
+
+ item_handler<xact_t>::operator()(temp);
+}
+
+void dow_xacts::flush()
+{
+ for (int i = 0; i < 7; i++) {
+ start = finish = date_t();
+ foreach (xact_t * xact, days_of_the_week[i])
+ subtotal_xacts::operator()(*xact);
+ subtotal_xacts::report_subtotal("%As");
+ days_of_the_week[i].clear();
+ }
+
+ subtotal_xacts::flush();
+}
+
+void generate_xacts::add_period_entries
+ (period_entries_list& period_entries)
+{
+ foreach (period_entry_t * entry, period_entries)
+ foreach (xact_t * xact, entry->xacts)
+ add_xact(entry->period, *xact);
+}
+
+void generate_xacts::add_xact(const interval_t& period,
+ xact_t& xact)
+{
+ pending_xacts.push_back(pending_xacts_pair(period, &xact));
+}
+
+void budget_xacts::report_budget_items(const date_t& date)
+{
+ if (pending_xacts.size() == 0)
+ return;
+
+ bool reported;
+ do {
+ reported = false;
+ foreach (pending_xacts_list::value_type& pair, pending_xacts) {
+ date_t& begin = pair.first.begin;
+ if (! is_valid(begin)) {
+ pair.first.start(date);
+ begin = pair.first.begin;
+ }
+
+ if (begin < date &&
+ (! is_valid(pair.first.end) || begin < pair.first.end)) {
+ xact_t& xact = *pair.second;
+
+ DEBUG("ledger.walk.budget", "Reporting budget for "
+ << xact.reported_account()->fullname());
+
+ entry_temps.push_back(entry_t());
+ entry_t& entry = entry_temps.back();
+ entry.payee = "Budget entry";
+ entry._date = begin;
+
+ xact_temps.push_back(xact);
+ xact_t& temp = xact_temps.back();
+ temp.entry = &entry;
+ temp.add_flags(XACT_AUTO | XACT_TEMP);
+ temp.amount.negate();
+ entry.add_xact(&temp);
+
+ begin = pair.first.increment(begin);
+
+ item_handler<xact_t>::operator()(temp);
+
+ reported = true;
+ }
+ }
+ } while (reported);
+}
+
+void budget_xacts::operator()(xact_t& xact)
+{
+ bool xact_in_budget = false;
+
+ foreach (pending_xacts_list::value_type& pair, pending_xacts)
+ for (account_t * acct = xact.reported_account();
+ acct;
+ acct = acct->parent) {
+ if (acct == (*pair.second).reported_account()) {
+ xact_in_budget = true;
+ // Report the xact as if it had occurred in the parent
+ // account.
+ if (xact.reported_account() != acct)
+ xact.xdata().account = acct;
+ goto handle;
+ }
+ }
+
+ handle:
+ if (xact_in_budget && flags & BUDGET_BUDGETED) {
+ report_budget_items(xact.date());
+ item_handler<xact_t>::operator()(xact);
+ }
+ else if (! xact_in_budget && flags & BUDGET_UNBUDGETED) {
+ item_handler<xact_t>::operator()(xact);
+ }
+}
+
+void forecast_xacts::add_xact(const interval_t& period, xact_t& xact)
+{
+ generate_xacts::add_xact(period, xact);
+
+ interval_t& i = pending_xacts.back().first;
+ if (! is_valid(i.begin)) {
+ i.start(current_date);
+ i.begin = i.increment(i.begin);
+ } else {
+ while (i.begin < current_date)
+ i.begin = i.increment(i.begin);
+ }
+}
+
+void forecast_xacts::flush()
+{
+ xacts_list passed;
+ date_t last;
+
+ while (pending_xacts.size() > 0) {
+ pending_xacts_list::iterator least = pending_xacts.begin();
+ for (pending_xacts_list::iterator i = ++pending_xacts.begin();
+ i != pending_xacts.end();
+ i++)
+ if ((*i).first.begin < (*least).first.begin)
+ least = i;
+
+ date_t& begin = (*least).first.begin;
+
+ if (is_valid((*least).first.end) && begin >= (*least).first.end) {
+ pending_xacts.erase(least);
+ passed.remove((*least).second);
+ continue;
+ }
+
+ xact_t& xact = *(*least).second;
+
+ entry_temps.push_back(entry_t());
+ entry_t& entry = entry_temps.back();
+ entry.payee = "Forecast entry";
+ entry._date = begin;
+
+ xact_temps.push_back(xact);
+ xact_t& temp = xact_temps.back();
+ temp.entry = &entry;
+ temp.add_flags(XACT_AUTO | XACT_TEMP);
+ entry.add_xact(&temp);
+
+ date_t next = (*least).first.increment(begin);
+ if (next < begin || (is_valid(last) && (next - last).days() > 365 * 5))
+ break;
+ begin = next;
+
+ item_handler<xact_t>::operator()(temp);
+
+ if (temp.has_xdata() &&
+ temp.xdata().has_flags(XACT_EXT_MATCHES)) {
+ if (! pred(temp))
+ break;
+ last = temp.date();
+ passed.clear();
+ } else {
+ bool found = false;
+ foreach (xact_t * x, passed)
+ if (x == &xact) {
+ found = true;
+ break;
+ }
+
+ if (! found) {
+ passed.push_back(&xact);
+ if (passed.size() >= pending_xacts.size())
+ break;
+ }
+ }
+ }
+
+ item_handler<xact_t>::flush();
+}
+
+pass_down_accounts::pass_down_accounts(acct_handler_ptr handler,
+ accounts_iterator& iter)
+ : item_handler<account_t>(handler)
+{
+ TRACE_CTOR(pass_down_accounts,
+ "acct_handler_ptr, accounts_iterator");
+ for (account_t * account = iter(); account; account = iter())
+ item_handler<account_t>::operator()(*account);
+}
+
+} // namespace ledger