diff options
-rw-r--r-- | Makefile.am | 13 | ||||
-rw-r--r-- | account.cc | 21 | ||||
-rw-r--r-- | account.h | 48 | ||||
-rw-r--r-- | compare.cc | 85 | ||||
-rw-r--r-- | compare.h | 77 | ||||
-rw-r--r-- | csv.h | 2 | ||||
-rw-r--r-- | derive.cc | 4 | ||||
-rw-r--r-- | emacs.cc | 1 | ||||
-rw-r--r-- | emacs.h | 2 | ||||
-rw-r--r-- | filters.cc (renamed from walk.cc) | 289 | ||||
-rw-r--r-- | filters.h (renamed from walk.h) | 383 | ||||
-rw-r--r-- | format.cc | 13 | ||||
-rw-r--r-- | format.h | 1 | ||||
-rw-r--r-- | gnucash.cc | 19 | ||||
-rw-r--r-- | handler.h | 72 | ||||
-rw-r--r-- | help.cc | 205 | ||||
-rw-r--r-- | help.h | 49 | ||||
-rw-r--r-- | iterators.cc | 200 | ||||
-rw-r--r-- | iterators.h | 226 | ||||
-rw-r--r-- | main.cc | 543 | ||||
-rw-r--r-- | option.cc | 169 | ||||
-rw-r--r-- | output.cc | 282 | ||||
-rw-r--r-- | output.h | 143 | ||||
-rw-r--r-- | reconcile.cc | 1 | ||||
-rw-r--r-- | reconcile.h | 3 | ||||
-rw-r--r-- | report.cc | 295 | ||||
-rw-r--r-- | report.h | 116 | ||||
-rw-r--r-- | session.cc | 47 | ||||
-rw-r--r-- | textual.cc | 2 | ||||
-rw-r--r-- | textual.h | 3 | ||||
-rw-r--r-- | token.cc | 28 | ||||
-rw-r--r-- | xact.h | 8 | ||||
-rw-r--r-- | xml.h | 1 |
33 files changed, 1874 insertions, 1477 deletions
diff --git a/Makefile.am b/Makefile.am index 6fe575be..72ff6f38 100644 --- a/Makefile.am +++ b/Makefile.am @@ -70,6 +70,8 @@ libledger_la_SOURCES = \ entry.cc \ xact.cc \ account.cc \ + iterators.cc \ + compare.cc \ \ textual.cc \ cache.cc \ @@ -80,7 +82,9 @@ libledger_la_SOURCES = \ \ session.cc \ report.cc \ - walk.cc \ + filters.cc \ + output.cc \ + help.cc \ \ derive.cc \ reconcile.cc \ @@ -130,6 +134,8 @@ pkginclude_HEADERS = \ entry.h \ xact.h \ account.h \ + iterators.h \ + compare.h \ \ textual.h \ cache.h \ @@ -142,7 +148,10 @@ pkginclude_HEADERS = \ \ session.h \ report.h \ - walk.h \ + handler.h \ + filters.h \ + output.h \ + help.h \ \ derive.h \ reconcile.h \ @@ -143,4 +143,25 @@ bool account_t::valid() const return true; } +void account_t::calculate_sums() +{ + xdata_t& xd(xdata()); + + foreach (accounts_map::value_type& pair, accounts) { + (*pair.second).calculate_sums(); + + xd.total += (*pair.second).xdata().total; + xd.total_count += ((*pair.second).xdata().total_count + + (*pair.second).xdata().count); + } + + value_t result; +#if 0 + compute_amount(result, details_t(account)); +#endif + if (! result.is_realzero()) + xd.total += result; + xd.total_count += xd.count; +} + } // namespace ledger @@ -98,6 +98,54 @@ class account_t : public scope_t bool valid() const; friend class journal_t; + + struct xdata_t : public supports_flags<> + { +#define ACCOUNT_EXT_TO_DISPLAY 0x01 +#define ACCOUNT_EXT_DISPLAYED 0x02 +#define ACCOUNT_EXT_SORT_CALC 0x04 +#define ACCOUNT_EXT_HAS_NON_VIRTUALS 0x08 +#define ACCOUNT_EXT_HAS_UNB_VIRTUALS 0x10 + + value_t value; + value_t total; + value_t sort_value; + unsigned int count; // xacts counted toward amount + unsigned int total_count; // xacts counted toward total + unsigned int virtuals; + unsigned short dflags; + + xdata_t() + : supports_flags<>(), count(0), total_count(0), + virtuals(0), dflags(0) + { + TRACE_CTOR(xdata_t, ""); + } + + ~xdata_t() throw() { + TRACE_DTOR(xdata_t); + } + }; + + // This variable holds optional "extended data" which is usually produced + // only during reporting, and only for the transaction set being reported. + // It's a memory-saving measure to delay allocation until the last possible + // moment. + mutable optional<xdata_t> xdata_; + + bool has_xdata() const { + return xdata_; + } + void clear_xdata() { + xdata_ = none; + } + xdata_t& xdata() { + if (! xdata_) + xdata_ = xdata_t(); + return *xdata_; + } + + void calculate_sums(); }; std::ostream& operator<<(std::ostream& out, const account_t& account); diff --git a/compare.cc b/compare.cc new file mode 100644 index 00000000..1cbe7082 --- /dev/null +++ b/compare.cc @@ -0,0 +1,85 @@ +/* + * 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 "compare.h" + +namespace ledger { + +template <> +bool compare_items<xact_t>::operator()(xact_t * left, xact_t * right) +{ + assert(left); + assert(right); + + xact_t::xdata_t& lxdata(left->xdata()); + if (! lxdata.has_flags(XACT_EXT_SORT_CALC)) { + lxdata.sort_value = sort_order.calc(*left); + lxdata.sort_value.reduce(); + lxdata.add_flags(XACT_EXT_SORT_CALC); + } + + xact_t::xdata_t& rxdata(right->xdata()); + if (! rxdata.has_flags(XACT_EXT_SORT_CALC)) { + rxdata.sort_value = sort_order.calc(*right); + rxdata.sort_value.reduce(); + rxdata.add_flags(XACT_EXT_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; +} + +template <> +bool compare_items<account_t>::operator()(account_t * left, account_t * right) +{ + assert(left); + assert(right); + + account_t::xdata_t& lxdata(left->xdata()); + if (! lxdata.has_flags(ACCOUNT_EXT_SORT_CALC)) { + lxdata.sort_value = sort_order.calc(*left); + lxdata.add_flags(ACCOUNT_EXT_SORT_CALC); + } + + account_t::xdata_t& rxdata(right->xdata()); + if (! rxdata.has_flags(ACCOUNT_EXT_SORT_CALC)) { + rxdata.sort_value = sort_order.calc(*right); + rxdata.add_flags(ACCOUNT_EXT_SORT_CALC); + } + + return lxdata.sort_value < rxdata.sort_value; +} + +} // namespace ledger diff --git a/compare.h b/compare.h new file mode 100644 index 00000000..d86771ef --- /dev/null +++ b/compare.h @@ -0,0 +1,77 @@ +/* + * 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. + */ + +#ifndef _COMPARE_H +#define _COMPARE_H + +#include "expr.h" +#include "xact.h" +#include "account.h" + +namespace ledger { + +template <typename T> +class compare_items +{ + expr_t sort_order; + + compare_items(); + +public: + compare_items(const compare_items& other) : sort_order(other.sort_order) { + TRACE_CTOR(compare_items, "copy"); + } + compare_items(const expr_t& _sort_order) : sort_order(_sort_order) { + TRACE_CTOR(compare_items, "const value_expr&"); + } + ~compare_items() throw() { + TRACE_DTOR(compare_items); + } + bool operator()(T * left, T * right); +}; + +template <typename T> +bool compare_items<T>::operator()(T * left, T * right) +{ + assert(left); + assert(right); + return sort_order.calc(*left) < sort_order.calc(*right); +} + +template <> +bool compare_items<xact_t>::operator()(xact_t * left, xact_t * right); +template <> +bool compare_items<account_t>::operator()(account_t * left, + account_t * right); + +} // namespace ledger + +#endif // _COMPARE_H @@ -32,7 +32,7 @@ #ifndef _CSV_H #define _CSV_H -#include "journal.h" +#include "handler.h" #include "format.h" namespace ledger { @@ -31,7 +31,7 @@ #include "derive.h" #include "session.h" -#include "walk.h" +#include "iterators.h" namespace ledger { @@ -96,7 +96,7 @@ entry_t * derive_new_entry(report_t& report, report.sum_all_accounts(); - value_t total = account_xdata(*acct).total; + value_t total = acct->xdata().total; if (total.is_type(value_t::AMOUNT)) xact->amount.set_commodity(total.as_amount().commodity()); } @@ -30,6 +30,7 @@ */ #include "emacs.h" +#include "account.h" namespace ledger { @@ -32,7 +32,7 @@ #ifndef _EMACS_H #define _EMACS_H -#include "journal.h" +#include "handler.h" #include "format.h" namespace ledger { @@ -1,89 +1,51 @@ -#include "walk.h" +/* + * 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" -#include <algorithm> - namespace ledger { -template <> -bool compare_items<xact_t>::operator()(xact_t * left, xact_t * right) -{ - assert(left); - assert(right); - - xact_t::xdata_t& lxdata(left->xdata()); - if (! lxdata.has_flags(XACT_EXT_SORT_CALC)) { - lxdata.sort_value = sort_order.calc(*left); - lxdata.sort_value.reduce(); - lxdata.add_flags(XACT_EXT_SORT_CALC); - } - - xact_t::xdata_t& rxdata(right->xdata()); - if (! rxdata.has_flags(XACT_EXT_SORT_CALC)) { - rxdata.sort_value = sort_order.calc(*right); - rxdata.sort_value.reduce(); - rxdata.add_flags(XACT_EXT_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; -} - -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) +pass_down_xacts::pass_down_xacts(xact_handler_ptr handler, + xacts_iterator& iter) + : item_handler<xact_t>(handler) { - entries.reset(session); - entry_t * entry = entries(); - if (entry != NULL) - xacts.reset(*entry); -} + TRACE_CTOR(pass_down_xacts, "xact_handler_ptr, xacts_iterator"); -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; + for (xact_t * xact = iter(); xact; xact = iter()) + item_handler<xact_t>::operator()(*xact); } void truncate_entries::flush() @@ -136,9 +98,8 @@ void truncate_entries::flush() void set_account_value::operator()(xact_t& xact) { account_t * acct = xact.reported_account(); - assert(acct); - account_xdata_t& xdata = account_xdata(*acct); + account_t::xdata_t& xdata(acct->xdata()); xact.add_to_value(xdata.value); xdata.count++; @@ -233,10 +194,10 @@ void handle_value(const value_t& value, // 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)) { + if (account && account->has_xdata()) + if (! account->xdata().has_flags(ACCOUNT_EXT_HAS_NON_VIRTUALS)) { xact.add_flags(XACT_VIRTUAL); - if (! (account_xdata_(*account).dflags & ACCOUNT_HAS_UNB_VIRTUALS)) + if (! account->xdata().has_flags(ACCOUNT_EXT_HAS_UNB_VIRTUALS)) xact.add_flags(XACT_BALANCE); } @@ -459,9 +420,9 @@ void subtotal_xacts::operator()(xact_t& xact) // that contain only virtual xacts. if (! xact.has_flags(XACT_VIRTUAL)) - account_xdata(*xact.reported_account()).dflags |= ACCOUNT_HAS_NON_VIRTUALS; + xact.reported_account()->xdata().add_flags(ACCOUNT_EXT_HAS_NON_VIRTUALS); else if (! xact.has_flags(XACT_BALANCE)) - account_xdata(*xact.reported_account()).dflags |= ACCOUNT_HAS_UNB_VIRTUALS; + xact.reported_account()->xdata().add_flags(ACCOUNT_EXT_HAS_UNB_VIRTUALS); } void interval_xacts::report_subtotal(const date_t& date) @@ -781,158 +742,14 @@ void forecast_xacts::flush() item_handler<xact_t>::flush(); } -template <> -bool compare_items<account_t>::operator()(account_t * left, account_t * right) -{ - assert(left); - assert(right); - - account_xdata_t& lxdata(account_xdata(*left)); - if (! (lxdata.dflags & ACCOUNT_SORT_CALC)) { - lxdata.sort_value = sort_order.calc(*left); - lxdata.dflags |= ACCOUNT_SORT_CALC; - } - - account_xdata_t& rxdata(account_xdata(*right)); - if (! (rxdata.dflags & ACCOUNT_SORT_CALC)) { - rxdata.sort_value = sort_order.calc(*right); - rxdata.dflags |= ACCOUNT_SORT_CALC; - } - - return lxdata.sort_value < rxdata.sort_value; -} - -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()() +pass_down_accounts::pass_down_accounts(acct_handler_ptr handler, + accounts_iterator& iter) + : item_handler<account_t>(handler) { - if (journals_i == journals_end) - return NULL; - return &(*journals_i++); + 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 @@ -1,136 +1,47 @@ -#ifndef _WALK_H -#define _WALK_H - -#include "journal.h" +/* + * 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. + */ + +#ifndef _FILTERS_H +#define _FILTERS_H + +#include "handler.h" +#include "predicate.h" #include "entry.h" -#include "account.h" namespace ledger { -template <typename T> -struct item_handler : public noncopyable -{ - shared_ptr<item_handler> handler; - -public: - item_handler() { - TRACE_CTOR(item_handler, ""); - } - item_handler(shared_ptr<item_handler> _handler) : handler(_handler) { - TRACE_CTOR(item_handler, "shared_ptr<item_handler>"); - } - virtual ~item_handler() { - TRACE_DTOR(item_handler); - } - - virtual void flush() { - if (handler.get()) - handler->flush(); - } - virtual void operator()(T& item) { - if (handler.get()) - (*handler.get())(item); - } -}; - -typedef shared_ptr<item_handler<xact_t> > xact_handler_ptr; - -////////////////////////////////////////////////////////////////////// - -class entries_iterator : public noncopyable -{ - ptr_list<journal_t>::iterator journals_i; - ptr_list<journal_t>::iterator journals_end; - - bool journals_uninitialized; - - entries_list::iterator entries_i; - entries_list::iterator entries_end; - - bool entries_uninitialized; - -public: - entries_iterator() - : journals_uninitialized(true), entries_uninitialized(true) { - TRACE_CTOR(entries_iterator, ""); - } - entries_iterator(session_t& session) - : journals_uninitialized(true), entries_uninitialized(true) { - TRACE_CTOR(entries_iterator, "session_t&"); - reset(session); - } - ~entries_iterator() throw() { - TRACE_DTOR(entries_iterator); - } - - void reset(session_t& session); - - entry_t * operator()(); -}; - -class xacts_iterator : public noncopyable -{ -public: - virtual xact_t * operator()() = 0; -}; - -class entry_xacts_iterator : public xacts_iterator -{ - xacts_list::iterator xacts_i; - xacts_list::iterator xacts_end; - - bool xacts_uninitialized; - -public: - entry_xacts_iterator() : xacts_uninitialized(true) { - TRACE_CTOR(entry_xacts_iterator, ""); - } - entry_xacts_iterator(entry_t& entry) - : xacts_uninitialized(true) { - TRACE_CTOR(entry_xacts_iterator, "entry_t&"); - reset(entry); - } - virtual ~entry_xacts_iterator() throw() { - TRACE_DTOR(entry_xacts_iterator); - } - - void reset(entry_t& entry) { - xacts_i = entry.xacts.begin(); - xacts_end = entry.xacts.end(); - - xacts_uninitialized = false; - } - - virtual xact_t * operator()() { - if (xacts_i == xacts_end || xacts_uninitialized) - return NULL; - return *xacts_i++; - } -}; - -class session_xacts_iterator : public xacts_iterator -{ - entries_iterator entries; - entry_xacts_iterator xacts; - -public: - session_xacts_iterator() { - TRACE_CTOR(session_xacts_iterator, ""); - } - session_xacts_iterator(session_t& session) { - TRACE_CTOR(session_xacts_iterator, "session_t&"); - reset(session); - } - virtual ~session_xacts_iterator() throw() { - TRACE_DTOR(session_xacts_iterator); - } - - void reset(session_t& session); - - virtual xact_t * operator()(); -}; - ////////////////////////////////////////////////////////////////////// +// +// Transaction filters +// class ignore_xacts : public item_handler<xact_t> { @@ -146,19 +57,14 @@ public: } }; +class xacts_iterator; + class pass_down_xacts : public item_handler<xact_t> { pass_down_xacts(); public: - 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); - } + pass_down_xacts(xact_handler_ptr handler, xacts_iterator& iter); virtual ~pass_down_xacts() { TRACE_DTOR(pass_down_xacts); @@ -265,7 +171,7 @@ public: class sort_entries : public item_handler<xact_t> { sort_xacts sorter; - entry_t * last_entry; + entry_t * last_entry; sort_entries(); @@ -432,7 +338,7 @@ public: class related_xacts : public item_handler<xact_t> { xacts_list xacts; - bool also_matching; + bool also_matching; related_xacts(); @@ -626,8 +532,8 @@ class by_payee_xacts : public item_handler<xact_t> class set_comm_as_payee : public item_handler<xact_t> { - std::list<entry_t> entry_temps; - std::list<xact_t> xact_temps; + std::list<entry_t> entry_temps; + std::list<xact_t> xact_temps; set_comm_as_payee(); @@ -646,8 +552,8 @@ public: class set_code_as_payee : public item_handler<xact_t> { - std::list<entry_t> entry_temps; - std::list<xact_t> xact_temps; + std::list<entry_t> entry_temps; + std::list<xact_t> xact_temps; set_code_as_payee(); @@ -694,9 +600,9 @@ protected: typedef std::pair<interval_t, xact_t *> pending_xacts_pair; typedef std::list<pending_xacts_pair> pending_xacts_list; - pending_xacts_list pending_xacts; - std::list<entry_t> entry_temps; - std::list<xact_t> xact_temps; + pending_xacts_list pending_xacts; + std::list<entry_t> entry_temps; + std::list<xact_t> xact_temps; public: generate_xacts(xact_handler_ptr handler) @@ -714,12 +620,12 @@ public: virtual void add_xact(const interval_t& period, xact_t& xact); }; +class budget_xacts : public generate_xacts +{ #define BUDGET_NO_BUDGET 0x00 #define BUDGET_BUDGETED 0x01 #define BUDGET_UNBUDGETED 0x02 -class budget_xacts : public generate_xacts -{ unsigned short flags; budget_xacts(); @@ -764,204 +670,33 @@ class forecast_xacts : public generate_xacts virtual void flush(); }; - ////////////////////////////////////////////////////////////////////// // -// Account walking functions +// Account filters // -#define ACCOUNT_TO_DISPLAY 0x0001 -#define ACCOUNT_DISPLAYED 0x0002 -#define ACCOUNT_SORT_CALC 0x0004 -#define ACCOUNT_HAS_NON_VIRTUALS 0x0008 -#define ACCOUNT_HAS_UNB_VIRTUALS 0x0010 - -struct account_xdata_t : public noncopyable -{ - value_t value; - value_t total; - value_t sort_value; - unsigned int count; // xacts counted toward amount - unsigned int total_count; // xacts counted toward total - unsigned int virtuals; - unsigned short dflags; - - account_xdata_t() : count(0), total_count(0), virtuals(0), dflags(0) { - TRACE_CTOR(account_xdata_t, ""); - } - ~account_xdata_t() throw() { - TRACE_DTOR(account_xdata_t); - } -}; - -inline bool account_has_xdata(const account_t& account) { - return account.data != NULL; -} - -inline account_xdata_t& account_xdata_(const account_t& account) { - return *static_cast<account_xdata_t *>(account.data); -} - -account_xdata_t& account_xdata(const account_t& account); - -void sum_accounts(account_t& account); - -////////////////////////////////////////////////////////////////////// - -class accounts_iterator : public noncopyable -{ - std::list<accounts_map::const_iterator> accounts_i; - std::list<accounts_map::const_iterator> accounts_end; - -public: - accounts_iterator() { - TRACE_CTOR(accounts_iterator, ""); - } - accounts_iterator(account_t& account) { - TRACE_CTOR(accounts_iterator, "account_t&"); - push_back(account); - } - virtual ~accounts_iterator() throw() { - TRACE_DTOR(accounts_iterator); - } - - void push_back(account_t& account) { - accounts_i.push_back(account.accounts.begin()); - accounts_end.push_back(account.accounts.end()); - } - - virtual account_t * operator()(); -}; - -class sorted_accounts_iterator : public noncopyable -{ - expr_t sort_cmp; - - typedef std::deque<account_t *> accounts_deque_t; - - std::list<accounts_deque_t> accounts_list; - std::list<accounts_deque_t::const_iterator> sorted_accounts_i; - std::list<accounts_deque_t::const_iterator> sorted_accounts_end; - -public: - sorted_accounts_iterator(const string& sort_order) { - TRACE_CTOR(sorted_accounts_iterator, "const string&"); - sort_cmp = expr_t(sort_order); - } - sorted_accounts_iterator(account_t& account, const string& sort_order) { - TRACE_CTOR(sorted_accounts_iterator, "account_t&, const string&"); - sort_cmp = expr_t(sort_order); - push_back(account); - } - virtual ~sorted_accounts_iterator() throw() { - TRACE_DTOR(sorted_accounts_iterator); - } - - void sort_accounts(account_t& account, accounts_deque_t& deque); - - void push_back(account_t& account) { - accounts_list.push_back(accounts_deque_t()); - sort_accounts(account, accounts_list.back()); - - sorted_accounts_i.push_back(accounts_list.back().begin()); - sorted_accounts_end.push_back(accounts_list.back().end()); - } - - virtual account_t * operator()(); -}; - -////////////////////////////////////////////////////////////////////// - -typedef shared_ptr<item_handler<account_t> > acct_handler_ptr; - class clear_account_xdata : public item_handler<account_t> { public: virtual void operator()(account_t& acct) { - if (acct.data) { - checked_delete(static_cast<account_xdata_t *>(acct.data)); - acct.data = NULL; - } + acct.clear_xdata(); } }; -template <typename Iterator> +class accounts_iterator; + class pass_down_accounts : public item_handler<account_t> { pass_down_accounts(); public: - pass_down_accounts(acct_handler_ptr handler, 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); - } + pass_down_accounts(acct_handler_ptr handler, accounts_iterator& iter); virtual ~pass_down_accounts() { TRACE_DTOR(pass_down_accounts); } }; -////////////////////////////////////////////////////////////////////// - -class journals_iterator : public noncopyable -{ - ptr_list<journal_t>::iterator journals_i; - ptr_list<journal_t>::iterator journals_end; - -public: - journals_iterator() { - TRACE_CTOR(journals_iterator, ""); - } - journals_iterator(session_t& session) { - TRACE_CTOR(journals_iterator, "session_t&"); - reset(session); - } - virtual ~journals_iterator() throw() { - TRACE_DTOR(journals_iterator); - } - - void reset(session_t& session); - - virtual journal_t * operator()(); -}; - -template <typename T> -class compare_items -{ - expr_t sort_order; - - compare_items(); - -public: - compare_items(const compare_items& other) : sort_order(other.sort_order) { - TRACE_CTOR(compare_items, "copy"); - } - compare_items(const expr_t& _sort_order) : sort_order(_sort_order) { - TRACE_CTOR(compare_items, "const value_expr&"); - } - ~compare_items() throw() { - TRACE_DTOR(compare_items); - } - bool operator()(T * left, T * right); -}; - -template <typename T> -bool compare_items<T>::operator()(T * left, T * right) -{ - assert(left); - assert(right); - return sort_order.calc(*left) < sort_order.calc(*right); -} - -template <> -bool compare_items<xact_t>::operator()(xact_t * left, xact_t * right); -template <> -bool compare_items<account_t>::operator()(account_t * left, - account_t * right); - } // namespace ledger -#endif // _WALK_H +#endif // _FILTERS_H @@ -30,10 +30,7 @@ */ #include "format.h" -#include "error.h" -#include "util.h" - -#include <cstdlib> +#include "account.h" namespace ledger { @@ -70,15 +67,15 @@ void format_t::element_t::dump(std::ostream& out) const } namespace { - string partial_account_name(const account_t& account) + string partial_account_name(account_t& account) { string name; - for (const account_t * acct = &account; + for (account_t * acct = &account; acct && acct->parent; acct = acct->parent) { - if (account_has_xdata(*acct) && - account_xdata_(*acct).dflags & ACCOUNT_DISPLAYED) + if (acct->has_xdata() && + acct->xdata().has_flags(ACCOUNT_EXT_DISPLAYED)) break; if (name.empty()) @@ -34,7 +34,6 @@ #include "journal.h" #include "expr.h" -#include "walk.h" namespace ledger { @@ -30,24 +30,7 @@ */ #include "gnucash.h" -#include "journal.h" -#include "format.h" -#include "error.h" -#include "acconf.h" - -#include <iostream> -#include <sstream> -#include <cstring> - -extern "C" { -#if defined(HAVE_EXPAT) -#include <expat.h> // expat XML parser -#elif defined(HAVE_XMLPARSE) -#include <xmlparse.h> // expat XML parser -#else -#error "No XML parser library defined." -#endif -} +#include "account.h" namespace ledger { diff --git a/handler.h b/handler.h new file mode 100644 index 00000000..6ebd6a5d --- /dev/null +++ b/handler.h @@ -0,0 +1,72 @@ +/* + * 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. + */ + +#ifndef _HANDLER_H +#define _HANDLER_H + +#include "utils.h" +#include "xact.h" +#include "account.h" + +namespace ledger { + +template <typename T> +struct item_handler : public noncopyable +{ + shared_ptr<item_handler> handler; + +public: + item_handler() { + TRACE_CTOR(item_handler, ""); + } + item_handler(shared_ptr<item_handler> _handler) : handler(_handler) { + TRACE_CTOR(item_handler, "shared_ptr<item_handler>"); + } + virtual ~item_handler() { + TRACE_DTOR(item_handler); + } + + virtual void flush() { + if (handler.get()) + handler->flush(); + } + virtual void operator()(T& item) { + if (handler.get()) + (*handler.get())(item); + } +}; + +typedef shared_ptr<item_handler<xact_t> > xact_handler_ptr; +typedef shared_ptr<item_handler<account_t> > acct_handler_ptr; + +} // namespace ledger + +#endif // _HANDLER_H diff --git a/help.cc b/help.cc new file mode 100644 index 00000000..84d5a178 --- /dev/null +++ b/help.cc @@ -0,0 +1,205 @@ +/* + * 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 "help.h" + +namespace ledger { + +void help(std::ostream& out) +{ + out << "usage: ledger [options] COMMAND [ACCT REGEX]... [-- [PAYEE REGEX]...]\n\n\ +Use -H to see all the help text on one page, or:\n\ + --help-calc calculation options\n\ + --help-disp display options\n\ + --help-comm commodity options\n\n\ +Basic options:\n\ + -h, --help display this help text\n\ + -v, --version show version information\n\ + -f, --file FILE read ledger data from FILE\n\ + -o, --output FILE write output to FILE\n\ + -i, --init-file FILE initialize ledger using FILE (default: ~/.ledgerrc)\n\ + --cache FILE use FILE as a binary cache when --file is not used\n\ + --no-cache don't use a cache, even if it would be appropriate\n\ + -a, --account NAME use NAME for the default account (useful with QIF)\n\n\ +Commands:\n\ + balance [REGEXP]... show balance totals for matching accounts\n\ + register [REGEXP]... show register of matching transactions\n\ + print [REGEXP]... print all matching entries\n\ + xml [REGEXP]... print matching entries in XML format\n\ + equity [REGEXP]... output equity entries for matching accounts\n\ + prices [REGEXP]... display price history for matching commodities\n\ + entry DATE PAYEE AMT output a derived entry, based on the arguments\n"; +} + +void calc_help(std::ostream& out) +{ + out << "Options to control how a report is calculated:\n\ + -c, --current show only current and past entries (not future)\n\ + -b, --begin DATE set report begin date\n\ + -e, --end DATE set report end date\n\ + -p, --period STR report using the given period\n\ + --period-sort EXPR sort each report period's entries by EXPR\n\ + -C, --cleared consider only cleared transactions\n\ + -U, --uncleared consider only uncleared transactions\n\ + -R, --real consider only real (non-virtual) transactions\n\ + -L, --actual consider only actual (non-automated) transactions\n\ + -r, --related calculate report using related transactions\n\ + --budget generate budget entries based on periodic entries\n\ + --add-budget show all transactions plus the budget\n\ + --unbudgeted show only unbudgeted transactions\n\ + --forecast EXPR generate forecast entries while EXPR is true\n\ + -l, --limit EXPR calculate only transactions matching EXPR\n\ + -t, --amount EXPR use EXPR to calculate the displayed amount\n\ + -T, --total EXPR use EXPR to calculate the displayed total\n"; +} + +void disp_help(std::ostream& out) +{ + out << "Output to control how report results are displayed:\n\ + -n, --collapse register: collapse entries; balance: no grand total\n\ + -s, --subtotal balance: show sub-accounts; other: show subtotals\n\ + -P, --by-payee show summarized totals by payee\n\ + -x, --comm-as-payee set commodity name as the payee, for reporting\n\ + -E, --empty balance: show accounts with zero balance\n\ + -W, --weekly show weekly sub-totals\n\ + -M, --monthly show monthly sub-totals\n\ + -Y, --yearly show yearly sub-totals\n\ + --dow show a days-of-the-week report\n\ + -S, --sort EXPR sort report according to the value expression EXPR\n\ + -w, --wide for the default register report, use 132 columns\n\ + --head COUNT show only the first COUNT entries (negative inverts)\n\ + --tail COUNT show only the last COUNT entries (negative inverts)\n\ + --pager PAGER send all output through the given PAGER program\n\ + -A, --average report average transaction amount\n\ + -D, --deviation report deviation from the average\n\ + -%, --percentage report balance totals as a percentile of the parent\n\ + --totals in the \"xml\" report, include running total\n\ + -j, --amount-data print only raw amount data (useful for scripting)\n\ + -J, --total-data print only raw total data\n\ + -d, --display EXPR display only transactions matching EXPR\n\ + -y, --date-format STR use STR as the date format (default: %Y/%m/%d)\n\ + -F, --format STR use STR as the format; for each report type, use:\n\ + --balance-format --register-format --print-format\n\ + --plot-amount-format --plot-total-format --equity-format\n\ + --prices-format --wide-register-format\n"; +} + +void comm_help(std::ostream& out) +{ + out << "Options to control how commodity values are determined:\n\ + --price-db FILE sets the price database to FILE (def: ~/.pricedb)\n\ + -Z, --price-exp MINS download quotes only if newer than MINS (def: 1440)\n\ + -Q, --download download price information when needed\n\ + -O, --quantity report commodity totals (this is the default)\n\ + -B, --basis report cost basis of commodities\n\ + -V, --market report last known market value\n\ + -g, --performance report gain/loss for each displayed transaction\n\ + -G, --gain report net gain/loss\n"; +} + +void full_help(std::ostream& out) +{ + out << "usage: ledger [options] COMMAND [ACCT REGEX]... [-- [PAYEE REGEX]...]\n\n\ +Basic options:\n\ + -H, --full-help display this help text\n\ + -h, --help display summarized help text\n\ + -v, --version show version information\n\ + -f, --file FILE read ledger data from FILE\n\ + -o, --output FILE write output to FILE\n\ + -i, --init-file FILE initialize ledger using FILE (default: ~/.ledgerrc)\n\ + --cache FILE use FILE as a binary cache when --file is not used\n\ + --no-cache don't use a cache, even if it would be appropriate\n\ + -a, --account NAME use NAME for the default account (useful with QIF)\n\n\ +Report filtering:\n\ + -c, --current show only current and past entries (not future)\n\ + -b, --begin DATE set report begin date\n\ + -e, --end DATE set report end date\n\ + -p, --period STR report using the given period\n\ + --period-sort EXPR sort each report period's entries by EXPR\n\ + -C, --cleared consider only cleared transactions\n\ + -U, --uncleared consider only uncleared transactions\n\ + -R, --real consider only real (non-virtual) transactions\n\ + -L, --actual consider only actual (non-automated) transactions\n\ + -r, --related calculate report using related transactions\n\ + --budget generate budget entries based on periodic entries\n\ + --add-budget show all transactions plus the budget\n\ + --unbudgeted show only unbudgeted transactions\n\ + --forecast EXPR generate forecast entries while EXPR is true\n\ + -l, --limit EXPR calculate only transactions matching EXPR\n\ + -t, --amount EXPR use EXPR to calculate the displayed amount\n\ + -T, --total EXPR use EXPR to calculate the displayed total\n\n\ +Output customization:\n\ + -n, --collapse register: collapse entries; balance: no grand total\n\ + -s, --subtotal balance: show sub-accounts; other: show subtotals\n\ + -P, --by-payee show summarized totals by payee\n\ + -x, --comm-as-payee set commodity name as the payee, for reporting\n\ + -E, --empty balance: show accounts with zero balance\n\ + -W, --weekly show weekly sub-totals\n\ + -M, --monthly show monthly sub-totals\n\ + -Y, --yearly show yearly sub-totals\n\ + --dow show a days-of-the-week report\n\ + -S, --sort EXPR sort report according to the value expression EXPR\n\ + -w, --wide for the default register report, use 132 columns\n\ + --head COUNT show only the first COUNT entries (negative inverts)\n\ + --tail COUNT show only the last COUNT entries (negative inverts)\n\ + --pager PAGER send all output through the given PAGER program\n\ + -A, --average report average transaction amount\n\ + -D, --deviation report deviation from the average\n\ + -%, --percentage report balance totals as a percentile of the parent\n\ + --totals in the \"xml\" report, include running total\n\ + -j, --amount-data print only raw amount data (useful for scripting)\n\ + -J, --total-data print only raw total data\n\ + -d, --display EXPR display only transactions matching EXPR\n\ + -y, --date-format STR use STR as the date format (default: %Y/%m/%d)\n\ + -F, --format STR use STR as the format; for each report type, use:\n\ + --balance-format --register-format --print-format\n\ + --plot-amount-format --plot-total-format --equity-format\n\ + --prices-format --wide-register-format\n\n\ +Commodity reporting:\n\ + --price-db FILE sets the price database to FILE (def: ~/.pricedb)\n\ + -L, --price-exp MINS download quotes only if newer than MINS (def: 1440)\n\ + -Q, --download download price information when needed\n\ + -O, --quantity report commodity totals (this is the default)\n\ + -B, --basis report cost basis of commodities\n\ + -V, --market report last known market value\n\ + -g, --performance report gain/loss for each displayed transaction\n\ + -G, --gain report net gain/loss\n\n\ +Commands:\n\ + balance [REGEXP]... show balance totals for matching accounts\n\ + register [REGEXP]... show register of matching transactions\n\ + print [REGEXP]... print all matching entries\n\ + xml [REGEXP]... print matching entries in XML format\n\ + equity [REGEXP]... output equity entries for matching accounts\n\ + prices [REGEXP]... display price history for matching commodities\n\ + entry DATE PAYEE AMT output a derived entry, based on the arguments\n"; +} + +} // namespace ledger @@ -0,0 +1,49 @@ +/* + * 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. + */ + +#ifndef _HELP_H +#define _HELP_H + +#include "utils.h" + +namespace ledger { + +void help(std::ostream& out); + +void calc_help(std::ostream& out); +void disp_help(std::ostream& out); +void comm_help(std::ostream& out); + +void full_help(std::ostream& out); + +} // namespace ledger + +#endif // _HELP_H diff --git a/iterators.cc b/iterators.cc new file mode 100644 index 00000000..34beba7e --- /dev/null +++ b/iterators.cc @@ -0,0 +1,200 @@ +/* + * 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 "iterators.h" +#include "session.h" +#include "compare.h" + +namespace ledger { + +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; +} + +account_t * basic_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().drop_flags(ACCOUNT_EXT_SORT_CALC); + return account; +} + +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++); +} + +#if 0 +// jww (2008-08-03): This needs to be changed into a commodities->xacts +// iterator. + +// jww (2008-08-03): We then could also use a payees->xacts iterator + +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); +} +#endif + +} // namespace ledger diff --git a/iterators.h b/iterators.h new file mode 100644 index 00000000..c52a6827 --- /dev/null +++ b/iterators.h @@ -0,0 +1,226 @@ +/* + * 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. + */ + +#ifndef _ITERATORS_H +#define _ITERATORS_H + +#include "journal.h" +#include "entry.h" +#include "account.h" + +namespace ledger { + +class xacts_iterator : public noncopyable +{ +public: + virtual xact_t * operator()() = 0; +}; + +class entry_xacts_iterator : public xacts_iterator +{ + xacts_list::iterator xacts_i; + xacts_list::iterator xacts_end; + + bool xacts_uninitialized; + +public: + entry_xacts_iterator() : xacts_uninitialized(true) { + TRACE_CTOR(entry_xacts_iterator, ""); + } + entry_xacts_iterator(entry_t& entry) + : xacts_uninitialized(true) { + TRACE_CTOR(entry_xacts_iterator, "entry_t&"); + reset(entry); + } + virtual ~entry_xacts_iterator() throw() { + TRACE_DTOR(entry_xacts_iterator); + } + + void reset(entry_t& entry) { + xacts_i = entry.xacts.begin(); + xacts_end = entry.xacts.end(); + + xacts_uninitialized = false; + } + + virtual xact_t * operator()() { + if (xacts_i == xacts_end || xacts_uninitialized) + return NULL; + return *xacts_i++; + } +}; + +class entries_iterator : public noncopyable +{ + ptr_list<journal_t>::iterator journals_i; + ptr_list<journal_t>::iterator journals_end; + + bool journals_uninitialized; + + entries_list::iterator entries_i; + entries_list::iterator entries_end; + + bool entries_uninitialized; + +public: + entries_iterator() + : journals_uninitialized(true), entries_uninitialized(true) { + TRACE_CTOR(entries_iterator, ""); + } + entries_iterator(session_t& session) + : journals_uninitialized(true), entries_uninitialized(true) { + TRACE_CTOR(entries_iterator, "session_t&"); + reset(session); + } + ~entries_iterator() throw() { + TRACE_DTOR(entries_iterator); + } + + void reset(session_t& session); + + entry_t * operator()(); +}; + +class session_xacts_iterator : public xacts_iterator +{ + entries_iterator entries; + entry_xacts_iterator xacts; + +public: + session_xacts_iterator() { + TRACE_CTOR(session_xacts_iterator, ""); + } + session_xacts_iterator(session_t& session) { + TRACE_CTOR(session_xacts_iterator, "session_t&"); + reset(session); + } + virtual ~session_xacts_iterator() throw() { + TRACE_DTOR(session_xacts_iterator); + } + + void reset(session_t& session); + + virtual xact_t * operator()(); +}; + +class accounts_iterator : public noncopyable +{ +public: + virtual account_t * operator()() = 0; +}; + +class basic_accounts_iterator : public accounts_iterator +{ + std::list<accounts_map::const_iterator> accounts_i; + std::list<accounts_map::const_iterator> accounts_end; + +public: + basic_accounts_iterator() { + TRACE_CTOR(basic_accounts_iterator, ""); + } + basic_accounts_iterator(account_t& account) { + TRACE_CTOR(basic_accounts_iterator, "account_t&"); + push_back(account); + } + virtual ~basic_accounts_iterator() throw() { + TRACE_DTOR(basic_accounts_iterator); + } + + void push_back(account_t& account) { + accounts_i.push_back(account.accounts.begin()); + accounts_end.push_back(account.accounts.end()); + } + + virtual account_t * operator()(); +}; + +class sorted_accounts_iterator : public accounts_iterator +{ + expr_t sort_cmp; + + typedef std::deque<account_t *> accounts_deque_t; + + std::list<accounts_deque_t> accounts_list; + std::list<accounts_deque_t::const_iterator> sorted_accounts_i; + std::list<accounts_deque_t::const_iterator> sorted_accounts_end; + +public: + sorted_accounts_iterator(const string& sort_order) { + TRACE_CTOR(sorted_accounts_iterator, "const string&"); + sort_cmp = expr_t(sort_order); + } + sorted_accounts_iterator(account_t& account, const string& sort_order) { + TRACE_CTOR(sorted_accounts_iterator, "account_t&, const string&"); + sort_cmp = expr_t(sort_order); + push_back(account); + } + virtual ~sorted_accounts_iterator() throw() { + TRACE_DTOR(sorted_accounts_iterator); + } + + void sort_accounts(account_t& account, accounts_deque_t& deque); + + void push_back(account_t& account) { + accounts_list.push_back(accounts_deque_t()); + sort_accounts(account, accounts_list.back()); + + sorted_accounts_i.push_back(accounts_list.back().begin()); + sorted_accounts_end.push_back(accounts_list.back().end()); + } + + virtual account_t * operator()(); +}; + +class journals_iterator : public noncopyable +{ + ptr_list<journal_t>::iterator journals_i; + ptr_list<journal_t>::iterator journals_end; + +public: + journals_iterator() { + TRACE_CTOR(journals_iterator, ""); + } + journals_iterator(session_t& session) { + TRACE_CTOR(journals_iterator, "session_t&"); + reset(session); + } + virtual ~journals_iterator() throw() { + TRACE_DTOR(journals_iterator); + } + + void reset(session_t& session); + + virtual journal_t * operator()(); +}; + +} // namespace ledger + +#endif // _ITERATORS_H @@ -29,15 +29,21 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "utils.h" +#include "session.h" +#include "report.h" #include "option.h" +#include "output.h" +#include "help.h" + +#include "textual.h" +#include "qif.h" #if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) +#include "xml.h" #include "gnucash.h" #endif -#include "qif.h" +#ifdef HAVE_LIBOFX #include "ofx.h" - -#include <ledger.h> +#endif #ifdef HAVE_UNIX_PIPES #include <sys/types.h> @@ -46,352 +52,351 @@ #endif namespace ledger { - value_t register_command(call_scope_t& args) + template <class Formatter = format_xacts> + class xacts_report { - ptr_t<report_t> report(args, 0); - ptr_t<std::ostream> ostream(args, 1); + string format_name; - report->xacts_report - (xact_handler_ptr(new format_xacts - (*ostream, report->session.register_format))); - return true; - } -} + public: + xacts_report(const string& _format_name) + : format_name(_format_name) {} -static int read_and_report(ledger::report_t& report, int argc, char * argv[], - char * envp[]) -{ - using namespace ledger; + value_t operator()(call_scope_t& args) + { + ptr_t<std::ostream> ostream(args, 0); + var_t<string> format(args, format_name); + + find_scope<report_t>(args).xacts_report + (xact_handler_ptr(new Formatter(*ostream, *format))); + return true; + } + }; - session_t& session(report.session); + template <class Formatter = format_accounts> + class accounts_report + { + string format_name; - // Handle the command-line arguments + public: + accounts_report(const string& _format_name) + : format_name(_format_name) {} - strings_list args; - process_arguments(argc - 1, argv + 1, false, report, args); + value_t operator()(call_scope_t& args) + { + ptr_t<std::ostream> ostream(args, 0); + var_t<string> format(args, format_name); - if (args.empty()) { -#if 0 - help(std::cerr); -#endif - return 1; - } - strings_list::iterator arg = args.begin(); + find_scope<report_t>(args).accounts_report + (acct_handler_ptr(new Formatter(*ostream, *format))); + return true; + } + }; - if (! session.cache_file) - session.use_cache = false; - else - session.use_cache = ! session.data_file.empty() && session.price_db; + int read_and_report(ledger::report_t& report, + int argc, char * argv[], char * envp[]) + { + using namespace ledger; - DEBUG("ledger.session.cache", "1. use_cache = " << session.use_cache); + session_t& session(report.session); - // Process the environment settings + // Handle the command-line arguments + + strings_list args; + process_arguments(argc - 1, argv + 1, false, report, args); + + if (args.empty()) { + ledger::help(std::cout); + return 1; + } + strings_list::iterator arg = args.begin(); + + if (! session.cache_file) + session.use_cache = false; + else + session.use_cache = ! session.data_file.empty() && session.price_db; - TRACE_START(environment, 1, "Processed environment variables"); - process_environment(const_cast<const char **>(envp), "LEDGER_", report); - TRACE_FINISH(environment, 1); + DEBUG("ledger.session.cache", "1. use_cache = " << session.use_cache); - optional<path> home; - if (const char * home_var = std::getenv("HOME")) - home = home_var; + // Process the environment settings - if (! session.init_file) - session.init_file = home ? *home / ".ledgerrc" : "./.ledgerrc"; - if (! session.price_db) - session.price_db = home ? *home / ".pricedb" : "./.pricedb"; + TRACE_START(environment, 1, "Processed environment variables"); + process_environment(const_cast<const char **>(envp), "LEDGER_", report); + TRACE_FINISH(environment, 1); - if (! session.cache_file) - session.cache_file = home ? *home / ".ledger-cache" : "./.ledger-cache"; + optional<path> home; + if (const char * home_var = std::getenv("HOME")) + home = home_var; - if (session.data_file == *session.cache_file) - session.use_cache = false; + if (! session.init_file) + session.init_file = home ? *home / ".ledgerrc" : "./.ledgerrc"; + if (! session.price_db) + session.price_db = home ? *home / ".pricedb" : "./.pricedb"; - DEBUG("ledger.session.cache", "2. use_cache = " << session.use_cache); + if (! session.cache_file) + session.cache_file = home ? *home / ".ledger-cache" : "./.ledger-cache"; - INFO("Initialization file is " << session.init_file->string()); - INFO("Price database is " << session.price_db->string()); - INFO("Binary cache is " << session.cache_file->string()); - INFO("Journal file is " << session.data_file.string()); + if (session.data_file == *session.cache_file) + session.use_cache = false; - if (! session.use_cache) - INFO("Binary cache mechanism will not be used"); + DEBUG("ledger.session.cache", "2. use_cache = " << session.use_cache); - // Configure the output stream + INFO("Initialization file is " << session.init_file->string()); + INFO("Price database is " << session.price_db->string()); + INFO("Binary cache is " << session.cache_file->string()); + INFO("Journal file is " << session.data_file.string()); + + if (! session.use_cache) + INFO("Binary cache mechanism will not be used"); + + // Configure the output stream #ifdef HAVE_UNIX_PIPES - int status, pfd[2]; // Pipe file descriptors + int status, pfd[2]; // Pipe file descriptors #endif - std::ostream * out = &std::cout; + std::ostream * out = &std::cout; - if (report.output_file) { - out = new ofstream(*report.output_file); - } -#ifdef HAVE_UNIX_PIPES - else if (report.pager) { - status = pipe(pfd); - if (status == -1) - throw_(std::logic_error, "Failed to create pipe"); - - status = fork(); - if (status < 0) { - throw_(std::logic_error, "Failed to fork child process"); + if (report.output_file) { + out = new ofstream(*report.output_file); } - else if (status == 0) { // child - // Duplicate pipe's reading end into stdin - status = dup2(pfd[0], STDIN_FILENO); +#ifdef HAVE_UNIX_PIPES + else if (report.pager) { + status = pipe(pfd); if (status == -1) - perror("dup2"); + throw_(std::logic_error, "Failed to create pipe"); - // Close unuseful file descriptors: the pipe's writing and - // reading ends (the latter is not needed anymore, after the - // duplication). - close(pfd[1]); - close(pfd[0]); - - // Find command name: its the substring starting right of the - // rightmost '/' character in the pager pathname. See manpage - // for strrchr. - execlp(report.pager->native_file_string().c_str(), - basename(*report.pager).c_str(), NULL); - perror("execl"); - exit(1); - } - else { // parent - close(pfd[0]); - out = new boost::fdostream(pfd[1]); + status = fork(); + if (status < 0) { + throw_(std::logic_error, "Failed to fork child process"); + } + else if (status == 0) { // child + // Duplicate pipe's reading end into stdin + status = dup2(pfd[0], STDIN_FILENO); + if (status == -1) + perror("dup2"); + + // Close unuseful file descriptors: the pipe's writing and + // reading ends (the latter is not needed anymore, after the + // duplication). + close(pfd[1]); + close(pfd[0]); + + // Find command name: its the substring starting right of the + // rightmost '/' character in the pager pathname. See manpage + // for strrchr. + execlp(report.pager->native_file_string().c_str(), + basename(*report.pager).c_str(), NULL); + perror("execl"); + exit(1); + } + else { // parent + close(pfd[0]); + out = new boost::fdostream(pfd[1]); + } } - } #endif - // Read the command word and create a command object based on it + // Read the command word and see if it's any of the debugging commands + // that Ledger supports. - string verb = *arg++; + string verb = *arg++; - if (verb == "parse") { - expr_t expr(*arg); + if (verb == "parse") { + expr_t expr(*arg); - *out << "Value expression as input: " << *arg << std::endl; + *out << "Value expression as input: " << *arg << std::endl; - *out << "Value expression as parsed: "; - expr.print(*out, report); - *out << std::endl; - - *out << std::endl; - *out << "--- Parsed tree ---" << std::endl; - expr.dump(*out); - - *out << std::endl; - *out << "--- Calculated value ---" << std::endl; - expr.calc(report).print(*out); - *out << std::endl; - - return 0; - } - else if (verb == "compile") { - expr_t expr(*arg); - - *out << "Value expression as input: " << *arg << std::endl; - *out << "Value expression as parsed: "; - expr.print(*out, report); - *out << std::endl; + *out << "Value expression as parsed: "; + expr.print(*out, report); + *out << std::endl; - *out << std::endl; - *out << "--- Parsed tree ---" << std::endl; - expr.dump(*out); + *out << std::endl; + *out << "--- Parsed tree ---" << std::endl; + expr.dump(*out); - expr.compile(report); + *out << std::endl; + *out << "--- Calculated value ---" << std::endl; + expr.calc(report).print(*out); + *out << std::endl; - *out << std::endl; - *out << "--- Compiled tree ---" << std::endl; - expr.dump(*out); + return 0; + } + else if (verb == "compile") { + expr_t expr(*arg); - *out << std::endl; - *out << "--- Calculated value ---" << std::endl; - expr.calc(report).print(*out); - *out << std::endl; + *out << "Value expression as input: " << *arg << std::endl; + *out << "Value expression as parsed: "; + expr.print(*out, report); + *out << std::endl; - return 0; - } - else if (verb == "eval") { - expr_t expr(*arg); - *out << expr.calc(report).strip_annotations() << std::endl; - return 0; - } - else if (verb == "format") { - format_t fmt(*arg); - fmt.dump(*out); - return 0; - } - else if (verb == "period") { - interval_t interval(*arg); - - if (! is_valid(interval.begin)) { - *out << "Time period has no beginning." << std::endl; - } else { - *out << "begin: " << format_date(interval.begin) << std::endl; - *out << " end: " << format_date(interval.end) << std::endl; *out << std::endl; + *out << "--- Parsed tree ---" << std::endl; + expr.dump(*out); - date_t date = interval.first(); + expr.compile(report); - for (int i = 0; i < 20; i++) { - *out << std::right; - out->width(2); + *out << std::endl; + *out << "--- Compiled tree ---" << std::endl; + expr.dump(*out); - *out << i << ": " << format_date(date) << std::endl; + *out << std::endl; + *out << "--- Calculated value ---" << std::endl; + expr.calc(report).print(*out); + *out << std::endl; - date = interval.increment(date); - if (is_valid(interval.end) && date >= interval.end) - break; - } + return 0; } - return 0; - } + else if (verb == "eval") { + expr_t expr(*arg); + *out << expr.calc(report).strip_annotations() << std::endl; + return 0; + } + else if (verb == "format") { + format_t fmt(*arg); + fmt.dump(*out); + return 0; + } + else if (verb == "period") { + interval_t interval(*arg); - // Parse the initialization file, which can only be textual; then - // parse the journal data. + if (! is_valid(interval.begin)) { + *out << "Time period has no beginning." << std::endl; + } else { + *out << "begin: " << format_date(interval.begin) << std::endl; + *out << " end: " << format_date(interval.end) << std::endl; + *out << std::endl; - session.read_init(); + date_t date = interval.first(); - INFO_START(journal, "Read journal file"); + for (int i = 0; i < 20; i++) { + *out << std::right; + out->width(2); - journal_t& journal(*session.create_journal()); + *out << i << ": " << format_date(date) << std::endl; - std::size_t count = session.read_data(journal, report.account); - if (count == 0) - throw_(parse_error, "Failed to locate any journal entries; " - "did you specify a valid file with -f?"); + date = interval.increment(date); + if (is_valid(interval.end) && date >= interval.end) + break; + } + } + return 0; + } - INFO_FINISH(journal); + // Parse the initialization file, which can only be textual; then + // parse the journal data. - INFO("Found " << count << " entries"); + session.read_init(); - TRACE_FINISH(entry_text, 1); - TRACE_FINISH(entry_date, 1); - TRACE_FINISH(entry_details, 1); - TRACE_FINISH(entry_xacts, 1); - TRACE_FINISH(entries, 1); - TRACE_FINISH(parsing_total, 1); + INFO_START(journal, "Read journal file"); - // Are we handling the expr commands? Do so now. + journal_t& journal(*session.create_journal()); - if (verb == "expr") { - expr_t expr(*arg); + std::size_t count = session.read_data(journal, report.account); + if (count == 0) + throw_(parse_error, "Failed to locate any journal entries; " + "did you specify a valid file with -f?"); - IF_INFO() { - *out << "Value expression tree:" << std::endl; - expr.dump(*out); - *out << std::endl; - *out << "Value expression parsed was:" << std::endl; - expr.print(*out, report); - *out << std::endl << std::endl; - *out << "Result of calculation: "; - } + INFO_FINISH(journal); - *out << expr.calc(report).strip_annotations() << std::endl; + INFO("Found " << count << " entries"); - return 0; - } + TRACE_FINISH(entry_text, 1); + TRACE_FINISH(entry_date, 1); + TRACE_FINISH(entry_details, 1); + TRACE_FINISH(entry_xacts, 1); + TRACE_FINISH(entries, 1); + TRACE_FINISH(parsing_total, 1); - // Read the command word and create a command object based on it + // Create a command object based on the command verb that was seen + // above. - function_t command; + function_t command; - if (verb == "register" || verb == "reg" || verb == "r") - command = register_command; + if (verb == "register" || verb == "reg" || verb == "r") + command = xacts_report<>("register_format"); + else if (verb == "print" || verb == "p") + command = xacts_report<>("print_format"); + else if (verb == "balance" || verb == "bal" || verb == "b") + command = accounts_report<>("balance_format"); + else if (verb == "equity") + command = accounts_report<format_equity>("print_format"); #if 0 - else if (verb == "balance" || verb == "bal" || verb == "b") - command = balance_command(); - else if (verb == "print" || verb == "p") - command = print_command(); - else if (verb == "equity") - command = equity_command(); - else if (verb == "entry") - command = entry_command(); - else if (verb == "dump") - command = dump_command(); - else if (verb == "output") - command = output_command(); - else if (verb == "prices") - command = prices_command(); - else if (verb == "pricesdb") - command = pricesdb_command(); - else if (verb == "csv") - command = csv_command(); - else if (verb == "emacs" || verb == "lisp") - command = emacs_command(); - else if (verb == "xml") - command = xml_command(); + else if (verb == "entry") + command = entry_command(); + else if (verb == "dump") + command = dump_command(); + else if (verb == "output") + command = output_command(); + else if (verb == "prices") + command = prices_command(); + else if (verb == "pricesdb") + command = pricesdb_command(); + else if (verb == "csv") + command = csv_command(); + else if (verb == "emacs" || verb == "lisp") + command = emacs_command(); + else if (verb == "xml") + command = xml_command(); #endif - else if (verb == "expr") - ; - else if (verb == "xpath") - ; - else { - char buf[128]; - std::strcpy(buf, "command_"); - std::strcat(buf, verb.c_str()); - - if (expr_t::ptr_op_t def = report.lookup(buf)) - command = def->as_function(); - - if (! command) - throw_(std::logic_error, string("Unrecognized command '") + verb + "'"); - } + else { + char buf[128]; + std::strcpy(buf, "cmd_"); + std::strcat(buf, verb.c_str()); - // Create an argument scope containing the report command's - // arguments, and then invoke the command. + if (expr_t::ptr_op_t def = report.lookup(buf)) + command = def->as_function(); - call_scope_t command_args(report); + if (! command) + throw_(std::logic_error, string("Unrecognized command '") + verb + "'"); + } - command_args.push_back(value_t(&report)); - command_args.push_back(value_t(out)); + // Create an argument scope containing the report command's + // arguments, and then invoke the command. - for (strings_list::iterator i = arg; i != args.end(); i++) - command_args.push_back(string_value(*i)); + call_scope_t command_args(report); - INFO_START(command, "Did user command '" << verb << "'"); + command_args.push_back(value_t(out)); - command(command_args); + for (strings_list::iterator i = arg; i != args.end(); i++) + command_args.push_back(string_value(*i)); - INFO_FINISH(command); + INFO_START(command, "Did user command '" << verb << "'"); - // Clean up memory, if it matters + command(command_args); - if (DO_VERIFY() && report.output_file) - checked_delete(out); + INFO_FINISH(command); #if 0 - // Write out the binary cache, if need be + // Write out the binary cache, if need be - if (session.use_cache && session.cache_dirty && session.cache_file) { - TRACE_START(binary_cache, 1, "Wrote binary journal file"); + if (session.use_cache && session.cache_dirty && session.cache_file) { + TRACE_START(binary_cache, 1, "Wrote binary journal file"); - ofstream stream(*session.cache_file); - journal.write(stream); + ofstream stream(*session.cache_file); + journal.write(stream); - TRACE_FINISH(binary_cache, 1); - } + TRACE_FINISH(binary_cache, 1); + } #endif - // If the user specified a pager, wait for it to exit now + // If the user specified a pager, wait for it to exit now #ifdef HAVE_UNIX_PIPES - if (! report.output_file && report.pager) { - checked_delete(out); - close(pfd[1]); - - // Wait for child to finish - wait(&status); - if (status & 0xffff != 0) - throw_(std::logic_error, "Something went wrong in the pager"); - } + if (! report.output_file && report.pager) { + checked_delete(out); + close(pfd[1]); + + // Wait for child to finish + wait(&status); + if (status & 0xffff != 0) + throw_(std::logic_error, "Something went wrong in the pager"); + } #endif - else if (DO_VERIFY() && report.output_file) { - checked_delete(out); - } + else if (DO_VERIFY() && report.output_file) { + checked_delete(out); + } - return 0; + return 0; + } } int main(int argc, char * argv[], char * envp[]) @@ -469,7 +474,7 @@ int main(int argc, char * argv[], char * envp[]) if (DO_VERIFY()) ledger::set_session_context(); else - session.release(); // don't free anything! + session.release(); // don't free anything! just let it leak } catch (const std::exception& err) { std::cout.flush(); @@ -212,175 +212,6 @@ void process_arguments(int, char ** argv, const bool anywhere, } // namespace ledger #if 0 -void option_full_help(std::ostream& out) -{ - out << "usage: ledger [options] COMMAND [ACCT REGEX]... [-- [PAYEE REGEX]...]\n\n\ -Basic options:\n\ - -H, --full-help display this help text\n\ - -h, --help display summarized help text\n\ - -v, --version show version information\n\ - -f, --file FILE read ledger data from FILE\n\ - -o, --output FILE write output to FILE\n\ - -i, --init-file FILE initialize ledger using FILE (default: ~/.ledgerrc)\n\ - --cache FILE use FILE as a binary cache when --file is not used\n\ - --no-cache don't use a cache, even if it would be appropriate\n\ - -a, --account NAME use NAME for the default account (useful with QIF)\n\n\ -Report filtering:\n\ - -c, --current show only current and past entries (not future)\n\ - -b, --begin DATE set report begin date\n\ - -e, --end DATE set report end date\n\ - -p, --period STR report using the given period\n\ - --period-sort EXPR sort each report period's entries by EXPR\n\ - -C, --cleared consider only cleared transactions\n\ - -U, --uncleared consider only uncleared transactions\n\ - -R, --real consider only real (non-virtual) transactions\n\ - -L, --actual consider only actual (non-automated) transactions\n\ - -r, --related calculate report using related transactions\n\ - --budget generate budget entries based on periodic entries\n\ - --add-budget show all transactions plus the budget\n\ - --unbudgeted show only unbudgeted transactions\n\ - --forecast EXPR generate forecast entries while EXPR is true\n\ - -l, --limit EXPR calculate only transactions matching EXPR\n\ - -t, --amount EXPR use EXPR to calculate the displayed amount\n\ - -T, --total EXPR use EXPR to calculate the displayed total\n\n\ -Output customization:\n\ - -n, --collapse register: collapse entries; balance: no grand total\n\ - -s, --subtotal balance: show sub-accounts; other: show subtotals\n\ - -P, --by-payee show summarized totals by payee\n\ - -x, --comm-as-payee set commodity name as the payee, for reporting\n\ - -E, --empty balance: show accounts with zero balance\n\ - -W, --weekly show weekly sub-totals\n\ - -M, --monthly show monthly sub-totals\n\ - -Y, --yearly show yearly sub-totals\n\ - --dow show a days-of-the-week report\n\ - -S, --sort EXPR sort report according to the value expression EXPR\n\ - -w, --wide for the default register report, use 132 columns\n\ - --head COUNT show only the first COUNT entries (negative inverts)\n\ - --tail COUNT show only the last COUNT entries (negative inverts)\n\ - --pager PAGER send all output through the given PAGER program\n\ - -A, --average report average transaction amount\n\ - -D, --deviation report deviation from the average\n\ - -%, --percentage report balance totals as a percentile of the parent\n\ - --totals in the \"xml\" report, include running total\n\ - -j, --amount-data print only raw amount data (useful for scripting)\n\ - -J, --total-data print only raw total data\n\ - -d, --display EXPR display only transactions matching EXPR\n\ - -y, --date-format STR use STR as the date format (default: %Y/%m/%d)\n\ - -F, --format STR use STR as the format; for each report type, use:\n\ - --balance-format --register-format --print-format\n\ - --plot-amount-format --plot-total-format --equity-format\n\ - --prices-format --wide-register-format\n\n\ -Commodity reporting:\n\ - --price-db FILE sets the price database to FILE (def: ~/.pricedb)\n\ - -L, --price-exp MINS download quotes only if newer than MINS (def: 1440)\n\ - -Q, --download download price information when needed\n\ - -O, --quantity report commodity totals (this is the default)\n\ - -B, --basis report cost basis of commodities\n\ - -V, --market report last known market value\n\ - -g, --performance report gain/loss for each displayed transaction\n\ - -G, --gain report net gain/loss\n\n\ -Commands:\n\ - balance [REGEXP]... show balance totals for matching accounts\n\ - register [REGEXP]... show register of matching transactions\n\ - print [REGEXP]... print all matching entries\n\ - xml [REGEXP]... print matching entries in XML format\n\ - equity [REGEXP]... output equity entries for matching accounts\n\ - prices [REGEXP]... display price history for matching commodities\n\ - entry DATE PAYEE AMT output a derived entry, based on the arguments\n"; -} - -void option_help(std::ostream& out) -{ - out << "usage: ledger [options] COMMAND [ACCT REGEX]... [-- [PAYEE REGEX]...]\n\n\ -Use -H to see all the help text on one page, or:\n\ - --help-calc calculation options\n\ - --help-disp display options\n\ - --help-comm commodity options\n\n\ -Basic options:\n\ - -h, --help display this help text\n\ - -v, --version show version information\n\ - -f, --file FILE read ledger data from FILE\n\ - -o, --output FILE write output to FILE\n\ - -i, --init-file FILE initialize ledger using FILE (default: ~/.ledgerrc)\n\ - --cache FILE use FILE as a binary cache when --file is not used\n\ - --no-cache don't use a cache, even if it would be appropriate\n\ - -a, --account NAME use NAME for the default account (useful with QIF)\n\n\ -Commands:\n\ - balance [REGEXP]... show balance totals for matching accounts\n\ - register [REGEXP]... show register of matching transactions\n\ - print [REGEXP]... print all matching entries\n\ - xml [REGEXP]... print matching entries in XML format\n\ - equity [REGEXP]... output equity entries for matching accounts\n\ - prices [REGEXP]... display price history for matching commodities\n\ - entry DATE PAYEE AMT output a derived entry, based on the arguments\n"; -} - -void option_calc_help(std::ostream& out) -{ - out << "Options to control how a report is calculated:\n\ - -c, --current show only current and past entries (not future)\n\ - -b, --begin DATE set report begin date\n\ - -e, --end DATE set report end date\n\ - -p, --period STR report using the given period\n\ - --period-sort EXPR sort each report period's entries by EXPR\n\ - -C, --cleared consider only cleared transactions\n\ - -U, --uncleared consider only uncleared transactions\n\ - -R, --real consider only real (non-virtual) transactions\n\ - -L, --actual consider only actual (non-automated) transactions\n\ - -r, --related calculate report using related transactions\n\ - --budget generate budget entries based on periodic entries\n\ - --add-budget show all transactions plus the budget\n\ - --unbudgeted show only unbudgeted transactions\n\ - --forecast EXPR generate forecast entries while EXPR is true\n\ - -l, --limit EXPR calculate only transactions matching EXPR\n\ - -t, --amount EXPR use EXPR to calculate the displayed amount\n\ - -T, --total EXPR use EXPR to calculate the displayed total\n"; -} - -void option_disp_help(std::ostream& out) -{ - out << "Output to control how report results are displayed:\n\ - -n, --collapse register: collapse entries; balance: no grand total\n\ - -s, --subtotal balance: show sub-accounts; other: show subtotals\n\ - -P, --by-payee show summarized totals by payee\n\ - -x, --comm-as-payee set commodity name as the payee, for reporting\n\ - -E, --empty balance: show accounts with zero balance\n\ - -W, --weekly show weekly sub-totals\n\ - -M, --monthly show monthly sub-totals\n\ - -Y, --yearly show yearly sub-totals\n\ - --dow show a days-of-the-week report\n\ - -S, --sort EXPR sort report according to the value expression EXPR\n\ - -w, --wide for the default register report, use 132 columns\n\ - --head COUNT show only the first COUNT entries (negative inverts)\n\ - --tail COUNT show only the last COUNT entries (negative inverts)\n\ - --pager PAGER send all output through the given PAGER program\n\ - -A, --average report average transaction amount\n\ - -D, --deviation report deviation from the average\n\ - -%, --percentage report balance totals as a percentile of the parent\n\ - --totals in the \"xml\" report, include running total\n\ - -j, --amount-data print only raw amount data (useful for scripting)\n\ - -J, --total-data print only raw total data\n\ - -d, --display EXPR display only transactions matching EXPR\n\ - -y, --date-format STR use STR as the date format (default: %Y/%m/%d)\n\ - -F, --format STR use STR as the format; for each report type, use:\n\ - --balance-format --register-format --print-format\n\ - --plot-amount-format --plot-total-format --equity-format\n\ - --prices-format --wide-register-format\n"; -} - -void option_comm_help(std::ostream& out) -{ - out << "Options to control how commodity values are determined:\n\ - --price-db FILE sets the price database to FILE (def: ~/.pricedb)\n\ - -Z, --price-exp MINS download quotes only if newer than MINS (def: 1440)\n\ - -Q, --download download price information when needed\n\ - -O, --quantity report commodity totals (this is the default)\n\ - -B, --basis report cost basis of commodities\n\ - -V, --market report last known market value\n\ - -g, --performance report gain/loss for each displayed transaction\n\ - -G, --gain report net gain/loss\n"; -} - ////////////////////////////////////////////////////////////////////// // // Basic options diff --git a/output.cc b/output.cc new file mode 100644 index 00000000..a470c3ec --- /dev/null +++ b/output.cc @@ -0,0 +1,282 @@ +/* + * 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 "output.h" + +namespace ledger { + +format_xacts::format_xacts(std::ostream& _output_stream, + const string& format) + : output_stream(_output_stream), last_entry(NULL), last_xact(NULL) +{ + TRACE_CTOR(format_xacts, "std::ostream&, const string&"); + + const char * f = format.c_str(); + if (const char * p = std::strstr(f, "%/")) { + first_line_format.parse(string(f, 0, p - f)); + next_lines_format.parse(string(p + 2)); + } else { + first_line_format.parse(format); + next_lines_format.parse(format); + } +} + +void format_xacts::operator()(xact_t& xact) +{ + if (! xact.has_xdata() || + ! xact.xdata().has_flags(XACT_EXT_DISPLAYED)) { + if (last_entry != xact.entry) { + first_line_format.format(output_stream, xact); + last_entry = xact.entry; + } + else if (last_xact && last_xact->date() != xact.date()) { + first_line_format.format(output_stream, xact); + } + else { + next_lines_format.format(output_stream, xact); + } + + xact.xdata().add_flags(XACT_EXT_DISPLAYED); + last_xact = &xact; + } +} + +void format_entries::format_last_entry() +{ + bool first = true; + + foreach (xact_t * xact, last_entry->xacts) { + if (xact->has_xdata() && + xact->xdata().has_flags(XACT_EXT_TO_DISPLAY)) { + if (first) { + first_line_format.format(output_stream, *xact); + first = false; + } else { + next_lines_format.format(output_stream, *xact); + } + xact->xdata().add_flags(XACT_EXT_DISPLAYED); + } + } +} + +void format_entries::operator()(xact_t& xact) +{ + xact.xdata().add_flags(XACT_EXT_TO_DISPLAY); + + if (last_entry && xact.entry != last_entry) + format_last_entry(); + + last_entry = xact.entry; +} + +void print_entry(std::ostream& out, const entry_base_t& entry_base, + const string& prefix) +{ + string print_format; + + if (dynamic_cast<const entry_t *>(&entry_base)) { + print_format = (prefix + "%D %X%C%P\n" + + prefix + " %-34A %12o\n%/" + + prefix + " %-34A %12o\n"); + } + else if (const auto_entry_t * entry = + dynamic_cast<const auto_entry_t *>(&entry_base)) { + out << "= " << entry->predicate.predicate.text() << '\n'; + print_format = prefix + " %-34A %12o\n"; + } + else if (const period_entry_t * entry = + dynamic_cast<const period_entry_t *>(&entry_base)) { + out << "~ " << entry->period_string << '\n'; + print_format = prefix + " %-34A %12o\n"; + } + else { + assert(false); + } + +#if 0 + format_entries formatter(out, print_format); + walk_xacts(const_cast<xacts_list&>(entry_base.xacts), formatter); + formatter.flush(); + + clear_xact_xdata cleaner; + walk_xacts(const_cast<xacts_list&>(entry_base.xacts), cleaner); +#endif +} + +void format_accounts::operator()(account_t& account) +{ + if (display_account(account)) { + if (! account.parent) { + account.xdata().add_flags(ACCOUNT_EXT_TO_DISPLAY); + } else { + format.format(output_stream, account); + account.xdata().add_flags(ACCOUNT_EXT_DISPLAYED); + } + } +} + +bool format_accounts::disp_subaccounts_p(account_t& account, + account_t *& to_show) +{ + bool display = false; + unsigned int counted = 0; + bool matches = disp_pred(account); + bool computed = false; + value_t acct_total; + value_t result; + + to_show = NULL; + + foreach (accounts_map::value_type pair, account.accounts) { + if (! disp_pred(*pair.second)) + continue; + +#if 0 + compute_total(result, *pair.second); +#endif + if (! computed) { +#if 0 + compute_total(acct_total, account); +#endif + computed = true; + } + + if ((result != acct_total) || counted > 0) { + display = matches; + break; + } + to_show = pair.second; + counted++; + } + + return display; +} + +bool format_accounts::display_account(account_t& account) +{ + // Never display an account that has already been displayed. + if (account.has_xdata() && + account.xdata().has_flags(ACCOUNT_EXT_DISPLAYED)) + return false; + + // At this point, one of two possibilities exists: the account is a + // leaf which matches the predicate restrictions; or it is a parent + // and two or more children must be subtotaled; or it is a parent + // and its child has been hidden by the predicate. So first, + // determine if it is a parent that must be displayed regardless of + // the predicate. + + account_t * account_to_show = NULL; + if (disp_subaccounts_p(account, account_to_show)) + return true; + + return ! account_to_show && disp_pred(account); +} + +format_equity::format_equity(std::ostream& _output_stream, + const string& _format, + const string& display_predicate) + : format_accounts(_output_stream, "", display_predicate) +{ + const char * f = _format.c_str(); + + if (const char * p = std::strstr(f, "%/")) { + first_line_format.parse(string(f, 0, p - f)); + next_lines_format.parse(string(p + 2)); + } else { + first_line_format.parse(_format); + next_lines_format.parse(_format); + } + + entry_t header_entry; + header_entry.payee = "Opening Balances"; + header_entry._date = current_date; + first_line_format.format(output_stream, header_entry); +} + +void format_equity::flush() +{ + account_t summary(NULL, "Equity:Opening Balances"); + + account_t::xdata_t& xdata(summary.xdata()); + + xdata.value = total.negate(); + + if (total.type() >= value_t::BALANCE) { + const balance_t * bal; + if (total.is_type(value_t::BALANCE)) + bal = &(total.as_balance()); + else if (total.is_type(value_t::BALANCE_PAIR)) + bal = &(total.as_balance_pair().quantity()); + else + assert(false); + + foreach (balance_t::amounts_map::value_type pair, bal->amounts) { + xdata.value = pair.second; + xdata.value.negate(); + next_lines_format.format(output_stream, summary); + } + } else { + next_lines_format.format(output_stream, summary); + } + output_stream.flush(); +} + +void format_equity::operator()(account_t& account) +{ + if (display_account(account)) { + if (account.has_xdata()) { + value_t val = account.xdata().value; + + if (val.type() >= value_t::BALANCE) { + const balance_t * bal; + if (val.is_type(value_t::BALANCE)) + bal = &(val.as_balance()); + else if (val.is_type(value_t::BALANCE_PAIR)) + bal = &(val.as_balance_pair().quantity()); + else + assert(false); + + foreach (balance_t::amounts_map::value_type pair, bal->amounts) { + account.xdata().value = pair.second; + next_lines_format.format(output_stream, account); + } + account.xdata().value = val; + } else { + next_lines_format.format(output_stream, account); + } + total += val; + } + account.xdata().add_flags(ACCOUNT_EXT_DISPLAYED); + } +} + +} // namespace ledger diff --git a/output.h b/output.h new file mode 100644 index 00000000..4954942f --- /dev/null +++ b/output.h @@ -0,0 +1,143 @@ +/* + * 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. + */ + +#ifndef _OUTPUT_H +#define _OUTPUT_H + +#include "session.h" +#include "handler.h" +#include "format.h" + +namespace ledger { + +class format_xacts : public item_handler<xact_t> +{ +protected: + std::ostream& output_stream; + format_t first_line_format; + format_t next_lines_format; + entry_t * last_entry; + xact_t * last_xact; + +public: + format_xacts(std::ostream& _output_stream, + const string& format); + virtual ~format_xacts() { + TRACE_DTOR(format_xacts); + } + + virtual void flush() { + output_stream.flush(); + } + virtual void operator()(xact_t& xact); +}; + +class format_entries : public format_xacts +{ + public: + format_entries(std::ostream& output_stream, const string& format) + : format_xacts(output_stream, format) { + TRACE_CTOR(format_entries, "std::ostream&, const string&"); + } + virtual ~format_entries() { + TRACE_DTOR(format_entries); + } + + virtual void format_last_entry(); + + virtual void flush() { + if (last_entry) { + format_last_entry(); + last_entry = NULL; + } + format_xacts::flush(); + } + virtual void operator()(xact_t& xact); + +private: + void print_entry(std::ostream& out, + const entry_base_t& entry, + const string& prefix = ""); +}; + +class format_accounts : public item_handler<account_t> +{ +protected: + std::ostream& output_stream; + + item_predicate<account_t> disp_pred; + + bool disp_subaccounts_p(account_t& account, account_t *& to_show); + bool display_account(account_t& account); + +public: + format_t format; + + format_accounts(std::ostream& _output_stream, + const string& _format, + const string& display_predicate = "") + : output_stream(_output_stream), disp_pred(display_predicate), + format(_format) { + TRACE_CTOR(format_accounts, "std::ostream&, const string&, const string&"); + } + virtual ~format_accounts() { + TRACE_DTOR(format_accounts); + } + + virtual void flush() { + output_stream.flush(); + } + + virtual void operator()(account_t& account); +}; + +class format_equity : public format_accounts +{ + format_t first_line_format; + format_t next_lines_format; + + mutable value_t total; + + public: + format_equity(std::ostream& _output_stream, + const string& _format, + const string& display_predicate = ""); + virtual ~format_equity() { + TRACE_DTOR(format_equity); + } + + virtual void flush(); + virtual void operator()(account_t& account); +}; + +} // namespace ledger + +#endif // _OUTPUT_H diff --git a/reconcile.cc b/reconcile.cc index 273517f1..aca1732e 100644 --- a/reconcile.cc +++ b/reconcile.cc @@ -30,7 +30,6 @@ */ #include "reconcile.h" -#include "walk.h" namespace ledger { diff --git a/reconcile.h b/reconcile.h index 97ae6c7b..2e133087 100644 --- a/reconcile.h +++ b/reconcile.h @@ -33,7 +33,8 @@ #define _RECONCILE_H #include "value.h" -#include "walk.h" +#include "iterators.h" +#include "filters.h" namespace ledger { @@ -372,7 +372,7 @@ report_t::chain_xact_handlers(xact_handler_ptr base_handler, forecast_xacts * forecast_handler = new forecast_xacts(handler, forecast_limit); forecast_handler->add_period_entries(journal->period_entries); - handler.reset(forecast_handler; + handler.reset(forecast_handler); // See above, under budget_xacts. if (! predicate.empty()) @@ -415,7 +415,7 @@ void report_t::sum_all_accounts() (chain_xact_handlers(xact_handler_ptr(new set_account_value), false), walker); // no flush() needed with set_account_value - sum_accounts(*session.master); + session.master->calculate_sums(); } void report_t::accounts_report(acct_handler_ptr handler, @@ -425,11 +425,11 @@ void report_t::accounts_report(acct_handler_ptr handler, sum_all_accounts(); if (sort_string.empty()) { - accounts_iterator walker(*session.master); - pass_down_accounts<accounts_iterator>(handler, walker); + basic_accounts_iterator walker(*session.master); + pass_down_accounts(handler, walker); } else { sorted_accounts_iterator walker(*session.master, sort_string); - pass_down_accounts<sorted_accounts_iterator>(handler, walker); + pass_down_accounts(handler, walker); } handler->flush(); @@ -463,43 +463,6 @@ void report_t::entry_report(const entry_t& entry, const string& format) { } -#if 0 -value_t report_t::abbrev(call_scope_t& args) -{ - if (args.size() < 2) - throw_(usage_error, "usage: abbrev(STRING, WIDTH [, STYLE, ABBREV_LEN])"); - - const var_t<string> str(args, 0); - const var_t<long> wid(args, 1); - const var_t<long> style(args, 2); - const var_t<long> abbrev_len(args, 3); - - return value_t(abbreviate(*str, *wid, - (style ? - static_cast<format_t::elision_style_t>(*style) : - session.elision_style), - true, - abbrev_len ? *abbrev_len : session.abbrev_len), - true); -} - -value_t report_t::ftime(call_scope_t& args) -{ - if (args.size() < 1) - throw_(std::logic_error, "usage: ftime(DATE [, DATE_FORMAT])"); - - date_t date = args[0].as_date(); - - string date_format; - if (args.size() == 2) - date_format = args[1].as_string(); - else - date_format = moment_t::output_format; - - return string_value(date.as_string(date_format)); -} -#endif - expr_t::ptr_op_t report_t::lookup(const string& name) { const char * p = name.c_str(); @@ -552,252 +515,4 @@ expr_t::ptr_op_t report_t::lookup(const string& name) return session.lookup(name); } -// jww (2008-08-01): Find a home for this code - -format_xacts::format_xacts(std::ostream& _output_stream, - const string& format) - : output_stream(_output_stream), last_entry(NULL), last_xact(NULL) -{ - TRACE_CTOR(format_xacts, "std::ostream&, const string&"); - - const char * f = format.c_str(); - if (const char * p = std::strstr(f, "%/")) { - first_line_format.parse(string(f, 0, p - f)); - next_lines_format.parse(string(p + 2)); - } else { - first_line_format.parse(format); - next_lines_format.parse(format); - } -} - -void format_xacts::operator()(xact_t& xact) -{ - if (! xact.has_xdata() || - ! xact.xdata().has_flags(XACT_EXT_DISPLAYED)) { - if (last_entry != xact.entry) { - first_line_format.format(output_stream, xact); - last_entry = xact.entry; - } - else if (last_xact && last_xact->date() != xact.date()) { - first_line_format.format(output_stream, xact); - } - else { - next_lines_format.format(output_stream, xact); - } - - xact.xdata().add_flags(XACT_EXT_DISPLAYED); - last_xact = &xact; - } -} - -void format_entries::format_last_entry() -{ - bool first = true; - - foreach (xact_t * xact, last_entry->xacts) { - if (xact->has_xdata() && - xact->xdata().has_flags(XACT_EXT_TO_DISPLAY)) { - if (first) { - first_line_format.format(output_stream, *xact); - first = false; - } else { - next_lines_format.format(output_stream, *xact); - } - xact->xdata().add_flags(XACT_EXT_DISPLAYED); - } - } -} - -void format_entries::operator()(xact_t& xact) -{ - xact.xdata().add_flags(XACT_EXT_TO_DISPLAY); - - if (last_entry && xact.entry != last_entry) - format_last_entry(); - - last_entry = xact.entry; -} - -void print_entry(std::ostream& out, const entry_base_t& entry_base, - const string& prefix) -{ - string print_format; - - if (dynamic_cast<const entry_t *>(&entry_base)) { - print_format = (prefix + "%D %X%C%P\n" + - prefix + " %-34A %12o\n%/" + - prefix + " %-34A %12o\n"); - } - else if (const auto_entry_t * entry = - dynamic_cast<const auto_entry_t *>(&entry_base)) { - out << "= " << entry->predicate.predicate.text() << '\n'; - print_format = prefix + " %-34A %12o\n"; - } - else if (const period_entry_t * entry = - dynamic_cast<const period_entry_t *>(&entry_base)) { - out << "~ " << entry->period_string << '\n'; - print_format = prefix + " %-34A %12o\n"; - } - else { - assert(false); - } - -#if 0 - format_entries formatter(out, print_format); - walk_xacts(const_cast<xacts_list&>(entry_base.xacts), formatter); - formatter.flush(); - - clear_xact_xdata cleaner; - walk_xacts(const_cast<xacts_list&>(entry_base.xacts), cleaner); -#endif -} - -bool disp_subaccounts_p(account_t& account, - item_predicate<account_t>& disp_pred, - account_t *& to_show) -{ - bool display = false; - unsigned int counted = 0; - bool matches = disp_pred(account); - bool computed = false; - value_t acct_total; - value_t result; - - to_show = NULL; - - foreach (accounts_map::value_type pair, account.accounts) { - if (! disp_pred(*pair.second)) - continue; - -#if 0 - compute_total(result, *pair.second); -#endif - if (! computed) { -#if 0 - compute_total(acct_total, account); -#endif - computed = true; - } - - if ((result != acct_total) || counted > 0) { - display = matches; - break; - } - to_show = pair.second; - counted++; - } - - return display; -} - -bool display_account(account_t& account, item_predicate<account_t>& disp_pred) -{ - // Never display an account that has already been displayed. - if (account_has_xdata(account) && - account_xdata_(account).dflags & ACCOUNT_DISPLAYED) - return false; - - // At this point, one of two possibilities exists: the account is a - // leaf which matches the predicate restrictions; or it is a parent - // and two or more children must be subtotaled; or it is a parent - // and its child has been hidden by the predicate. So first, - // determine if it is a parent that must be displayed regardless of - // the predicate. - - account_t * account_to_show = NULL; - if (disp_subaccounts_p(account, disp_pred, account_to_show)) - return true; - - return ! account_to_show && disp_pred(account); -} - -void format_accounts::operator()(account_t& account) -{ - if (display_account(account, disp_pred)) { - if (! account.parent) { - account_xdata(account).dflags |= ACCOUNT_TO_DISPLAY; - } else { - format.format(output_stream, account); - account_xdata(account).dflags |= ACCOUNT_DISPLAYED; - } - } -} - -format_equity::format_equity(std::ostream& _output_stream, - const string& _format, - const string& display_predicate) - : output_stream(_output_stream), disp_pred(display_predicate) -{ - const char * f = _format.c_str(); - if (const char * p = std::strstr(f, "%/")) { - first_line_format.parse(string(f, 0, p - f)); - next_lines_format.parse(string(p + 2)); - } else { - first_line_format.parse(_format); - next_lines_format.parse(_format); - } - - entry_t header_entry; - header_entry.payee = "Opening Balances"; - header_entry._date = current_date; - first_line_format.format(output_stream, header_entry); -} - -void format_equity::flush() -{ - account_xdata_t xdata; - xdata.value = total; - xdata.value.negate(); - account_t summary(NULL, "Equity:Opening Balances"); - summary.data = &xdata; - - if (total.type() >= value_t::BALANCE) { - const balance_t * bal; - if (total.is_type(value_t::BALANCE)) - bal = &(total.as_balance()); - else if (total.is_type(value_t::BALANCE_PAIR)) - bal = &(total.as_balance_pair().quantity()); - else - assert(false); - - foreach (balance_t::amounts_map::value_type pair, bal->amounts) { - xdata.value = pair.second; - xdata.value.negate(); - next_lines_format.format(output_stream, summary); - } - } else { - next_lines_format.format(output_stream, summary); - } - output_stream.flush(); -} - -void format_equity::operator()(account_t& account) -{ - if (display_account(account, disp_pred)) { - if (account_has_xdata(account)) { - value_t val = account_xdata_(account).value; - - if (val.type() >= value_t::BALANCE) { - const balance_t * bal; - if (val.is_type(value_t::BALANCE)) - bal = &(val.as_balance()); - else if (val.is_type(value_t::BALANCE_PAIR)) - bal = &(val.as_balance_pair().quantity()); - else - assert(false); - - foreach (balance_t::amounts_map::value_type pair, bal->amounts) { - account_xdata_(account).value = pair.second; - next_lines_format.format(output_stream, account); - } - account_xdata_(account).value = val; - } else { - next_lines_format.format(output_stream, account); - } - total += val; - } - account_xdata(account).dflags |= ACCOUNT_DISPLAYED; - } -} - } // namespace ledger @@ -33,8 +33,7 @@ #define _REPORT_H #include "session.h" -#include "format.h" -#include "walk.h" +#include "handler.h" namespace ledger { @@ -196,13 +195,6 @@ public: void entry_report(const entry_t& entry, const string& format); // - // Utility functions for value expressions - // - - value_t ftime(call_scope_t& args); - value_t abbrev(call_scope_t& args); - - // // Config options // @@ -257,112 +249,6 @@ public: virtual expr_t::ptr_op_t lookup(const string& name); }; -string abbrev(const string& str, unsigned int width, - const bool is_account); - -// jww (2008-08-01): Where does this code belong? - -class format_xacts : public item_handler<xact_t> -{ -protected: - std::ostream& output_stream; - format_t first_line_format; - format_t next_lines_format; - entry_t * last_entry; - xact_t * last_xact; - -public: - format_xacts(std::ostream& _output_stream, - const string& format); - ~format_xacts() throw() { - TRACE_DTOR(format_xacts); - } - - virtual void flush() { - output_stream.flush(); - } - virtual void operator()(xact_t& xact); -}; - -class format_entries : public format_xacts -{ - public: - format_entries(std::ostream& output_stream, const string& format) - : format_xacts(output_stream, format) { - TRACE_CTOR(format_entries, "std::ostream&, const string&"); - } - ~format_entries() throw() { - TRACE_DTOR(format_entries); - } - - virtual void format_last_entry(); - - virtual void flush() { - if (last_entry) { - format_last_entry(); - last_entry = NULL; - } - format_xacts::flush(); - } - virtual void operator()(xact_t& xact); -}; - -void print_entry(std::ostream& out, - const entry_base_t& entry, - const string& prefix = ""); - -bool disp_subaccounts_p(account_t& account, - item_predicate<account_t>& disp_pred, - account_t *& to_show); - -bool display_account(account_t& account, item_predicate<account_t>& disp_pred); - -class format_accounts : public item_handler<account_t> -{ - std::ostream& output_stream; - - item_predicate<account_t> disp_pred; - - public: - format_t format; - - format_accounts(std::ostream& _output_stream, - const string& _format, - const string& display_predicate = NULL) - : output_stream(_output_stream), disp_pred(display_predicate), - format(_format) { - TRACE_CTOR(format_accounts, "std::ostream&, const string&, const string&"); - } - ~format_accounts() throw() { - TRACE_DTOR(format_accounts); - } - - virtual void flush() { - output_stream.flush(); - } - - virtual void operator()(account_t& account); -}; - -class format_equity : public item_handler<account_t> -{ - std::ostream& output_stream; - format_t first_line_format; - format_t next_lines_format; - - item_predicate<account_t> disp_pred; - - mutable value_t total; - - public: - format_equity(std::ostream& _output_stream, - const string& _format, - const string& display_predicate); - - virtual void flush(); - virtual void operator()(account_t& account); -}; - } // namespace ledger #endif // _REPORT_H @@ -31,7 +31,9 @@ #include "session.h" #include "report.h" -#include "walk.h" +#include "handler.h" +#include "iterators.h" +#include "filters.h" namespace ledger { @@ -256,9 +258,9 @@ void session_t::clean_xacts(entry_t& entry) void session_t::clean_accounts() { - accounts_iterator acct_walker(*master); - pass_down_accounts<accounts_iterator> - (acct_handler_ptr(new clear_account_xdata), acct_walker); + basic_accounts_iterator acct_walker(*master); + pass_down_accounts(acct_handler_ptr(new clear_account_xdata), + acct_walker); } #if 0 @@ -297,6 +299,43 @@ expr_t::ptr_op_t session_t::lookup(const string& name) { const char * p = name.c_str(); switch (*p) { + case 'b': + if (std::strcmp(p, "balance_format") == 0) + return expr_t::op_t::wrap_value(string_value(balance_format)); + break; + + case 'e': + if (std::strcmp(p, "equity_format") == 0) + return expr_t::op_t::wrap_value(string_value(equity_format)); + break; + + case 'p': + if (std::strcmp(p, "plot_amount_format") == 0) + return expr_t::op_t::wrap_value(string_value(plot_amount_format)); + else if (std::strcmp(p, "plot_total_format") == 0) + return expr_t::op_t::wrap_value(string_value(plot_total_format)); + else if (std::strcmp(p, "prices_format") == 0) + return expr_t::op_t::wrap_value(string_value(prices_format)); + else if (std::strcmp(p, "pricesdb_format") == 0) + return expr_t::op_t::wrap_value(string_value(pricesdb_format)); + else if (std::strcmp(p, "print_format") == 0) + return expr_t::op_t::wrap_value(string_value(print_format)); + break; + + case 'r': + if (std::strcmp(p, "register_format") == 0) + return expr_t::op_t::wrap_value(string_value(register_format)); + break; + + case 'w': + if (std::strcmp(p, "wide_register_format") == 0) + return expr_t::op_t::wrap_value(string_value(wide_register_format)); + else if (std::strcmp(p, "write_hdr_format") == 0) + return expr_t::op_t::wrap_value(string_value(write_hdr_format)); + else if (std::strcmp(p, "write_xact_format") == 0) + return expr_t::op_t::wrap_value(string_value(write_xact_format)); + break; + case 'o': if (std::strncmp(p, "opt_", 4) == 0) { p = p + 4; @@ -306,7 +306,7 @@ xact_t * parse_xact(char * line, account_t * account, entry_t * entry = NULL) if (entry != NULL) { // Add this amount to the related account now - account_xdata_t& xdata(account_xdata(*xact->account)); + account_t::xdata_t& xdata(xact->account->xdata()); if (! xact->amount.is_null()) { if (xdata.value.is_null()) @@ -33,8 +33,7 @@ #define _TEXTUAL_H #include "journal.h" -#include "format.h" -#include "walk.h" +#include "handler.h" namespace ledger { @@ -243,36 +243,8 @@ void expr_t::token_t::next(std::istream& in, const uint_least8_t pflags) kind = COLON; break; - case 'c': - case 'C': - case 'p': - case 'w': - case 'W': - case 'e': case '/': { - bool code_mask = c == 'c'; - bool commodity_mask = c == 'C'; - bool payee_mask = c == 'p'; - bool note_mask = c == 'e'; - bool short_account_mask = c == 'w'; - in.get(c); - if (c == '/') { - c = peek_next_nonws(in); - if (c == '/') { - in.get(c); - c = in.peek(); - if (c == '/') { - in.get(c); - c = in.peek(); - short_account_mask = true; - } else { - payee_mask = true; - } - } - } else { - in.get(c); - } // Read in the regexp char buf[256]; @@ -184,10 +184,10 @@ public: #endif }; - // This variable holds a pointer to "extended data" which is usually - // produced only during reporting, and only for the transaction set being - // reported. It's a memory-saving measure to delay allocation until the - // last possible moment. + // This variable holds optional "extended data" which is usually produced + // only during reporting, and only for the transaction set being reported. + // It's a memory-saving measure to delay allocation until the last possible + // moment. mutable optional<xdata_t> xdata_; bool has_xdata() const { @@ -34,6 +34,7 @@ #include "journal.h" #include "report.h" +#include "output.h" namespace ledger { |