ledger/walk.cc
John Wiegley 5bf3f536b3 Restored the interval_t time and added a new "period" debugging command.
You can use 'ledger period "daily in june"' to find out how Ledger will parse
that date string, plus up to the first 20 dates it encounters in the range.
Note that the 'end' displayed is currently exclusive.
2008-08-02 00:37:36 -04:00

984 lines
22 KiB
C++

#include "walk.h"
#include "session.h"
#include "format.h"
#include "textual.h"
#include <algorithm>
namespace ledger {
template <>
bool compare_items<xact_t>::operator()(const xact_t * left,
const xact_t * right)
{
assert(left);
assert(right);
#if 0
xact_xdata_t& lxdata(xact_xdata(*left));
if (! (lxdata.dflags & XACT_SORT_CALC)) {
sort_order.compute(lxdata.sort_value, details_t(*left));
lxdata.sort_value.reduce();
lxdata.dflags |= XACT_SORT_CALC;
}
xact_xdata_t& rxdata(xact_xdata(*right));
if (! (rxdata.dflags & XACT_SORT_CALC)) {
sort_order.compute(rxdata.sort_value, details_t(*right));
rxdata.sort_value.reduce();
rxdata.dflags |= XACT_SORT_CALC;
}
DEBUG("ledger.walk.compare_items_xact",
"lxdata.sort_value = " << lxdata.sort_value);
DEBUG("ledger.walk.compare_items_xact",
"rxdata.sort_value = " << rxdata.sort_value);
return lxdata.sort_value < rxdata.sort_value;
#else
return false;
#endif
}
xact_xdata_t& xact_xdata(const xact_t& xact)
{
if (! xact.data)
xact.data = new xact_xdata_t();
return *static_cast<xact_xdata_t *>(xact.data);
}
void add_xact_to(const xact_t& xact, value_t& value)
{
if (xact_has_xdata(xact) &&
xact_xdata_(xact).dflags & XACT_COMPOUND) {
value += xact_xdata_(xact).value;
}
else if (xact.cost || (! value.is_null() && ! value.is_realzero())) {
value.add(xact.amount, xact.cost);
}
else {
value = xact.amount;
}
}
void entries_iterator::reset(session_t& session)
{
journals_i = session.journals.begin();
journals_end = session.journals.end();
journals_uninitialized = false;
if (journals_i != journals_end) {
entries_i = (*journals_i).entries.begin();
entries_end = (*journals_i).entries.end();
entries_uninitialized = false;
} else {
entries_uninitialized = true;
}
}
entry_t * entries_iterator::operator()()
{
if (entries_i == entries_end) {
journals_i++;
if (journals_i == journals_end)
return NULL;
entries_i = (*journals_i).entries.begin();
entries_end = (*journals_i).entries.end();
}
return *entries_i++;
}
void session_xacts_iterator::reset(session_t& session)
{
entries.reset(session);
entry_t * entry = entries();
if (entry != NULL)
xacts.reset(*entry);
}
xact_t * session_xacts_iterator::operator()()
{
xact_t * xact = xacts();
if (xact == NULL) {
entry_t * entry = entries();
if (entry != NULL) {
xacts.reset(*entry);
xact = xacts();
}
}
return 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_account(xact);
assert(acct);
account_xdata_t& xdata = account_xdata(*acct);
add_xact_to(xact, 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(*xact).dflags &= ~XACT_SORT_CALC;
item_handler<xact_t>::operator()(*xact);
}
xacts.clear();
}
void calc_xacts::operator()(xact_t& xact)
{
try {
xact_xdata_t& xdata(xact_xdata(xact));
if (last_xact && xact_has_xdata(*last_xact)) {
if (xdata.total.is_null())
xdata.total = xact_xdata_(*last_xact).total;
else
xdata.total += xact_xdata_(*last_xact).total;
xdata.index = xact_xdata_(*last_xact).index + 1;
} else {
xdata.index = 0;
}
if (! (xdata.dflags & XACT_NO_TOTAL))
add_xact_to(xact, 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) &&
xact_xdata_(xact).dflags & XACT_COMPOUND) {
xact_xdata_(xact).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(xact).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(*account))
if (! (account_xdata_(*account).dflags & ACCOUNT_HAS_NON_VIRTUALS)) {
xact.add_flags(XACT_VIRTUAL);
if (! (account_xdata_(*account).dflags & ACCOUNT_HAS_UNB_VIRTUALS))
xact.add_flags(XACT_BALANCE);
}
xact_xdata_t& xdata(xact_xdata(xact));
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_COMPOUND;
break;
default:
assert(false); // jww (2008-04-24): What to do here?
break;
}
if (flags)
xdata.dflags |= 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();
add_xact_to(xact, 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_xdata_t& xdata = xact_xdata(*r_xact);
if (! (xdata.dflags & XACT_HANDLED) &&
(! (xdata.dflags & XACT_RECEIVED) ?
! r_xact->has_flags(XACT_AUTO | XACT_VIRTUAL) :
also_matching)) {
xdata.dflags |= XACT_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_xdata_t& xdata = xact_xdata(*xact);
if (! (xdata.dflags & XACT_HANDLED) &&
! xact->has_flags(XACT_AUTO)) {
xdata.dflags |= XACT_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;
xact_xdata(*last_xact).date = date;
#if 0
compute_total(cur_bal, details_t(*last_xact));
#endif
cur_bal.round();
// jww (2008-04-24): What does this do?
#if 0
xact_xdata(*last_xact).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_NO_TOTAL, xact_temps,
*handler);
}
}
void changed_value_xacts::operator()(xact_t& xact)
{
if (last_xact) {
date_t date;
if (xact_has_xdata(*last_xact))
date = xact_xdata_(*last_xact).date;
else
date = xact.date();
output_diff(date);
}
if (changed_values_only)
xact_xdata(xact).dflags |= XACT_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) &&
xact_xdata_(xact).have_component_xacts())
xact_xdata_(xact).walk_component_xacts(*handler);
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_account(xact);
assert(acct);
values_map::iterator i = values.find(acct->fullname());
if (i == values.end()) {
value_t temp;
add_xact_to(xact, 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 {
add_xact_to(xact, (*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))
account_xdata(*xact_account(xact)).dflags |= ACCOUNT_HAS_NON_VIRTUALS;
else if (! xact.has_flags(XACT_BALANCE))
account_xdata(*xact_account(xact)).dflags |= ACCOUNT_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++) {
// jww (2008-04-24): What to use here?
#if 0
start = finish = 0;
#endif
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_account(xact)->fullname());
#if 0
// jww (2008-04-24): Need a new debug macro here
DEBUG_TIME("ledger.walk.budget", begin);
DEBUG_TIME("ledger.walk.budget", date);
#endif
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_account(xact);
acct;
acct = acct->parent) {
if (acct == xact_account(*pair.second)) {
xact_in_budget = true;
// Report the xact as if it had occurred in the parent
// account.
if (xact_account(xact) != acct)
xact_xdata(xact).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 0
// jww (2008-04-24): Does seconds() here give the total seconds?
if (next < begin || // wraparound
(is_valid(last) && (next - last).seconds() > 365 * 5 * 24 * 3600))
break;
#endif
begin = next;
item_handler<xact_t>::operator()(temp);
if (xact_has_xdata(temp) &&
xact_xdata_(temp).dflags & XACT_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();
}
template <>
bool compare_items<account_t>::operator()(const account_t * left,
const account_t * right)
{
assert(left);
assert(right);
#if 0
account_xdata_t& lxdata(account_xdata(*left));
if (! (lxdata.dflags & ACCOUNT_SORT_CALC)) {
sort_order.compute(lxdata.sort_value, details_t(*left));
lxdata.dflags |= ACCOUNT_SORT_CALC;
}
account_xdata_t& rxdata(account_xdata(*right));
if (! (rxdata.dflags & ACCOUNT_SORT_CALC)) {
sort_order.compute(rxdata.sort_value, details_t(*right));
rxdata.dflags |= ACCOUNT_SORT_CALC;
}
return lxdata.sort_value < rxdata.sort_value;
#else
return false;
#endif
}
account_xdata_t& account_xdata(const account_t& account)
{
if (! account.data)
account.data = new account_xdata_t();
return *static_cast<account_xdata_t *>(account.data);
}
void sum_accounts(account_t& account)
{
account_xdata_t& xdata(account_xdata(account));
foreach (accounts_map::value_type& pair, account.accounts) {
sum_accounts(*pair.second);
xdata.total += account_xdata_(*pair.second).total;
xdata.total_count += (account_xdata_(*pair.second).total_count +
account_xdata_(*pair.second).count);
}
value_t result;
#if 0
compute_amount(result, details_t(account));
#endif
if (! result.is_realzero())
xdata.total += result;
xdata.total_count += xdata.count;
}
account_t * accounts_iterator::operator()()
{
while (! accounts_i.empty() &&
accounts_i.back() == accounts_end.back()) {
accounts_i.pop_back();
accounts_end.pop_back();
}
if (accounts_i.empty())
return NULL;
account_t * account = (*(accounts_i.back()++)).second;
assert(account);
// If this account has children, queue them up to be iterated next.
if (! account->accounts.empty())
push_back(*account);
return account;
}
void sorted_accounts_iterator::sort_accounts(account_t& account,
accounts_deque_t& deque)
{
foreach (accounts_map::value_type& pair, account.accounts)
deque.push_back(pair.second);
std::stable_sort(deque.begin(), deque.end(),
compare_items<account_t>(sort_cmp));
}
account_t * sorted_accounts_iterator::operator()()
{
while (! sorted_accounts_i.empty() &&
sorted_accounts_i.back() == sorted_accounts_end.back()) {
sorted_accounts_i.pop_back();
sorted_accounts_end.pop_back();
assert(! accounts_list.empty());
accounts_list.pop_back();
}
if (sorted_accounts_i.empty())
return NULL;
account_t * account = *sorted_accounts_i.back()++;
assert(account);
// If this account has children, queue them up to be iterated next.
if (! account->accounts.empty())
push_back(*account);
account_xdata(*account).dflags &= ~ACCOUNT_SORT_CALC;
return account;
}
void walk_commodities(commodity_pool_t::commodities_by_ident& commodities,
item_handler<xact_t>& handler)
{
std::list<xact_t> xact_temps;
std::list<entry_t> entry_temps;
std::list<account_t> acct_temps;
for (commodity_pool_t::commodities_by_ident::iterator
i = commodities.begin();
i != commodities.end();
i++) {
if ((*i)->has_flags(COMMODITY_STYLE_NOMARKET))
continue;
entry_temps.push_back(entry_t());
acct_temps.push_back(account_t(NULL, (*i)->symbol()));
if ((*i)->history())
foreach (const commodity_t::history_map::value_type& pair,
(*i)->history()->prices) {
entry_temps.back()._date = pair.first.date();
xact_temps.push_back(xact_t(&acct_temps.back()));
xact_t& temp = xact_temps.back();
temp.entry = &entry_temps.back();
temp.amount = pair.second;
temp.add_flags(XACT_TEMP);
entry_temps.back().add_xact(&temp);
handler(xact_temps.back());
}
}
handler.flush();
clear_entries_xacts(entry_temps);
}
void journals_iterator::reset(session_t& session)
{
journals_i = session.journals.begin();
journals_end = session.journals.end();
}
journal_t * journals_iterator::operator()()
{
if (journals_i == journals_end)
return NULL;
return &(*journals_i++);
}
} // namespace ledger