#include "walk.h" #include "format.h" namespace ledger { std::list transactions_xdata; std::list transactions_xdata_ptrs; std::list accounts_xdata; std::list accounts_xdata_ptrs; template <> bool compare_items::operator()(const transaction_t * left, const transaction_t * right) { assert(left); assert(right); transaction_xdata_t& lxdata(transaction_xdata(*left)); if (! (lxdata.dflags & TRANSACTION_SORT_CALC)) { sort_order->compute(lxdata.sort_value, details_t(*left)); lxdata.dflags |= TRANSACTION_SORT_CALC; } transaction_xdata_t& rxdata(transaction_xdata(*right)); if (! (rxdata.dflags & TRANSACTION_SORT_CALC)) { sort_order->compute(rxdata.sort_value, details_t(*right)); rxdata.dflags |= TRANSACTION_SORT_CALC; } return lxdata.sort_value < rxdata.sort_value; } void sort_transactions::flush() { std::stable_sort(transactions.begin(), transactions.end(), compare_items(sort_order)); for (transactions_deque::iterator i = transactions.begin(); i != transactions.end(); i++) (*handler)(**i); transactions.clear(); item_handler::flush(); } void calc_transactions::operator()(transaction_t& xact) { if (last_xact && transaction_has_xdata(*last_xact)) { transaction_xdata(xact).total += transaction_xdata(*last_xact).total; transaction_xdata(xact).index = transaction_xdata(*last_xact).index + 1; } else { transaction_xdata(xact).index = 0; } if (inverted) { xact.amount.negate(); if (xact.cost) xact.cost->negate(); } if (! (transaction_xdata(xact).dflags & TRANSACTION_NO_TOTAL)) add_transaction_to(xact, transaction_xdata(xact).total); (*handler)(xact); if (inverted) { xact.amount.negate(); if (xact.cost) xact.cost->negate(); } last_xact = &xact; } static void handle_value(const value_t& value, account_t * account, entry_t * entry, unsigned int flags, std::list& temps, item_handler * handler) { balance_t * bal = NULL; switch (value.type) { case value_t::BOOLEAN: case value_t::INTEGER: case value_t::AMOUNT: { temps.push_back(transaction_t(account)); transaction_t& xact = temps.back(); xact.entry = entry; switch (value.type) { case value_t::BOOLEAN: xact.amount = *((bool *) value.data); break; case value_t::INTEGER: xact.amount = *((unsigned int *) value.data); break; case value_t::AMOUNT: xact.amount = *((amount_t *) value.data); break; default: assert(0); break; } if (flags) transaction_xdata(xact).dflags |= flags; (*handler)(xact); break; } case value_t::BALANCE_PAIR: bal = &((balance_pair_t *) value.data)->quantity; // fall through... case value_t::BALANCE: if (! bal) bal = (balance_t *) value.data; for (amounts_map::const_iterator i = bal->amounts.begin(); i != bal->amounts.end(); i++) { temps.push_back(transaction_t(account)); transaction_t& xact = temps.back(); xact.entry = entry; xact.amount = (*i).second; if (flags) transaction_xdata(xact).dflags |= flags; (*handler)(xact); } break; default: assert(0); break; } } static inline void determine_amount(value_t& result, balance_pair_t& bal_pair) { account_xdata_t xdata; xdata.value = bal_pair; account_t temp_account; temp_account.data = &xdata; format_t::compute_amount(result, details_t(temp_account)); } void collapse_transactions::report_cumulative_subtotal() { if (count == 1) { (*handler)(*last_xact); } else { assert(count > 1); value_t result; determine_amount(result, subtotal); handle_value(result, &totals_account, last_entry, 0, xact_temps, handler); } last_entry = NULL; last_xact = NULL; subtotal = 0; count = 0; } void changed_value_transactions::output_diff(const std::time_t current) { value_t prev_bal; value_t cur_bal; std::time_t prev_date = last_xact->entry->date; format_t::compute_total(prev_bal, details_t(*last_xact)); last_xact->entry->date = current; format_t::compute_total(cur_bal, details_t(*last_xact)); last_xact->entry->date = prev_date; cur_bal -= prev_bal; if (cur_bal) { entry_temps.push_back(entry_t()); entry_t& entry = entry_temps.back(); entry.payee = "Commodities revalued"; entry.date = current; handle_value(cur_bal, NULL, &entry, TRANSACTION_NO_TOTAL, xact_temps, handler); } } void changed_value_transactions::operator()(transaction_t& xact) { if (last_xact) output_diff(xact.entry->date); if (changed_values_only) transaction_xdata(xact).dflags |= TRANSACTION_DISPLAYED; (*handler)(xact); last_xact = &xact; } void subtotal_transactions::flush(const char * spec_fmt) { char buf[256]; if (! spec_fmt) { std::string fmt = "- "; fmt += format_t::date_format; // Make sure the end date is inclusive if (start != finish) finish -= 86400; std::strftime(buf, 255, fmt.c_str(), std::localtime(&finish)); } else { std::strftime(buf, 255, spec_fmt, std::localtime(&finish)); } entry_temps.push_back(entry_t()); entry_t& entry = entry_temps.back(); entry.payee = buf; value_t result; for (balances_map::iterator i = balances.begin(); i != balances.end(); i++) { entry.date = finish; value_t result; determine_amount(result, (*i).second); entry.date = start; handle_value(result, (*i).first, &entry, 0, xact_temps, handler); } balances.clear(); item_handler::flush(); } void subtotal_transactions::operator()(transaction_t& xact) { if (balances.size() == 0) { start = finish = xact.entry->date; } else { if (std::difftime(xact.entry->date, start) < 0) start = xact.entry->date; if (std::difftime(xact.entry->date, finish) > 0) finish = xact.entry->date; } balances_map::iterator i = balances.find(xact.account); if (i == balances.end()) { balance_pair_t temp; add_transaction_to(xact, temp); balances.insert(balances_pair(xact.account, temp)); } else { add_transaction_to(xact, (*i).second); } } void interval_transactions::operator()(transaction_t& xact) { if ((interval.begin && std::difftime(xact.entry->date, interval.begin) < 0) || (interval.end && std::difftime(xact.entry->date, interval.end) >= 0)) return; std::time_t quant = interval.increment(interval.begin); if (std::difftime(xact.entry->date, quant) > 0) { if (last_xact) { start = interval.begin; finish = quant; flush(); } if (! interval.seconds) { struct std::tm * desc = std::localtime(&xact.entry->date); if (interval.years) desc->tm_mon = 0; desc->tm_mday = 1; desc->tm_hour = 0; desc->tm_min = 0; desc->tm_sec = 0; quant = std::mktime(desc); } std::time_t temp; #if DEBUG_LEVEL >= RELEASE int cutoff = 10000; #endif while (std::difftime(xact.entry->date, temp = interval.increment(quant)) > 0) { if (quant == temp) break; quant = temp; #if DEBUG_LEVEL >= RELEASE assert(--cutoff > 0); #endif } interval.begin = quant; } subtotal_transactions::operator()(xact); last_xact = &xact; } void dow_transactions::flush() { for (int i = 0; i < 7; i++) { for (transactions_list::iterator d = days_of_the_week[i].begin(); d != days_of_the_week[i].end(); d++) subtotal_transactions::operator()(**d); subtotal_transactions::flush("%As"); days_of_the_week[i].clear(); } } void clear_transactions_xdata() { transactions_xdata.clear(); for (std::list::iterator i = transactions_xdata_ptrs.begin(); i != transactions_xdata_ptrs.end(); i++) **i = NULL; } template <> bool compare_items::operator()(const account_t * left, const account_t * right) { assert(left); assert(right); 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; } void sum_accounts(account_t& account) { for (accounts_map::iterator i = account.accounts.begin(); i != account.accounts.end(); i++) { sum_accounts(*(*i).second); account_xdata(account).total += account_xdata(*(*i).second).total; account_xdata(account).count += (account_xdata(*(*i).second).count + account_xdata(*(*i).second).subcount); } account_xdata(account).total += account_xdata(account).value; account_xdata(account).count += account_xdata(account).subcount; } void sort_accounts(account_t& account, const value_expr_t * sort_order, accounts_deque& accounts) { for (accounts_map::iterator i = account.accounts.begin(); i != account.accounts.end(); i++) accounts.push_back((*i).second); std::stable_sort(accounts.begin(), accounts.end(), compare_items(sort_order)); } void walk_accounts(account_t& account, item_handler& handler, const value_expr_t * sort_order) { handler(account); if (sort_order) { accounts_deque accounts; sort_accounts(account, sort_order, accounts); for (accounts_deque::const_iterator i = accounts.begin(); i != accounts.end(); i++) walk_accounts(**i, handler, sort_order); } else { for (accounts_map::const_iterator i = account.accounts.begin(); i != account.accounts.end(); i++) walk_accounts(*(*i).second, handler, NULL); } } void walk_accounts(account_t& account, item_handler& handler, const std::string& sort_string) { if (! sort_string.empty()) { std::auto_ptr sort_order; try { sort_order.reset(parse_value_expr(sort_string)); } catch (value_expr_error& err) { throw error(std::string("In sort string '" + sort_string + "': " + err.what())); } walk_accounts(account, handler, sort_order.get()); } else { walk_accounts(account, handler); } } void clear_accounts_xdata() { accounts_xdata.clear(); for (std::list::iterator i = accounts_xdata_ptrs.begin(); i != accounts_xdata_ptrs.end(); i++) **i = NULL; } } // namespace ledger #ifdef USE_BOOST_PYTHON #include using namespace boost::python; using namespace ledger; template struct item_handler_wrap : public item_handler { PyObject* self; item_handler_wrap(PyObject * self_) : self(self_) {} item_handler_wrap(PyObject * self_, const item_handler& handler) : item_handler(const_cast *>(&handler)), self(self_) {} virtual void flush() { call_method(self, "flush"); } void default_flush() { item_handler::flush(); } virtual void operator()(T& item) { call_method(self, "__call__", ptr(&item)); } void default_call(T& item) { item_handler::operator()(item); } }; void (subtotal_transactions::*subtotal_transactions_flush)() = &subtotal_transactions::flush; void py_walk_entries(journal_t& journal, item_handler& handler) { walk_entries(journal.entries, handler); } void py_walk_transactions(entry_t& entry, item_handler& handler) { walk_transactions(entry.transactions, handler); } void py_walk_accounts_1(account_t& account, item_handler& handler) { walk_accounts(account, handler); } void py_walk_accounts_2(account_t& account, item_handler& handler, const value_expr_t * sort_order) { walk_accounts(account, handler, sort_order); } void py_walk_accounts_3(account_t& account, item_handler& handler, const std::string& sort_string) { walk_accounts(account, handler, sort_string); } void export_walk() { typedef item_handler xact_handler_t; scope().attr("TRANSACTION_HANDLED") = TRANSACTION_HANDLED; scope().attr("TRANSACTION_TO_DISPLAY") = TRANSACTION_TO_DISPLAY; scope().attr("TRANSACTION_DISPLAYED") = TRANSACTION_DISPLAYED; scope().attr("TRANSACTION_NO_TOTAL") = TRANSACTION_NO_TOTAL; class_< transaction_xdata_t > ("TransactionXData") .def_readwrite("total", &transaction_xdata_t::total) .def_readwrite("index", &transaction_xdata_t::index) .def_readwrite("dflags", &transaction_xdata_t::dflags) ; def("transaction_has_xdata", transaction_has_xdata); def("transaction_xdata", transaction_xdata, return_internal_reference<1>()); def("clear_transactions_xdata", clear_transactions_xdata); class_< xact_handler_t, item_handler_wrap > ("TransactionHandler") .def(init()) .def("flush", &xact_handler_t::flush, &item_handler_wrap::default_flush) .def("__call__", &xact_handler_t::operator(), &item_handler_wrap::default_call) ; class_< ignore_transactions, bases > ("IgnoreTransactions") .def("flush", &xact_handler_t::flush) .def("__call__", &ignore_transactions::operator()); ; class_< set_account_value, bases > ("SetAccountValue") .def(init()[with_custodian_and_ward<1, 2>()]) .def("flush", &xact_handler_t::flush) .def("__call__", &set_account_value::operator()); ; class_< sort_transactions, bases > ("SortTransactions", init() [with_custodian_and_ward<1, 2>()]) .def(init() [with_custodian_and_ward<1, 2>()]) .def("flush", &sort_transactions::flush) .def("__call__", &sort_transactions::operator()); ; class_< filter_transactions, bases > ("FilterTransactions", init() [with_custodian_and_ward<1, 2>()]) .def(init() [with_custodian_and_ward<1, 2>()]) .def("flush", &xact_handler_t::flush) .def("__call__", &filter_transactions::operator()); ; class_< calc_transactions, bases > ("CalcTransactions", init >() [with_custodian_and_ward<1, 2>()]) .def("flush", &xact_handler_t::flush) .def("__call__", &calc_transactions::operator()); ; class_< collapse_transactions, bases > ("CollapseTransactions", init() [with_custodian_and_ward<1, 2>()]) .def("flush", &collapse_transactions::flush) .def("__call__", &collapse_transactions::operator()); ; class_< changed_value_transactions, bases > ("ChangeValueTransactions", init() [with_custodian_and_ward<1, 2>()]) .def("flush", &changed_value_transactions::flush) .def("__call__", &changed_value_transactions::operator()); ; class_< subtotal_transactions, bases > ("SubtotalTransactions", init() [with_custodian_and_ward<1, 2>()]) .def("flush", subtotal_transactions_flush) .def("__call__", &subtotal_transactions::operator()); ; class_< interval_transactions, bases > ("IntervalTransactions", init() [with_custodian_and_ward<1, 2>()]) .def(init() [with_custodian_and_ward<1, 2>()]) .def("flush", &xact_handler_t::flush) .def("__call__", &interval_transactions::operator()); ; class_< dow_transactions, bases > ("DowTransactions", init() [with_custodian_and_ward<1, 2>()]) .def("flush", &dow_transactions::flush) .def("__call__", &dow_transactions::operator()); ; class_< related_transactions, bases > ("RelatedTransactions", init >() [with_custodian_and_ward<1, 2>()]) .def("flush", &xact_handler_t::flush) .def("__call__", &related_transactions::operator()); ; def("walk_entries", py_walk_entries); def("walk_transactions", py_walk_transactions); typedef item_handler account_handler_t; scope().attr("ACCOUNT_TO_DISPLAY") = ACCOUNT_TO_DISPLAY; scope().attr("ACCOUNT_DISPLAYED") = ACCOUNT_DISPLAYED; class_< account_xdata_t > ("AccountXData") .def_readwrite("value", &account_xdata_t::value) .def_readwrite("total", &account_xdata_t::total) .def_readwrite("count", &account_xdata_t::count) .def_readwrite("subcount", &account_xdata_t::subcount) .def_readwrite("dflags", &account_xdata_t::dflags) ; def("account_has_xdata", account_has_xdata); def("account_xdata", account_xdata, return_internal_reference<1>()); def("clear_accounts_xdata", clear_accounts_xdata); class_< account_handler_t, item_handler_wrap > ("AccountHandler") .def(init()) .def("flush", &account_handler_t::flush, &item_handler_wrap::default_flush) .def("__call__", &account_handler_t::operator(), &item_handler_wrap::default_call) ; def("sum_accounts", sum_accounts); def("walk_accounts", py_walk_accounts_1); def("walk_accounts", py_walk_accounts_2); def("walk_accounts", py_walk_accounts_3); } #endif // USE_BOOST_PYTHON