Brought in the final round of 3.0 code, although it does not compile yet:

report, session, parts of xpath, main, journal, option.
This commit is contained in:
John Wiegley 2008-07-20 05:03:54 -04:00
parent 59f6ffb863
commit 52fc9f2e44
22 changed files with 2730 additions and 2674 deletions

View file

@ -51,6 +51,7 @@ libledger_la_SOURCES = \
qif.cc \ qif.cc \
reconcile.cc \ reconcile.cc \
report.cc \ report.cc \
session.cc \
startup.cc \ startup.cc \
textual.cc \ textual.cc \
valexpr.cc \ valexpr.cc \

188
binary.cc
View file

@ -179,6 +179,28 @@ void read_string(const char *& data, string * str)
read_guard(data, 0x3002); read_guard(data, 0x3002);
} }
void read_string(std::istream& in, optional<string>& str)
{
if (read_bool(in)) {
string temp;
read_string(in, temp);
str = temp;
} else {
str = none;
}
}
void read_string(const char *& data, optional<string>& str)
{
if (read_bool(data)) {
string temp;
read_string(data, temp);
str = temp;
} else {
str = none;
}
}
void write_bool(std::ostream& out, bool num) void write_bool(std::ostream& out, bool num)
{ {
@ -207,6 +229,16 @@ void write_string(std::ostream& out, const string& str)
write_guard(out, 0x3002); write_guard(out, 0x3002);
} }
void write_string(std::ostream& out, const optional<string>& str)
{
if (str) {
write_bool(out, true);
write_string(out, *str);
} else {
write_bool(out, false);
}
}
inline void read_amount(const char *& data, amount_t& amt) inline void read_amount(const char *& data, amount_t& amt)
{ {
commodity_t::ident_t ident; commodity_t::ident_t ident;
@ -260,50 +292,53 @@ inline void read_mask(const char *& data, mask_t *& mask)
mask->exclude = exclude; mask->exclude = exclude;
} }
inline void read_value_expr(const char *& data, value_expr_t *& expr) inline expr::ptr_op_t read_value_expr(const char *& data)
{ {
if (! read_bool(data)) { if (! read_bool(data))
expr = NULL; return expr::ptr_op_t();
return;
}
value_expr_t::kind_t kind; expr::op_t::kind_t kind;
read_number(data, kind); read_number(data, kind);
expr = new value_expr_t(kind); expr::ptr_op_t expr = new expr::op_t(kind);
if (kind > value_expr_t::TERMINALS) { if (kind > expr::op_t::TERMINALS)
read_value_expr(data, expr->left); expr->set_left(read_value_expr(data));
if (expr->left) expr->left->acquire();
}
switch (expr->kind) { switch (expr->kind) {
case value_expr_t::O_ARG: case expr::op_t::O_ARG:
case value_expr_t::INDEX: case expr::op_t::INDEX: {
read_long(data, expr->arg_index); long temp;
read_long(data, temp);
expr->set_long(temp);
break; break;
case value_expr_t::CONSTANT: }
expr->value = new value_t; case expr::op_t::VALUE: {
read_value(data, *expr->value); value_t temp;
read_value(data, temp);
expr->set_value(temp);
break; break;
}
case value_expr_t::F_CODE_MASK: case expr::op_t::F_CODE_MASK:
case value_expr_t::F_PAYEE_MASK: case expr::op_t::F_PAYEE_MASK:
case value_expr_t::F_NOTE_MASK: case expr::op_t::F_NOTE_MASK:
case value_expr_t::F_ACCOUNT_MASK: case expr::op_t::F_ACCOUNT_MASK:
case value_expr_t::F_SHORT_ACCOUNT_MASK: case expr::op_t::F_SHORT_ACCOUNT_MASK:
case value_expr_t::F_COMMODITY_MASK: case expr::op_t::F_COMMODITY_MASK:
#if 0
if (read_bool(data)) if (read_bool(data))
read_mask(data, expr->mask); read_mask(data, expr->mask);
#endif
break; break;
default: default:
if (kind > value_expr_t::TERMINALS) { if (kind > expr::op_t::TERMINALS)
read_value_expr(data, expr->right); expr->set_right(read_value_expr(data));
if (expr->right) expr->right->acquire();
}
break; break;
} }
return expr;
} }
@ -322,25 +357,29 @@ inline void read_transaction(const char *& data, transaction_t * xact)
read_string(data, xact->amount_expr.expr); read_string(data, xact->amount_expr.expr);
} }
else { else {
value_expr_t * ptr = NULL; expr::ptr_op_t ptr = read_value_expr(data);
read_value_expr(data, ptr); assert(ptr.get());
assert(ptr);
xact->amount_expr.reset(ptr); xact->amount_expr.reset(ptr);
read_string(data, xact->amount_expr.expr); read_string(data, xact->amount_expr.expr);
} }
if (read_bool(data)) { if (read_bool(data)) {
xact->cost = new amount_t; xact->cost = amount_t();
read_amount(data, *xact->cost); read_amount(data, *xact->cost);
read_string(data, xact->cost_expr);
expr::ptr_op_t ptr = read_value_expr(data);
assert(ptr.get());
value_expr expr;
expr.reset(ptr);
xact->cost_expr = expr;
} else { } else {
xact->cost = NULL; xact->cost = none;
} }
read_number(data, xact->state); read_number(data, xact->state);
read_number(data, xact->flags); xact->set_flags(read_number<transaction_t::flags_t>(data));
xact->flags |= TRANSACTION_BULK_ALLOC; xact->add_flags(TRANSACTION_BULK_ALLOC);
read_string(data, &xact->note); read_string(data, xact->note);
xact->beg_pos = read_long<unsigned long>(data); xact->beg_pos = read_long<unsigned long>(data);
read_long(data, xact->beg_line); read_long(data, xact->beg_line);
@ -350,7 +389,7 @@ inline void read_transaction(const char *& data, transaction_t * xact)
xact->data = NULL; xact->data = NULL;
if (xact->amount_expr) if (xact->amount_expr)
compute_amount(xact->amount_expr, xact->amount, xact); expr::compute_amount(xact->amount_expr.get(), xact->amount, xact);
} }
inline void read_entry_base(const char *& data, entry_base_t * entry, inline void read_entry_base(const char *& data, entry_base_t * entry,
@ -369,7 +408,7 @@ inline void read_entry_base(const char *& data, entry_base_t * entry,
i++) { i++) {
new(xact_pool) transaction_t; new(xact_pool) transaction_t;
read_transaction(data, xact_pool); read_transaction(data, xact_pool);
if (ignore_calculated && xact_pool->flags & TRANSACTION_CALCULATED) if (ignore_calculated && xact_pool->has_flags(TRANSACTION_CALCULATED))
finalize = true; finalize = true;
entry->add_transaction(xact_pool++); entry->add_transaction(xact_pool++);
} }
@ -381,8 +420,8 @@ inline void read_entry(const char *& data, entry_t * entry,
read_entry_base(data, entry, xact_pool, finalize); read_entry_base(data, entry, xact_pool, finalize);
read_number(data, entry->_date); read_number(data, entry->_date);
read_number(data, entry->_date_eff); read_number(data, entry->_date_eff);
read_string(data, &entry->code); read_string(data, entry->code);
read_string(data, &entry->payee); read_string(data, entry->payee);
} }
inline void read_auto_entry(const char *& data, auto_entry_t * entry, inline void read_auto_entry(const char *& data, auto_entry_t * entry,
@ -390,10 +429,7 @@ inline void read_auto_entry(const char *& data, auto_entry_t * entry,
{ {
bool ignore; bool ignore;
read_entry_base(data, entry, xact_pool, ignore); read_entry_base(data, entry, xact_pool, ignore);
value_expr_t * expr; entry->predicate = item_predicate<transaction_t>(read_value_expr(data));
read_value_expr(data, expr);
// the item_predicate constructor will acquire the reference
entry->predicate = new item_predicate<transaction_t>(expr);
} }
inline void read_period_entry(const char *& data, period_entry_t * entry, inline void read_period_entry(const char *& data, period_entry_t * entry,
@ -411,8 +447,7 @@ inline commodity_t::base_t * read_commodity_base(const char *& data)
read_string(data, str); read_string(data, str);
commodity_t::base_t * commodity = new commodity_t::base_t(str); std::auto_ptr<commodity_t::base_t> commodity(new commodity_t::base_t(str));
*base_commodities_next++ = commodity;
read_string(data, str); read_string(data, str);
if (! str.empty()) if (! str.empty())
@ -427,7 +462,7 @@ inline commodity_t::base_t * read_commodity_base(const char *& data)
read_number(data, flags); read_number(data, flags);
commodity->set_flags(flags); commodity->set_flags(flags);
return commodity; return *base_commodities_next++ = commodity.release();
} }
inline void read_commodity_base_extra(const char *& data, inline void read_commodity_base_extra(const char *& data,
@ -596,9 +631,14 @@ unsigned int read_journal(std::istream& in,
// Make sure that the cache uses the same price database, // Make sure that the cache uses the same price database,
// otherwise it means that LEDGER_PRICE_DB has been changed, and // otherwise it means that LEDGER_PRICE_DB has been changed, and
// we should ignore this cache file. // we should ignore this cache file.
if (read_string(in) != journal->price_db) if (read_bool(in)) {
string pathname;
read_string(in, pathname);
if (! journal->price_db ||
journal->price_db->string() != std::string(pathname))
return 0; return 0;
} }
}
// Read all of the data in at once, so that we're just dealing with // Read all of the data in at once, so that we're just dealing with
// a big data buffer. // a big data buffer.
@ -812,44 +852,47 @@ void write_mask(std::ostream& out, mask_t * mask)
write_string(out, mask->expr.str()); write_string(out, mask->expr.str());
} }
void write_value_expr(std::ostream& out, const value_expr_t * expr) void write_value_expr(std::ostream& out, const expr::ptr_op_t expr)
{ {
if (! expr) { if (! expr) {
write_bool(out, false); write_bool(out, false);
return; return;
} }
write_bool(out, true); write_bool(out, true);
write_number(out, expr->kind); write_number(out, expr->kind);
if (expr->kind > value_expr_t::TERMINALS) if (expr->kind > expr::op_t::TERMINALS)
write_value_expr(out, expr->left); write_value_expr(out, expr->left());
switch (expr->kind) { switch (expr->kind) {
case value_expr_t::O_ARG: case expr::op_t::O_ARG:
case value_expr_t::INDEX: case expr::op_t::INDEX:
write_long(out, expr->arg_index); write_long(out, expr->as_long());
break; break;
case value_expr_t::CONSTANT: case expr::op_t::VALUE:
write_value(out, *expr->value); write_value(out, expr->as_value());
break; break;
case value_expr_t::F_CODE_MASK: case expr::op_t::F_CODE_MASK:
case value_expr_t::F_PAYEE_MASK: case expr::op_t::F_PAYEE_MASK:
case value_expr_t::F_NOTE_MASK: case expr::op_t::F_NOTE_MASK:
case value_expr_t::F_ACCOUNT_MASK: case expr::op_t::F_ACCOUNT_MASK:
case value_expr_t::F_SHORT_ACCOUNT_MASK: case expr::op_t::F_SHORT_ACCOUNT_MASK:
case value_expr_t::F_COMMODITY_MASK: case expr::op_t::F_COMMODITY_MASK:
#if 0
if (expr->mask) { if (expr->mask) {
write_bool(out, true); write_bool(out, true);
write_mask(out, expr->mask); write_mask(out, expr->mask);
} else { } else {
write_bool(out, false); write_bool(out, false);
} }
#endif
break; break;
default: default:
if (expr->kind > value_expr_t::TERMINALS) if (expr->kind > expr::op_t::TERMINALS)
write_value_expr(out, expr->right); write_value_expr(out, expr->right());
break; break;
} }
@ -862,7 +905,7 @@ void write_transaction(std::ostream& out, transaction_t * xact,
write_number(out, xact->_date_eff); write_number(out, xact->_date_eff);
write_long(out, xact->account->ident); write_long(out, xact->account->ident);
if (ignore_calculated && xact->flags & TRANSACTION_CALCULATED) { if (ignore_calculated && xact->has_flags(TRANSACTION_CALCULATED)) {
write_number<unsigned char>(out, 0); write_number<unsigned char>(out, 0);
write_amount(out, amount_t()); write_amount(out, amount_t());
} }
@ -882,16 +925,16 @@ void write_transaction(std::ostream& out, transaction_t * xact,
} }
if (xact->cost && if (xact->cost &&
(! (ignore_calculated && xact->flags & TRANSACTION_CALCULATED))) { (! (ignore_calculated && xact->has_flags(TRANSACTION_CALCULATED)))) {
write_bool(out, true); write_bool(out, true);
write_amount(out, *xact->cost); write_amount(out, *xact->cost);
write_string(out, xact->cost_expr); write_string(out, xact->cost_expr->expr);
} else { } else {
write_bool(out, false); write_bool(out, false);
} }
write_number(out, xact->state); write_number(out, xact->state);
write_number(out, xact->flags); write_number(out, xact->flags());
write_string(out, xact->note); write_string(out, xact->note);
write_long(out, xact->beg_pos); write_long(out, xact->beg_pos);
@ -938,7 +981,7 @@ void write_entry(std::ostream& out, entry_t * entry)
void write_auto_entry(std::ostream& out, auto_entry_t * entry) void write_auto_entry(std::ostream& out, auto_entry_t * entry)
{ {
write_entry_base(out, entry); write_entry_base(out, entry);
write_value_expr(out, entry->predicate->predicate); write_value_expr(out, entry->predicate.predicate.get());
} }
void write_period_entry(std::ostream& out, period_entry_t * entry) void write_period_entry(std::ostream& out, period_entry_t * entry)
@ -1088,7 +1131,12 @@ void write_journal(std::ostream& out, journal_t * journal)
// Write out the price database that relates to this data file, so // Write out the price database that relates to this data file, so
// that if it ever changes the cache can be invalidated. // that if it ever changes the cache can be invalidated.
write_string(out, journal->price_db.string()); if (journal->price_db) {
write_bool(out, true);
write_string(out, journal->price_db->string());
} else {
write_bool(out, false);
}
} }
ostream_pos_type data_val = out.tellp(); ostream_pos_type data_val = out.tellp();

View file

@ -205,6 +205,9 @@ inline string read_string(const char *& data) {
return temp; return temp;
} }
void read_string(std::istream& in, optional<string>& str);
void read_string(const char *& data, optional<string>& str);
template <typename T> template <typename T>
inline void write_number_nocheck(std::ostream& out, T num) { inline void write_number_nocheck(std::ostream& out, T num) {
@ -262,6 +265,7 @@ void write_long(std::ostream& out, T num)
} }
void write_string(std::ostream& out, const string& str); void write_string(std::ostream& out, const string& str);
void write_string(std::ostream& out, const optional<string>& str);
template <typename T> template <typename T>
inline void write_object(std::ostream& out, const T& journal) { inline void write_object(std::ostream& out, const T& journal) {

3
csv.cc
View file

@ -87,7 +87,8 @@ void format_csv_transactions::operator()(transaction_t& xact)
} }
out << ','; out << ',';
write_escaped_string(out, xact.entry->code); if (xact.entry->code)
write_escaped_string(out, *xact.entry->code);
out << ','; out << ',';
{ {

View file

@ -20,10 +20,10 @@ void format_emacs_transactions::write_entry(entry_t& entry)
out << "(" << (date / 65536) << " " << (date % 65536) << " 0) "; out << "(" << (date / 65536) << " " << (date % 65536) << " 0) ";
if (entry.code.empty()) if (! entry.code)
out << "nil "; out << "nil ";
else else
out << "\"" << entry.code << "\" "; out << "\"" << *entry.code << "\" ";
if (entry.payee.empty()) if (entry.payee.empty())
out << "nil"; out << "nil";
@ -67,10 +67,8 @@ void format_emacs_transactions::operator()(transaction_t& xact)
if (xact.cost) if (xact.cost)
out << " \"" << *xact.cost << "\""; out << " \"" << *xact.cost << "\"";
else if (! xact.note.empty()) if (xact.note)
out << " nil"; out << " \"" << *xact.note << "\"";
if (! xact.note.empty())
out << " \"" << xact.note << "\"";
out << ")"; out << ")";
last_entry = xact.entry; last_entry = xact.entry;

View file

@ -468,8 +468,8 @@ void format_t::format(std::ostream& out_str, const details_t& details) const
else else
stream << details.xact->amount.strip_annotations(); stream << details.xact->amount.strip_annotations();
if (! details.xact->cost_expr.empty()) if (details.xact->cost_expr)
stream << details.xact->cost_expr; stream << details.xact->cost_expr->expr;
else else
stream << " @ " << amount_t(*details.xact->cost / stream << " @ " << amount_t(*details.xact->cost /
details.xact->amount).unround(); details.xact->amount).unround();
@ -653,9 +653,9 @@ void format_t::format(std::ostream& out_str, const details_t& details) const
case element_t::CODE: { case element_t::CODE: {
string temp; string temp;
if (details.entry && ! details.entry->code.empty()) { if (details.entry && details.entry->code) {
temp += "("; temp += "(";
temp += details.entry->code; temp += *details.entry->code;
temp += ") "; temp += ") ";
} }
out << temp; out << temp;
@ -670,14 +670,14 @@ void format_t::format(std::ostream& out_str, const details_t& details) const
break; break;
case element_t::OPT_NOTE: case element_t::OPT_NOTE:
if (details.xact && ! details.xact->note.empty()) if (details.xact && details.xact->note)
out << " ; "; out << " ; ";
// fall through... // fall through...
case element_t::NOTE: case element_t::NOTE:
if (details.xact) if (details.xact)
out << (elem->max_width == 0 ? out << (elem->max_width == 0 ?
details.xact->note : truncate(details.xact->note, details.xact->note : truncate(*details.xact->note,
elem->max_width)); elem->max_width));
break; break;
@ -705,11 +705,11 @@ void format_t::format(std::ostream& out_str, const details_t& details) const
details.account->fullname() : details.account->fullname() :
partial_account_name(*details.account)); partial_account_name(*details.account));
if (details.xact && details.xact->flags & TRANSACTION_VIRTUAL) { if (details.xact && details.xact->has_flags(TRANSACTION_VIRTUAL)) {
if (elem->max_width > 2) if (elem->max_width > 2)
name = truncate(name, elem->max_width - 2, true); name = truncate(name, elem->max_width - 2, true);
if (details.xact->flags & TRANSACTION_BALANCE) if (details.xact->has_flags(TRANSACTION_BALANCE))
name = string("[") + name + "]"; name = string("[") + name + "]";
else else
name = string("(") + name + ")"; name = string("(") + name + ")";
@ -828,7 +828,7 @@ void print_entry(std::ostream& out, const entry_base_t& entry_base,
} }
else if (const auto_entry_t * entry = else if (const auto_entry_t * entry =
dynamic_cast<const auto_entry_t *>(&entry_base)) { dynamic_cast<const auto_entry_t *>(&entry_base)) {
out << "= " << entry->predicate_string << '\n'; out << "= " << entry->predicate.predicate.expr << '\n';
print_format = prefix + " %-34A %12o\n"; print_format = prefix + " %-34A %12o\n";
} }
else if (const period_entry_t * entry = else if (const period_entry_t * entry =
@ -851,12 +851,12 @@ void print_entry(std::ostream& out, const entry_base_t& entry_base,
} }
bool disp_subaccounts_p(const account_t& account, bool disp_subaccounts_p(const account_t& account,
const item_predicate<account_t>& disp_pred, const optional<item_predicate<account_t> >& disp_pred,
const account_t *& to_show) const account_t *& to_show)
{ {
bool display = false; bool display = false;
unsigned int counted = 0; unsigned int counted = 0;
bool matches = disp_pred(account); bool matches = disp_pred ? (*disp_pred)(account) : true;
value_t acct_total; value_t acct_total;
bool computed = false; bool computed = false;
value_t result; value_t result;
@ -866,7 +866,7 @@ bool disp_subaccounts_p(const account_t& account,
for (accounts_map::const_iterator i = account.accounts.begin(); for (accounts_map::const_iterator i = account.accounts.begin();
i != account.accounts.end(); i != account.accounts.end();
i++) { i++) {
if (! disp_pred(*(*i).second)) if (disp_pred && ! (*disp_pred)(*(*i).second))
continue; continue;
compute_total(result, details_t(*(*i).second)); compute_total(result, details_t(*(*i).second));
@ -887,7 +887,7 @@ bool disp_subaccounts_p(const account_t& account,
} }
bool display_account(const account_t& account, bool display_account(const account_t& account,
const item_predicate<account_t>& disp_pred) const optional<item_predicate<account_t> >& disp_pred)
{ {
// Never display an account that has already been displayed. // Never display an account that has already been displayed.
if (account_has_xdata(account) && if (account_has_xdata(account) &&
@ -905,7 +905,7 @@ bool display_account(const account_t& account,
if (disp_subaccounts_p(account, disp_pred, account_to_show)) if (disp_subaccounts_p(account, disp_pred, account_to_show))
return true; return true;
return ! account_to_show && disp_pred(account); return ! account_to_show && (! disp_pred || (*disp_pred)(account));
} }
void format_account::operator()(account_t& account) void format_account::operator()(account_t& account)

View file

@ -154,16 +154,16 @@ void print_entry(std::ostream& out, const entry_base_t& entry,
const string& prefix = ""); const string& prefix = "");
bool disp_subaccounts_p(const account_t& account, bool disp_subaccounts_p(const account_t& account,
const item_predicate<account_t>& disp_pred, const optional<item_predicate<account_t> >& disp_pred,
const account_t *& to_show); const account_t *& to_show);
inline bool disp_subaccounts_p(const account_t& account) { inline bool disp_subaccounts_p(const account_t& account) {
const account_t * temp; const account_t * temp;
return disp_subaccounts_p(account, item_predicate<account_t>(NULL), temp); return disp_subaccounts_p(account, none, temp);
} }
bool display_account(const account_t& account, bool display_account(const account_t& account,
const item_predicate<account_t>& disp_pred); const optional<item_predicate<account_t> >& disp_pred);
class format_account : public item_handler<account_t> class format_account : public item_handler<account_t>
{ {

View file

@ -1,9 +1,38 @@
/*
* Copyright (c) 2003-2007, 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 "journal.h" #include "journal.h"
#include "utils.h" #include "utils.h"
#include "valexpr.h" #include "valexpr.h"
#include "mask.h" #include "mask.h"
#include "format.h"
#include "acconf.h"
namespace ledger { namespace ledger {
@ -13,22 +42,21 @@ bool transaction_t::use_effective_date = false;
transaction_t::~transaction_t() transaction_t::~transaction_t()
{ {
DEBUG("ledger.memory.dtors", "dtor transaction_t"); TRACE_DTOR(transaction_t);
if (cost) delete cost;
} }
datetime_t transaction_t::actual_date() const datetime_t transaction_t::actual_date() const
{ {
if (! is_valid(_date) && entry) if (! _date && entry)
return entry->actual_date(); return entry->actual_date();
return _date; return *_date;
} }
datetime_t transaction_t::effective_date() const datetime_t transaction_t::effective_date() const
{ {
if (! is_valid(_date_eff) && entry) if (! _date_eff && entry)
return entry->effective_date(); return entry->effective_date();
return _date_eff; return *_date_eff;
} }
bool transaction_t::valid() const bool transaction_t::valid() const
@ -43,15 +71,10 @@ bool transaction_t::valid() const
return false; return false;
} }
bool found = false; transactions_list::const_iterator i =
for (transactions_list::const_iterator i = entry->transactions.begin(); std::find(entry->transactions.begin(),
i != entry->transactions.end(); entry->transactions.end(), this);
i++) if (i == entry->transactions.end()) {
if (*i == this) {
found = true;
break;
}
if (! found) {
DEBUG("ledger.validate", "transaction_t: ! found"); DEBUG("ledger.validate", "transaction_t: ! found");
return false; return false;
} }
@ -71,7 +94,7 @@ bool transaction_t::valid() const
return false; return false;
} }
if (flags & ~0x003f) { if (flags() & ~0x003f) {
DEBUG("ledger.validate", "transaction_t: flags are bad"); DEBUG("ledger.validate", "transaction_t: flags are bad");
return false; return false;
} }
@ -99,29 +122,30 @@ bool entry_base_t::finalize()
// and the per-unit price of unpriced commodities. // and the per-unit price of unpriced commodities.
value_t balance; value_t balance;
bool no_amounts = true; bool no_amounts = true;
bool saw_null = false; bool saw_null = false;
for (transactions_list::const_iterator x = transactions.begin(); for (transactions_list::const_iterator x = transactions.begin();
x != transactions.end(); x != transactions.end();
x++) x++)
if (! ((*x)->flags & TRANSACTION_VIRTUAL) || if (! (*x)->has_flags(TRANSACTION_VIRTUAL) ||
((*x)->flags & TRANSACTION_BALANCE)) { (*x)->has_flags(TRANSACTION_BALANCE)) {
amount_t * p = (*x)->cost ? (*x)->cost : &(*x)->amount; amount_t& p((*x)->cost ? *(*x)->cost : (*x)->amount);
if (! p->is_null()) { if (! p.is_null()) {
if (no_amounts) { if (no_amounts) {
balance = *p; balance = p;
no_amounts = false; no_amounts = false;
} else { } else {
balance += *p; balance += p;
} }
assert((*x)->amount);
if ((*x)->cost && (*x)->amount.commodity().annotated) { if ((*x)->cost && (*x)->amount.commodity().annotated) {
annotated_commodity_t& annotated_commodity_t&
ann_comm(static_cast<annotated_commodity_t&> ann_comm(static_cast<annotated_commodity_t&>
((*x)->amount.commodity())); ((*x)->amount.commodity()));
if (ann_comm.details.price) if (ann_comm.details.price)
balance += ((*ann_comm.details.price) * (*x)->amount - balance += (*ann_comm.details.price * (*x)->amount.number() -
*((*x)->cost)); *((*x)->cost));
} }
} else { } else {
@ -137,12 +161,12 @@ bool entry_base_t::finalize()
// account if one has been set. // account if one has been set.
if (journal && journal->basket && transactions.size() == 1) { if (journal && journal->basket && transactions.size() == 1) {
assert(balance.type() < value_t::BALANCE); assert(balance.is_amount());
transaction_t * nxact = new transaction_t(journal->basket); transaction_t * nxact = new transaction_t(journal->basket);
// The amount doesn't need to be set because the code below will // The amount doesn't need to be set because the code below will
// balance this transaction against the other. // balance this transaction against the other.
add_transaction(nxact); add_transaction(nxact);
nxact->flags |= TRANSACTION_CALCULATED; nxact->add_flags(TRANSACTION_CALCULATED);
} }
// If the first transaction of a two-transaction entry is of a // If the first transaction of a two-transaction entry is of a
@ -150,30 +174,28 @@ bool entry_base_t::finalize()
// determine its price by dividing the unit count into the value of // determine its price by dividing the unit count into the value of
// the balance. This is done for the last eligible commodity. // the balance. This is done for the last eligible commodity.
if (! saw_null && balance && balance.is_type(value_t::BALANCE) && if (! saw_null && balance && balance.is_balance()) {
balance.as_balance_lval().amounts.size() == 2) { balance_t& bal(balance.as_balance_lval());
if (bal.amounts.size() == 2) {
transactions_list::const_iterator x = transactions.begin(); transactions_list::const_iterator x = transactions.begin();
assert((*x)->amount);
commodity_t& this_comm = (*x)->amount.commodity(); commodity_t& this_comm = (*x)->amount.commodity();
balance_t& bal(balance.as_balance_lval()); balance_t::amounts_map::const_iterator this_bal =
balance_t::amounts_map::const_iterator this_amt =
bal.amounts.find(&this_comm); bal.amounts.find(&this_comm);
balance_t::amounts_map::const_iterator other_amt = balance_t::amounts_map::const_iterator other_bal =
bal.amounts.begin(); bal.amounts.begin();
if (this_amt == other_amt) if (this_bal == other_bal)
other_amt++; other_bal++;
if (this_amt != bal.amounts.end()) {
amount_t per_unit_cost = amount_t per_unit_cost =
amount_t((*other_amt).second / (*this_amt).second).unround(); amount_t((*other_bal).second / (*this_bal).second.number()).unround();
for (; x != transactions.end(); x++) { for (; x != transactions.end(); x++) {
if ((*x)->cost || ((*x)->flags & TRANSACTION_VIRTUAL) || if ((*x)->cost || (*x)->has_flags(TRANSACTION_VIRTUAL) ||
! (*x)->amount || (*x)->amount.commodity() != this_comm) (*x)->amount.commodity() != this_comm)
continue; continue;
assert((*x)->amount);
balance -= (*x)->amount; balance -= (*x)->amount;
entry_t * entry = dynamic_cast<entry_t *>(this); entry_t * entry = dynamic_cast<entry_t *>(this);
@ -182,10 +204,10 @@ bool entry_base_t::finalize()
! (*x)->amount.commodity().annotated) ! (*x)->amount.commodity().annotated)
(*x)->amount.annotate_commodity (*x)->amount.annotate_commodity
(annotation_t(per_unit_cost.abs(), (annotation_t(per_unit_cost.abs(),
entry ? optional<datetime_t>(entry->actual_date()) : none, entry ? entry->actual_date() : optional<datetime_t>(),
entry ? optional<string>(entry->code) : none)); entry ? entry->code : optional<string>()));
(*x)->cost = new amount_t(- (per_unit_cost * (*x)->amount)); (*x)->cost = - (per_unit_cost * (*x)->amount.number());
balance += *(*x)->cost; balance += *(*x)->cost;
} }
} }
@ -199,13 +221,14 @@ bool entry_base_t::finalize()
for (transactions_list::const_iterator x = transactions.begin(); for (transactions_list::const_iterator x = transactions.begin();
x != transactions.end(); x != transactions.end();
x++) { x++) {
if (! (*x)->amount.is_null() || if ((*x)->amount ||
(((*x)->flags & TRANSACTION_VIRTUAL) && ((*x)->has_flags(TRANSACTION_VIRTUAL) &&
! ((*x)->flags & TRANSACTION_BALANCE))) ! (*x)->has_flags(TRANSACTION_BALANCE)))
continue; continue;
if (! empty_allowed) if (! empty_allowed)
throw new error("Only one transaction with null amount allowed per entry"); throw_(std::logic_error,
"Only one transaction with null amount allowed per entry");
empty_allowed = false; empty_allowed = false;
// If one transaction gives no value at all, its value will become // If one transaction gives no value at all, its value will become
@ -213,25 +236,25 @@ bool entry_base_t::finalize()
// commodities are involved, multiple transactions will be // commodities are involved, multiple transactions will be
// generated to balance them all. // generated to balance them all.
balance_t * bal = NULL; const balance_t * bal = NULL;
switch (balance.type()) { switch (balance.type()) {
case value_t::BALANCE_PAIR: case value_t::BALANCE_PAIR:
bal = &(balance.as_balance_pair_lval().quantity()); bal = &balance.as_balance_pair_lval().quantity();
// fall through... // fall through...
case value_t::BALANCE: case value_t::BALANCE:
if (! bal) if (! bal)
bal = &(balance.as_balance_lval()); bal = &balance.as_balance_lval();
if (bal->amounts.size() < 2) { if (bal->amounts.size() < 2) {
balance.cast(value_t::AMOUNT); balance.cast(value_t::AMOUNT);
} else { } else {
bool first = true; bool first = true;
for (balance_t::amounts_map::const_iterator i = bal->amounts.begin(); for (balance_t::amounts_map::const_iterator
i = bal->amounts.begin();
i != bal->amounts.end(); i != bal->amounts.end();
i++) { i++) {
amount_t amt = (*i).second; amount_t amt = (*i).second.negate();
amt.negate();
if (first) { if (first) {
(*x)->amount = amt; (*x)->amount = amt;
@ -239,7 +262,7 @@ bool entry_base_t::finalize()
} else { } else {
transaction_t * nxact = new transaction_t((*x)->account); transaction_t * nxact = new transaction_t((*x)->account);
add_transaction(nxact); add_transaction(nxact);
nxact->flags |= TRANSACTION_CALCULATED; nxact->add_flags(TRANSACTION_CALCULATED);
nxact->amount = amt; nxact->amount = amt;
} }
@ -250,9 +273,8 @@ bool entry_base_t::finalize()
// fall through... // fall through...
case value_t::AMOUNT: case value_t::AMOUNT:
(*x)->amount = balance.as_amount_lval(); (*x)->amount = balance.as_amount().negate();
(*x)->amount.in_place_negate(); (*x)->add_flags(TRANSACTION_CALCULATED);
(*x)->flags |= TRANSACTION_CALCULATED;
balance += (*x)->amount; balance += (*x)->amount;
break; break;
@ -280,8 +302,7 @@ entry_t::entry_t(const entry_t& e)
: entry_base_t(e), _date(e._date), _date_eff(e._date_eff), : entry_base_t(e), _date(e._date), _date_eff(e._date_eff),
code(e.code), payee(e.payee) code(e.code), payee(e.payee)
{ {
DEBUG("ledger.memory.ctors", "ctor entry_t"); TRACE_CTOR(entry_t, "copy");
for (transactions_list::const_iterator i = transactions.begin(); for (transactions_list::const_iterator i = transactions.begin();
i != transactions.end(); i != transactions.end();
i++) i++)
@ -333,20 +354,6 @@ bool entry_t::valid() const
return true; return true;
} }
auto_entry_t::auto_entry_t(const string& _predicate)
: predicate_string(_predicate)
{
DEBUG("ledger.memory.ctors", "ctor auto_entry_t");
predicate = new item_predicate<transaction_t>(predicate_string);
}
auto_entry_t::~auto_entry_t()
{
DEBUG("ledger.memory.dtors", "dtor auto_entry_t");
if (predicate)
delete predicate;
}
void auto_entry_t::extend_entry(entry_base_t& entry, bool post) void auto_entry_t::extend_entry(entry_base_t& entry, bool post)
{ {
transactions_list initial_xacts(entry.transactions.begin(), transactions_list initial_xacts(entry.transactions.begin(),
@ -355,14 +362,16 @@ void auto_entry_t::extend_entry(entry_base_t& entry, bool post)
for (transactions_list::iterator i = initial_xacts.begin(); for (transactions_list::iterator i = initial_xacts.begin();
i != initial_xacts.end(); i != initial_xacts.end();
i++) { i++) {
if ((*predicate)(**i)) { if (predicate(**i)) {
for (transactions_list::iterator t = transactions.begin(); for (transactions_list::iterator t = transactions.begin();
t != transactions.end(); t != transactions.end();
t++) { t++) {
amount_t amt; amount_t amt;
assert((*t)->amount);
if (! (*t)->amount.commodity()) { if (! (*t)->amount.commodity()) {
if (! post) if (! post)
continue; continue;
assert((*i)->amount);
amt = (*i)->amount * (*t)->amount; amt = (*i)->amount * (*t)->amount;
} else { } else {
if (post) if (post)
@ -377,7 +386,7 @@ void auto_entry_t::extend_entry(entry_base_t& entry, bool post)
account = (*i)->account; account = (*i)->account;
transaction_t * xact transaction_t * xact
= new transaction_t(account, amt, (*t)->flags | TRANSACTION_AUTO); = new transaction_t(account, amt, (*t)->flags() | TRANSACTION_AUTO);
// Copy over details so that the resulting transaction is a mirror of // Copy over details so that the resulting transaction is a mirror of
// the automated entry's one. // the automated entry's one.
@ -398,13 +407,12 @@ void auto_entry_t::extend_entry(entry_base_t& entry, bool post)
account_t::~account_t() account_t::~account_t()
{ {
DEBUG("ledger.memory.dtors", "dtor account_t " << this); TRACE_DTOR(account_t);
//assert(! data);
for (accounts_map::iterator i = accounts.begin(); for (accounts_map::iterator i = accounts.begin();
i != accounts.end(); i != accounts.end();
i++) i++)
delete (*i).second; checked_delete((*i).second);
} }
account_t * account_t::find_account(const string& name, account_t * account_t::find_account(const string& name,
@ -442,7 +450,7 @@ account_t * account_t::find_account(const string& name,
account->journal = journal; account->journal = journal;
std::pair<accounts_map::iterator, bool> result std::pair<accounts_map::iterator, bool> result
= accounts.insert(accounts_pair(first, account)); = accounts.insert(accounts_map::value_type(first, account));
assert(result.second); assert(result.second);
} else { } else {
account = (*i).second; account = (*i).second;
@ -526,29 +534,33 @@ bool account_t::valid() const
journal_t::~journal_t() journal_t::~journal_t()
{ {
DEBUG("ledger.memory.dtors", "dtor journal_t"); TRACE_DTOR(journal_t);
assert(master); assert(master);
delete master; checked_delete(master);
// Don't bother unhooking each entry's transactions from the // Don't bother unhooking each entry's transactions from the
// accounts they refer to, because all accounts are about to // accounts they refer to, because all accounts are about to
// be deleted. // be deleted.
for (entries_list::iterator i = entries.begin(); for (entries_list::iterator i = entries.begin();
i != entries.end(); i != entries.end();
i++) i++) {
if (! item_pool || if (! item_pool ||
((char *) *i) < item_pool || ((char *) *i) >= item_pool_end) reinterpret_cast<char *>(*i) < item_pool ||
delete *i; reinterpret_cast<char *>(*i) >= item_pool_end) {
else checked_delete(*i);
} else {
(*i)->~entry_t(); (*i)->~entry_t();
}
}
for (auto_entries_list::iterator i = auto_entries.begin(); for (auto_entries_list::iterator i = auto_entries.begin();
i != auto_entries.end(); i != auto_entries.end();
i++) i++)
if (! item_pool || if (! item_pool ||
((char *) *i) < item_pool || ((char *) *i) >= item_pool_end) reinterpret_cast<char *>(*i) < item_pool ||
delete *i; reinterpret_cast<char *>(*i) >= item_pool_end)
checked_delete(*i);
else else
(*i)->~auto_entry_t(); (*i)->~auto_entry_t();
@ -556,13 +568,14 @@ journal_t::~journal_t()
i != period_entries.end(); i != period_entries.end();
i++) i++)
if (! item_pool || if (! item_pool ||
((char *) *i) < item_pool || ((char *) *i) >= item_pool_end) reinterpret_cast<char *>(*i) < item_pool ||
delete *i; reinterpret_cast<char *>(*i) >= item_pool_end)
checked_delete(*i);
else else
(*i)->~period_entry_t(); (*i)->~period_entry_t();
if (item_pool) if (item_pool)
delete[] item_pool; checked_array_delete(item_pool);
} }
bool journal_t::add_entry(entry_t * entry) bool journal_t::add_entry(entry_t * entry)
@ -581,9 +594,11 @@ bool journal_t::add_entry(entry_t * entry)
for (transactions_list::const_iterator i = entry->transactions.begin(); for (transactions_list::const_iterator i = entry->transactions.begin();
i != entry->transactions.end(); i != entry->transactions.end();
i++) i++)
if ((*i)->cost && (*i)->amount) if ((*i)->cost) {
assert((*i)->amount);
(*i)->amount.commodity().add_price(entry->date(), (*i)->amount.commodity().add_price(entry->date(),
*(*i)->cost / (*i)->amount); *(*i)->cost / (*i)->amount.number());
}
return true; return true;
} }
@ -621,16 +636,43 @@ bool journal_t::valid() const
return false; return false;
} }
for (commodity_pool_t::commodities_by_ident::const_iterator return true;
i = amount_t::current_pool->commodities.begin(); }
i != amount_t::current_pool->commodities.end();
i++) void print_entry(std::ostream& out, const entry_base_t& entry_base,
if (! (*i)->valid()) { const string& prefix)
DEBUG("ledger.validate", "journal_t: commodity not valid"); {
return false; 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.expr << '\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);
} }
return true; #if 0
format_entries formatter(out, print_format);
walk_transactions(const_cast<transactions_list&>(entry_base.transactions),
formatter);
formatter.flush();
clear_transaction_xdata cleaner;
walk_transactions(const_cast<transactions_list&>(entry_base.transactions),
cleaner);
#endif
} }
void entry_context::describe(std::ostream& out) const throw() void entry_context::describe(std::ostream& out) const throw()

198
journal.h
View file

@ -1,3 +1,34 @@
/*
* Copyright (c) 2003-2007, 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 _JOURNAL_H #ifndef _JOURNAL_H
#define _JOURNAL_H #define _JOURNAL_H
@ -19,52 +50,64 @@ namespace ledger {
class entry_t; class entry_t;
class account_t; class account_t;
class transaction_t class transaction_t : public supports_flags<>
{ {
public: public:
enum state_t { UNCLEARED, CLEARED, PENDING }; enum state_t { UNCLEARED, CLEARED, PENDING };
entry_t * entry; entry_t * entry;
datetime_t _date; state_t state;
datetime_t _date_eff;
account_t * account; account_t * account;
optional<datetime_t> _date;
optional<datetime_t> _date_eff;
amount_t amount; amount_t amount;
value_expr amount_expr; value_expr amount_expr;
amount_t * cost; optional<amount_t> cost;
string cost_expr; optional<value_expr> cost_expr;
state_t state; optional<string> note;
unsigned short flags;
string note;
istream_pos_type beg_pos; istream_pos_type beg_pos;
unsigned long beg_line; unsigned long beg_line;
istream_pos_type end_pos; istream_pos_type end_pos;
unsigned long end_line; unsigned long end_line;
mutable void * data;
mutable void * data;
static bool use_effective_date; static bool use_effective_date;
transaction_t(account_t * _account = NULL) explicit transaction_t(account_t * _account = NULL)
: entry(NULL), account(_account), cost(NULL), : supports_flags<>(TRANSACTION_NORMAL), entry(NULL),
state(UNCLEARED), flags(TRANSACTION_NORMAL), state(UNCLEARED), account(_account),
beg_pos(0), beg_line(0), end_pos(0), end_line(0), data(NULL) { beg_pos(0), beg_line(0), end_pos(0), end_line(0), data(NULL)
DEBUG("ledger.memory.ctors", "ctor transaction_t"); {
TRACE_CTOR(transaction_t, "account_t *");
} }
transaction_t(account_t * _account, explicit transaction_t(account_t * _account,
const amount_t& _amount, const amount_t& _amount,
unsigned int _flags = TRANSACTION_NORMAL, unsigned int _flags = TRANSACTION_NORMAL,
const string& _note = "") const optional<string> _note = none)
: entry(NULL), account(_account), amount(_amount), cost(NULL), : supports_flags<>(_flags), entry(NULL), state(UNCLEARED),
state(UNCLEARED), flags(_flags), account(_account), amount(_amount), note(_note),
note(_note), beg_pos(0), beg_line(0), end_pos(0), end_line(0), beg_pos(0), beg_line(0), end_pos(0), end_line(0), data(NULL)
data(NULL) { {
DEBUG("ledger.memory.ctors", "ctor transaction_t"); TRACE_CTOR(transaction_t,
"account_t *, const amount_t&, unsigned int, const string&");
} }
transaction_t(const transaction_t& xact) explicit transaction_t(const transaction_t& xact)
: entry(xact.entry), account(xact.account), amount(xact.amount), : supports_flags<>(xact),
cost(xact.cost ? new amount_t(*xact.cost) : NULL), entry(xact.entry),
state(xact.state), flags(xact.flags), note(xact.note), state(xact.state),
beg_pos(0), beg_line(0), end_pos(0), end_line(0), data(NULL) { account(xact.account),
DEBUG("ledger.memory.ctors", "ctor transaction_t"); _date(xact._date),
_date_eff(xact._date_eff),
amount(xact.amount),
cost(xact.cost),
note(xact.note),
beg_pos(xact.beg_pos),
beg_line(xact.beg_line),
end_pos(xact.end_pos),
end_line(xact.end_line),
data(xact.data) // jww (2008-07-19): What are the copy semantics?
{
TRACE_CTOR(transaction_t, "copy");
} }
~transaction_t(); ~transaction_t();
@ -77,13 +120,6 @@ class transaction_t
return actual_date(); return actual_date();
} }
bool operator==(const transaction_t& xact) {
return this == &xact;
}
bool operator!=(const transaction_t& xact) {
return ! (*this == xact);
}
bool valid() const; bool valid() const;
}; };
@ -115,24 +151,24 @@ class entry_base_t
entry_base_t() : journal(NULL), entry_base_t() : journal(NULL),
beg_pos(0), beg_line(0), end_pos(0), end_line(0) beg_pos(0), beg_line(0), end_pos(0), end_line(0)
{ {
DEBUG("ledger.memory.ctors", "ctor entry_base_t"); TRACE_CTOR(entry_base_t, "");
} }
entry_base_t(const entry_base_t& e) : journal(NULL), entry_base_t(const entry_base_t& e) : journal(NULL),
beg_pos(0), beg_line(0), end_pos(0), end_line(0) beg_pos(0), beg_line(0), end_pos(0), end_line(0)
{ {
DEBUG("ledger.memory.ctors", "ctor entry_base_t"); TRACE_CTOR(entry_base_t, "copy");
for (transactions_list::const_iterator i = e.transactions.begin(); for (transactions_list::const_iterator i = e.transactions.begin();
i != e.transactions.end(); i != e.transactions.end();
i++) i++)
transactions.push_back(new transaction_t(**i)); transactions.push_back(new transaction_t(**i));
} }
virtual ~entry_base_t() { virtual ~entry_base_t() {
DEBUG("ledger.memory.dtors", "dtor entry_base_t"); TRACE_DTOR(entry_base_t);
for (transactions_list::iterator i = transactions.begin(); for (transactions_list::iterator i = transactions.begin();
i != transactions.end(); i != transactions.end();
i++) i++)
if (! ((*i)->flags & TRANSACTION_BULK_ALLOC)) if (! (*i)->has_flags(TRANSACTION_BULK_ALLOC))
delete *i; checked_delete(*i);
else else
(*i)->~transaction_t(); (*i)->~transaction_t();
} }
@ -153,28 +189,28 @@ class entry_base_t
class entry_t : public entry_base_t class entry_t : public entry_base_t
{ {
public: public:
datetime_t _date; datetime_t _date;
datetime_t _date_eff; optional<datetime_t> _date_eff;
string code; optional<string> code;
string payee; string payee;
entry_t() { entry_t() {
DEBUG("ledger.memory.ctors", "ctor entry_t"); TRACE_CTOR(entry_t, "");
} }
entry_t(const entry_t& e); entry_t(const entry_t& e);
virtual ~entry_t() { virtual ~entry_t() {
DEBUG("ledger.memory.dtors", "dtor entry_t"); TRACE_DTOR(entry_t);
} }
datetime_t actual_date() const { datetime_t actual_date() const {
return _date; return _date;
} }
datetime_t effective_date() const { datetime_t effective_date() const {
if (! is_valid(_date_eff)) if (! _date_eff)
return _date; return _date;
return _date_eff; return *_date_eff;
} }
datetime_t date() const { datetime_t date() const {
if (transaction_t::use_effective_date) if (transaction_t::use_effective_date)
@ -200,8 +236,8 @@ class entry_context : public error_context {
const entry_base_t& entry; const entry_base_t& entry;
entry_context(const entry_base_t& _entry, entry_context(const entry_base_t& _entry,
const string& desc = "") throw() const string& _desc = "") throw()
: error_context(desc), entry(_entry) {} : error_context(_desc), entry(_entry) {}
virtual ~entry_context() throw() {} virtual ~entry_context() throw() {}
virtual void describe(std::ostream& out) const throw(); virtual void describe(std::ostream& out) const throw();
@ -214,15 +250,18 @@ class item_predicate;
class auto_entry_t : public entry_base_t class auto_entry_t : public entry_base_t
{ {
public: public:
item_predicate<transaction_t> * predicate; item_predicate<transaction_t> predicate;
string predicate_string;
auto_entry_t() : predicate(NULL) { auto_entry_t();
DEBUG("ledger.memory.ctors", "ctor auto_entry_t"); auto_entry_t(const string& _predicate)
: predicate(_predicate)
{
TRACE_CTOR(auto_entry_t, "const string&");
} }
auto_entry_t(const string& _predicate);
virtual ~auto_entry_t(); virtual ~auto_entry_t() {
TRACE_DTOR(auto_entry_t);
}
virtual void extend_entry(entry_base_t& entry, bool post); virtual void extend_entry(entry_base_t& entry, bool post);
virtual bool valid() const { virtual bool valid() const {
@ -230,7 +269,6 @@ public:
} }
}; };
class journal_t;
struct auto_entry_finalizer_t : public entry_finalizer_t { struct auto_entry_finalizer_t : public entry_finalizer_t {
journal_t * journal; journal_t * journal;
auto_entry_finalizer_t(journal_t * _journal) : journal(_journal) {} auto_entry_finalizer_t(journal_t * _journal) : journal(_journal) {}
@ -245,19 +283,19 @@ class period_entry_t : public entry_base_t
string period_string; string period_string;
period_entry_t() { period_entry_t() {
DEBUG("ledger.memory.ctors", "ctor period_entry_t"); TRACE_CTOR(period_entry_t, "");
} }
period_entry_t(const string& _period) period_entry_t(const string& _period)
: period(_period), period_string(_period) { : period(_period), period_string(_period) {
DEBUG("ledger.memory.ctors", "ctor period_entry_t"); TRACE_CTOR(period_entry_t, "const string&");
} }
period_entry_t(const period_entry_t& e) period_entry_t(const period_entry_t& e)
: entry_base_t(e), period(e.period), period_string(e.period_string) { : entry_base_t(e), period(e.period), period_string(e.period_string) {
DEBUG("ledger.memory.ctors", "ctor period_entry_t"); TRACE_CTOR(period_entry_t, "copy");
} }
virtual ~period_entry_t() { virtual ~period_entry_t() {
DEBUG("ledger.memory.dtors", "dtor period_entry_t"); TRACE_DTOR(period_entry_t);
} }
virtual bool valid() const { virtual bool valid() const {
@ -267,7 +305,6 @@ class period_entry_t : public entry_base_t
typedef std::map<const string, account_t *> accounts_map; typedef std::map<const string, account_t *> accounts_map;
typedef std::pair<const string, account_t *> accounts_pair;
class account_t class account_t
{ {
@ -277,7 +314,7 @@ class account_t
journal_t * journal; journal_t * journal;
account_t * parent; account_t * parent;
string name; string name;
string note; optional<string> note;
unsigned short depth; unsigned short depth;
accounts_map accounts; accounts_map accounts;
@ -287,24 +324,20 @@ class account_t
account_t(account_t * _parent = NULL, account_t(account_t * _parent = NULL,
const string& _name = "", const string& _name = "",
const string& _note = "") const optional<string> _note = none)
: parent(_parent), name(_name), note(_note), : parent(_parent), name(_name), note(_note),
depth(parent ? parent->depth + 1 : 0), data(NULL), ident(0) { depth(parent ? parent->depth + 1 : 0), data(NULL), ident(0) {
DEBUG("ledger.memory.ctors", "ctor account_t " << this); TRACE_CTOR(account_t, "account_t *, const string&, const string&");
} }
~account_t(); ~account_t();
bool operator==(const account_t& account) { operator string() const {
return this == &account; return fullname();
} }
bool operator!=(const account_t& account) {
return ! (*this == account);
}
string fullname() const; string fullname() const;
void add_account(account_t * acct) { void add_account(account_t * acct) {
accounts.insert(accounts_pair(acct->name, acct)); accounts.insert(accounts_map::value_type(acct->name, acct));
acct->journal = journal; acct->journal = journal;
} }
bool remove_account(account_t * acct) { bool remove_account(account_t * acct) {
@ -315,10 +348,6 @@ class account_t
account_t * find_account(const string& name, bool auto_create = true); account_t * find_account(const string& name, bool auto_create = true);
operator string() const {
return fullname();
}
bool valid() const; bool valid() const;
friend class journal_t; friend class journal_t;
@ -331,7 +360,8 @@ struct func_finalizer_t : public entry_finalizer_t {
typedef bool (*func_t)(entry_t& entry, bool post); typedef bool (*func_t)(entry_t& entry, bool post);
func_t func; func_t func;
func_finalizer_t(func_t _func) : func(_func) {} func_finalizer_t(func_t _func) : func(_func) {}
func_finalizer_t(const func_finalizer_t& other) : func(other.func) {} func_finalizer_t(const func_finalizer_t& other) :
entry_finalizer_t(), func(other.func) {}
virtual bool operator()(entry_t& entry, bool post) { virtual bool operator()(entry_t& entry, bool post) {
return func(entry, post); return func(entry, post);
} }
@ -364,8 +394,8 @@ bool run_hooks(std::list<T>& list, Data& item, bool post) {
typedef std::list<entry_t *> entries_list; typedef std::list<entry_t *> entries_list;
typedef std::list<auto_entry_t *> auto_entries_list; typedef std::list<auto_entry_t *> auto_entries_list;
typedef std::list<period_entry_t *> period_entries_list; typedef std::list<period_entry_t *> period_entries_list;
typedef std::list<string> strings_list;
typedef std::list<path> paths_list; typedef std::list<path> paths_list;
typedef std::list<string> strings_list;
class journal_t class journal_t
{ {
@ -374,7 +404,7 @@ class journal_t
account_t * basket; account_t * basket;
entries_list entries; entries_list entries;
paths_list sources; paths_list sources;
path price_db; optional<path> price_db;
char * item_pool; char * item_pool;
char * item_pool_end; char * item_pool_end;
@ -384,21 +414,13 @@ class journal_t
std::list<entry_finalizer_t *> entry_finalize_hooks; std::list<entry_finalizer_t *> entry_finalize_hooks;
journal_t() : basket(NULL) { journal_t() : basket(NULL), item_pool(NULL), item_pool_end(NULL) {
DEBUG("ledger.memory.ctors", "ctor journal_t"); TRACE_CTOR(journal_t, "");
master = new account_t(NULL, ""); master = new account_t(NULL, "");
master->journal = this; master->journal = this;
item_pool = item_pool_end = NULL;
} }
~journal_t(); ~journal_t();
bool operator==(const journal_t& journal) {
return this == &journal;
}
bool operator!=(const journal_t& journal) {
return ! (*this == journal);
}
void add_account(account_t * acct) { void add_account(account_t * acct) {
master->add_account(acct); master->add_account(acct);
acct->journal = this; acct->journal = this;
@ -414,7 +436,7 @@ class journal_t
return (*c).second; return (*c).second;
account_t * account = master->find_account(name, auto_create); account_t * account = master->find_account(name, auto_create);
accounts_cache.insert(accounts_pair(name, account)); accounts_cache.insert(accounts_map::value_type(name, account));
account->journal = this; account->journal = this;
return account; return account;
} }

702
main.cc
View file

@ -1,232 +1,214 @@
#include <iostream> /*
#include <fstream> * Copyright (c) 2003-2007, John Wiegley. All rights reserved.
#include <sstream> *
#include <algorithm> * Redistribution and use in source and binary forms, with or without
#include <exception> * modification, are permitted provided that the following conditions are
#include <iterator> * met:
#include <string> *
#include <cstdio> * - Redistributions of source code must retain the above copyright
#include <cstdlib> * notice, this list of conditions and the following disclaimer.
#include <cstring> *
* - 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 "acconf.h" #include "utils.h"
#include "option.h"
//#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
//#include "gnucash.h"
//#endif
//#include "qif.h"
//#include "ofx.h"
#include "jbuilder.h"
#include "compile.h"
#include <ledger.h>
#ifdef HAVE_UNIX_PIPES #ifdef HAVE_UNIX_PIPES
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <unistd.h> #include <fdstream.hpp>
#include "fdstream.hpp"
#endif #endif
#include "ledger.h" static int read_and_report(ledger::report_t& report, int argc, char * argv[],
char * envp[])
using namespace ledger;
int parse_and_report(config_t& config, std::auto_ptr<journal_t>& journal,
report_t& report, int argc, char * argv[], char * envp[])
{ {
// Configure the terminus for value expressions using namespace ledger;
ledger::terminus = current_moment; session_t& session(report.session);
// Parse command-line arguments, and those set in the environment // Handle the command-line arguments
std::list<string> args; strings_list args;
process_arguments(ledger::config_options, argc - 1, argv + 1, false, args); process_arguments(argc - 1, argv + 1, false, report, args);
if (args.empty()) { if (args.empty()) {
option_help(std::cerr); #if 0
help(std::cerr);
#endif
return 1; return 1;
} }
strings_list::iterator arg = args.begin(); strings_list::iterator arg = args.begin();
if (config.cache_file == "<none>") if (! session.cache_file)
config.use_cache = false; session.use_cache = false;
else else
config.use_cache = config.data_file.empty() && config.price_db.empty(); session.use_cache = ! session.data_file.empty() && session.price_db;
DEBUG("ledger.config.cache", "1. use_cache = " << config.use_cache);
DEBUG("ledger.session.cache", "1. use_cache = " << session.use_cache);
// Process the environment settings
TRACE_START(environment, 1, "Processed environment variables");
process_environment(const_cast<const char **>(envp), "LEDGER_", report);
TRACE_FINISH(environment, 1);
optional<path> home;
if (const char * home_var = std::getenv("HOME"))
home = home_var;
if (! session.init_file)
session.init_file = home ? *home / ".ledgerrc" : "./.ledgerrc";
if (! session.price_db)
session.price_db = home ? *home / ".pricedb" : "./.pricedb";
if (! session.cache_file)
session.cache_file = home ? *home / ".ledger-cache" : "./.ledger-cache";
if (session.data_file == *session.cache_file)
session.use_cache = false;
DEBUG("ledger.session.cache", "2. use_cache = " << session.use_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.use_cache)
INFO("Binary cache mechanism will not be used");
// Read the command word and create a command object based on it
string verb = *arg++;
xml::xpath_t::function_t command;
#if 0 #if 0
TRACE(main, "Processing options and environment variables"); if (verb == "register" || verb == "reg" || verb == "r")
command = register_command();
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
#endif #endif
if (verb == "xml")
command = bind(xml_command, _1);
else if (verb == "expr")
;
else if (verb == "xpath")
;
else if (verb == "parse") {
xml::xpath_t expr(*arg);
xml::document_t temp(xml::LEDGER_NODE);
process_environment(ledger::config_options, xml::xpath_t::context_scope_t doc_scope(report, &temp);
const_cast<const char **>(envp), "LEDGER_");
#if 1 IF_INFO() {
// These are here for backwards compatability, but are deprecated.
if (const char * p = std::getenv("LEDGER"))
process_option(ledger::config_options, "file", p);
if (const char * p = std::getenv("LEDGER_INIT"))
process_option(ledger::config_options, "init-file", p);
if (const char * p = std::getenv("PRICE_HIST"))
process_option(ledger::config_options, "price-db", p);
if (const char * p = std::getenv("PRICE_EXP"))
process_option(ledger::config_options, "price-exp", p);
#endif
const char * p = std::getenv("HOME");
string home = p ? p : "";
if (config.init_file.empty())
config.init_file = home + "/.ledgerrc";
if (config.price_db.empty())
config.price_db = home + "/.pricedb";
if (config.cache_file.empty())
config.cache_file = home + "/.ledger-cache";
if (config.data_file == config.cache_file)
config.use_cache = false;
DEBUG("ledger.config.cache", "2. use_cache = " << config.use_cache);
#if 0
TRACE(main, string("Initialization file is ") + config.init_file);
TRACE(main, string("Price database is ") + config.price_db);
TRACE(main, string("Binary cache is ") + config.cache_file);
TRACE(main, string("Main journal is ") + config.data_file);
TRACE(main, string("Based on option settings, binary cache ") +
(config.use_cache ? "WILL " : "will NOT ") + "be used");
#endif
// Read the command word, canonicalize it to its one letter form,
// then configure the system based on the kind of report to be
// generated
string command = *arg++;
if (command == "balance" || command == "bal" || command == "b")
command = "b";
else if (command == "register" || command == "reg" || command == "r")
command = "r";
else if (command == "print" || command == "p")
command = "p";
else if (command == "output")
command = "w";
else if (command == "dump")
command = "W";
else if (command == "emacs" || command == "lisp")
command = "x";
else if (command == "xml")
command = "X";
else if (command == "entry")
command = "e";
else if (command == "equity")
command = "E";
else if (command == "prices")
command = "P";
else if (command == "pricesdb")
command = "D";
else if (command == "csv")
command = "c";
else if (command == "parse") {
value_expr expr(ledger::parse_value_expr(*arg));
if (config.verbose_mode) {
std::cout << "Value expression tree:" << std::endl; std::cout << "Value expression tree:" << std::endl;
ledger::dump_value_expr(std::cout, expr.get()); expr.dump(std::cout);
std::cout << std::endl; std::cout << std::endl;
std::cout << "Value expression parsed was:" << std::endl; std::cout << "Value expression parsed was:" << std::endl;
ledger::print_value_expr(std::cout, expr.get()); expr.print(std::cout, doc_scope);
std::cout << std::endl << std::endl; std::cout << std::endl << std::endl;
std::cout << "Result of computation: ";
expr.compile(doc_scope);
std::cout << "Value expression after compiling:" << std::endl;
expr.dump(std::cout);
std::cout << std::endl;
std::cout << "Value expression is now:" << std::endl;
expr.print(std::cout, doc_scope);
std::cout << std::endl << std::endl;
std::cout << "Result of calculation: ";
} }
value_t result = guarded_compute(expr.get()); std::cout << expr.calc(doc_scope).strip_annotations() << std::endl;
std::cout << result.strip_annotations() << std::endl;
return 0; return 0;
} }
else if (command == "expr") {
// this gets done below...
}
else { else {
throw new error(string("Unrecognized command '") + command + "'"); char buf[128];
std::strcpy(buf, "command_");
std::strcat(buf, verb.c_str());
if (xml::xpath_t::ptr_op_t def = report.lookup(buf))
command = def->as_function();
if (! command)
throw_(std::logic_error, string("Unrecognized command '") + verb + "'");
} }
// Parse initialization files, ledger data, price database, etc. // Parse the initialization file, which can only be textual; then
// parse the journal data.
journal.reset(new journal_t); session.read_init();
#if 0 INFO_START(journal, "Read journal file");
{ TRACE_PUSH(parser, "Parsing journal file");
#endif
if (parse_ledger_data(config, journal.get()) == 0) xml::document_t xml_document(xml::LEDGER_NODE);
throw new error("Please specify ledger file using -f" journal_t * journal = session.create_journal();
" or LEDGER_FILE environment variable."); xml::journal_builder_t builder(xml_document, journal);
#if 0 if (! session.read_data(builder, journal, report.account))
TRACE_POP(parser, "Finished parsing"); } throw_(parse_error, "Failed to locate any journal entries; "
#endif "did you specify a valid file with -f?");
// process the command word and its following arguments INFO_FINISH(journal);
string first_arg; TRACE_FINISH(entry_text, 1);
if (command == "w") { TRACE_FINISH(entry_date, 1);
if (arg != args.end()) TRACE_FINISH(entry_details, 1);
first_arg = *arg++; TRACE_FINISH(entry_xacts, 1);
} TRACE_FINISH(entries, 1);
else if (command == "W") { TRACE_FINISH(parsing_total, 1);
if (report.output_file.empty())
throw new error("The 'dump' command requires use of the --output option");
}
#if 0
TRACE(options, string("Post-processing options ") +
"for command \"" + command + "\"");
#endif
report.process_options(command, arg, args.end());
#if 0
// jww (2008-05-08): Is this disabled now?
// If downloading is to be supported, configure the updater
if (! commodity_base_t::updater && config.download_quotes)
commodity_base_t::updater =
new quotes_by_script(config.price_db, config.pricing_leeway,
config.cache_dirty);
#endif
std::auto_ptr<entry_t> new_entry;
if (command == "e") {
if (arg == args.end()) {
std::cout << "\
The entry command requires at least one argument, so Ledger can intelligently\n\
create a new entry for you. The possible arguments are:\n\
DATE PAYEE [ACCOUNT] [AMOUNT] [DRAW ACCOUNT]\n\n\
Some things to note:\n\
- The ACCOUNT is optional; if no account is given, the last account affected\n\
by PAYEE is used. If no payee can be found, the generic account 'Expenses'\n\
is used.\n\
- The AMOUNT is optional; if not specified, the same amount is used as the\n\
last time PAYEE was seen, or 0 if not applicable.\n\
- The AMOUNT does not require a commodity; if none is given, the commodity\n\
currently contained within ACCOUNT is used, or no commodity at all if\n\
either: the ACCOUNT was not found, or it contains more than one commodity.\n\
- Lastly, the DRAW ACCOUNT is optional; if not present, the last account\n\
drawn from by PAYEE is used, or the 'basket' account (specified with\n\
'A ACCOUNT' in your Ledger file) if that does not apply, or the generic\n\
account 'Equity' is used.\n\n\
Here are a few examples, all of which may be equivalent depending on your\n\
Ledger data:\n\
ledger entry 3/25 chevron\n\
ledger entry 3/25 chevron 20\n\
ledger entry 3/25 chevron \\$20\n\
ledger entry 3/25 chevron gas 20\n\
ledger entry 3/25 chevron gas \\$20 checking\n\n\
A final note: Ledger never modifies your data! You are responsible for\n\
appending the output of this command to your Ledger file if you so choose."
<< std::endl;
return 1;
}
new_entry.reset(derive_new_entry(*journal, arg, args.end()));
if (! new_entry.get())
return 1;
}
// Configure the output stream // Configure the output stream
@ -235,22 +217,20 @@ appending the output of this command to your Ledger file if you so choose."
#endif #endif
std::ostream * out = &std::cout; std::ostream * out = &std::cout;
if (! report.output_file.empty()) { if (report.output_file) {
out = new ofstream(report.output_file); out = new ofstream(*report.output_file);
} }
#ifdef HAVE_UNIX_PIPES #ifdef HAVE_UNIX_PIPES
else if (! config.pager.empty()) { else if (report.pager) {
status = pipe(pfd); status = pipe(pfd);
if (status == -1) if (status == -1)
throw new error("Failed to create pipe"); throw_(std::logic_error, "Failed to create pipe");
status = fork(); status = fork();
if (status < 0) { if (status < 0) {
throw new error("Failed to fork child process"); throw_(std::logic_error, "Failed to fork child process");
} }
else if (status == 0) { // child else if (status == 0) { // child
const char *arg0;
// Duplicate pipe's reading end into stdin // Duplicate pipe's reading end into stdin
status = dup2(pfd[0], STDIN_FILENO); status = dup2(pfd[0], STDIN_FILENO);
if (status == -1) if (status == -1)
@ -265,13 +245,8 @@ appending the output of this command to your Ledger file if you so choose."
// Find command name: its the substring starting right of the // Find command name: its the substring starting right of the
// rightmost '/' character in the pager pathname. See manpage // rightmost '/' character in the pager pathname. See manpage
// for strrchr. // for strrchr.
arg0 = std::strrchr(config.pager.c_str(), '/'); execlp(report.pager->native_file_string().c_str(),
if (arg0) basename(*report.pager).c_str(), (char *)0);
arg0++;
else
arg0 = config.pager.c_str(); // No slashes in pager.
execlp(config.pager.c_str(), arg0, (char *)0);
perror("execl"); perror("execl");
exit(1); exit(1);
} }
@ -282,236 +257,188 @@ appending the output of this command to your Ledger file if you so choose."
} }
#endif #endif
// Are we handling the parse or expr commands? Do so now. report.define("ostream", value_t(out));
if (command == "expr") { // Are we handling the expr commands? Do so now.
value_expr expr(ledger::parse_value_expr(*arg));
if (config.verbose_mode) { xml::xpath_t::context_scope_t doc_scope(report, &xml_document);
std::cout << "Value expression tree:" << std::endl;
ledger::dump_value_expr(std::cout, expr.get()); if (verb == "expr") {
std::cout << std::endl; xml::xpath_t expr(*arg);
std::cout << "Value expression parsed was:" << std::endl;
ledger::print_value_expr(std::cout, expr.get()); IF_INFO() {
std::cout << std::endl << std::endl; *out << "Value expression tree:" << std::endl;
std::cout << "Result of computation: "; expr.dump(*out);
*out << std::endl;
*out << "Value expression parsed was:" << std::endl;
expr.print(*out, doc_scope);
*out << std::endl << std::endl;
*out << "Result of calculation: ";
} }
value_t result = guarded_compute(expr.get()); *out << expr.calc(doc_scope).strip_annotations() << std::endl;
std::cout << result.strip_annotations() << std::endl;
return 0; return 0;
} }
else if (verb == "xpath") {
std::cout << "XPath parsed: ";
// Compile the format strings xml::xpath_t xpath(*arg);
xpath.print(*out, doc_scope);
*out << std::endl;
const string * format; IF_INFO() {
*out << "Raw results:" << std::endl;
if (! report.format_string.empty()) foreach (const value_t& value, xpath.find_all(doc_scope)) {
format = &report.format_string; if (value.is_xml_node())
else if (command == "b") value.as_xml_node()->print(std::cout);
format = &config.balance_format;
else if (command == "r")
format = &config.register_format;
else if (command == "E")
format = &config.equity_format;
else if (command == "P")
format = &config.prices_format;
else if (command == "D")
format = &config.pricesdb_format;
else if (command == "w")
format = &config.write_xact_format;
else else
format = &config.print_format; std::cout << value;
std::cout << std::endl;
}
// Walk the entries based on the report type and the options *out << "Compiled results:" << std::endl;
}
item_handler<transaction_t> * formatter; xml::compile_node(xml_document, report);
std::list<item_handler<transaction_t> *> formatter_ptrs;
if (command == "b" || command == "E") foreach (const value_t& value, xpath.find_all(doc_scope)) {
formatter = new set_account_value; if (value.is_xml_node())
else if (command == "p" || command == "e") value.as_xml_node()->print(std::cout);
formatter = new format_entries(*out, *format);
else if (command == "x")
formatter = new format_emacs_transactions(*out);
else if (command == "X")
formatter = new format_xml_entries(*out, report.show_totals);
else if (command == "c")
formatter = new format_csv_transactions(*out);
else else
formatter = new format_transactions(*out, *format); std::cout << value;
std::cout << std::endl;
if (command == "w") {
#if 0
TRACE_PUSH(text_writer, "Writing journal file");
#endif
write_textual_journal(*journal, first_arg, *formatter,
config.write_hdr_format, *out);
#if 0
TRACE_POP(text_writer, "Finished writing");
#endif
} }
else if (command == "W") { return 0;
#if 0
TRACE_PUSH(binary_writer, "Writing binary file");
#endif
ofstream stream(report.output_file);
binary::write_journal(stream, journal.get());
#if 0
TRACE_POP(binary_writer, "Finished writing");
#endif
}
else {
#if 0
TRACE_PUSH(main, "Walking journal entries");
#endif
formatter = report.chain_xact_handlers(command, formatter, journal.get(),
journal->master, formatter_ptrs);
if (command == "e")
walk_transactions(new_entry->transactions, *formatter);
else if (command == "P" || command == "D")
walk_commodities(amount_t::current_pool->commodities, *formatter);
else
walk_entries(journal->entries, *formatter);
if (command != "P" && command != "D")
formatter->flush();
#if 0
TRACE_POP(main, "Finished entry walk");
#endif
} }
// For the balance and equity reports, output the sum totals. // Apply transforms to the hierarchical document structure
if (command == "b") { INFO_START(transforms, "Applied transforms");
#if 0 report.apply_transforms(doc_scope);
TRACE_PUSH(main, "Walking journal accounts"); INFO_FINISH(transforms);
#endif
format_account acct_formatter(*out, *format, report.display_predicate); // Create an argument scope containing the report command's
sum_accounts(*journal->master); // arguments, and then invoke the command.
walk_accounts(*journal->master, acct_formatter, report.sort_string);
acct_formatter.flush();
if (account_has_xdata(*journal->master)) { xml::xpath_t::call_scope_t command_args(doc_scope);
account_xdata_t& xdata = account_xdata(*journal->master);
if (! report.show_collapsed && xdata.total) {
*out << "--------------------\n";
xdata.value = xdata.total;
acct_formatter.format.format(*out, details_t(*journal->master));
}
}
#if 0
TRACE_POP(main, "Finished account walk");
#endif
}
else if (command == "E") {
#if 0
TRACE_PUSH(main, "Walking journal accounts");
#endif
format_equity acct_formatter(*out, *format, report.display_predicate); for (strings_list::iterator i = arg; i != args.end(); i++)
sum_accounts(*journal->master); command_args.push_back(value_t(*i, true));
walk_accounts(*journal->master, acct_formatter, report.sort_string);
acct_formatter.flush();
#if 0 INFO_START(command, "Did user command '" << verb << "'");
TRACE_POP(main, "Finished account walk");
#endif
}
#if DEBUG_LEVEL >= BETA command(command_args);
#if 0
{ TRACE_PUSH(cleanup, "Cleaning up allocated memory");
#endif
clear_transaction_xdata xact_cleaner; INFO_FINISH(command);
walk_entries(journal->entries, xact_cleaner);
clear_account_xdata acct_cleaner;
walk_accounts(*journal->master, acct_cleaner);
if (! report.output_file.empty())
delete out;
for (std::list<item_handler<transaction_t> *>::iterator i
= formatter_ptrs.begin();
i != formatter_ptrs.end();
i++)
delete *i;
#if 0
TRACE_POP(cleanup, "Finished cleaning"); }
#endif
#endif
// Write out the binary cache, if need be // Write out the binary cache, if need be
if (config.use_cache && config.cache_dirty && if (session.use_cache && session.cache_dirty && session.cache_file) {
! config.cache_file.empty()) { TRACE_START(binary_cache, 1, "Wrote binary journal file");
#if 0
TRACE_PUSH(binary_cache, "Writing journal file");
#endif
ofstream stream(config.cache_file);
binary::write_journal(stream, journal.get());
#if 0 #if 0
TRACE_POP(binary_cache, "Finished writing"); ofstream stream(*session.cache_file);
write_binary_journal(stream, journal);
#endif #endif
TRACE_FINISH(binary_cache, 1);
} }
// If the user specified a pager, wait for it to exit now
#ifdef HAVE_UNIX_PIPES #ifdef HAVE_UNIX_PIPES
if (! config.pager.empty()) { if (! report.output_file && report.pager) {
delete out; checked_delete(out);
close(pfd[1]); close(pfd[1]);
// Wait for child to finish // Wait for child to finish
wait(&status); wait(&status);
if (status & 0xffff != 0) if (status & 0xffff != 0)
throw new error("Something went wrong in the pager"); throw_(std::logic_error, "Something went wrong in the pager");
} }
#endif #endif
else if (DO_VERIFY() && report.output_file) {
checked_delete(out);
}
return 0; return 0;
} }
int main(int argc, char * argv[], char * envp[]) int main(int argc, char * argv[], char * envp[])
{ {
// This variable must be defined here so that any memory it holds is still int status = 1;
// available should the subsequent exception handlers catch an inner
// exception and need to report something on the invalid state of the for (int i = 1; i < argc; i++)
// journal (such as an unbalanced entry). if (argv[i][0] == '-') {
std::auto_ptr<journal_t> journal; if (std::strcmp(argv[i], "--verify") == 0) {
#if defined(VERIFY_ON)
ledger::verify_enabled = true;
#endif
}
else if (std::strcmp(argv[i], "--verbose") == 0 ||
std::strcmp(argv[i], "-v") == 0) {
#if defined(LOGGING_ON)
ledger::_log_level = ledger::LOG_INFO;
#endif
}
else if (i + 1 < argc && std::strcmp(argv[i], "--debug") == 0) {
#if defined(DEBUG_ON)
ledger::_log_level = ledger::LOG_DEBUG;
ledger::_log_category = argv[i + 1];
i++;
#endif
}
else if (i + 1 < argc && std::strcmp(argv[i], "--trace") == 0) {
#if defined(TRACING_ON)
ledger::_log_level = ledger::LOG_TRACE;
ledger::_trace_level = boost::lexical_cast<int>(argv[i + 1]);
i++;
#endif
}
}
IF_VERIFY()
ledger::initialize_memory_tracing();
try { try {
#if DEBUG_LEVEL < BETA std::ios::sync_with_stdio(false);
ledger::do_cleanup = false;
#endif
config_t config;
report_t report;
ledger::config = &config;
ledger::report = &report;
amount_t::initialize(); boost::filesystem::path::default_name_check
value_t::initialize(); (boost::filesystem::portable_posix_name);
INFO("Ledger starting");
std::auto_ptr<ledger::session_t> session(new ledger::session_t);
ledger::set_session_context(session.get());
#if 0 #if 0
TRACE_PUSH(main, "Ledger starting"); session->register_parser(new binary_parser_t);
#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
session->register_parser(new xml::xml_parser_t);
session->register_parser(new gnucash_parser_t);
#endif #endif
int status = parse_and_report(config, journal, report, argc, argv, envp); #ifdef HAVE_LIBOFX
session->register_parser(new ofx_parser_t);
value_t::shutdown();
amount_t::shutdown();
#if 0
TRACE_POP(main, "Ledger done");
#endif #endif
return status; session->register_parser(new qif_parser_t);
#endif
session->register_parser(new ledger::textual_parser_t);
std::auto_ptr<ledger::report_t> report(new ledger::report_t(*session.get()));
status = read_and_report(*report.get(), argc, argv, envp);
if (DO_VERIFY()) {
ledger::set_session_context();
} else {
report.release();
session.release();
} }
}
#if 0
catch (error * err) { catch (error * err) {
std::cout.flush(); std::cout.flush();
// Push a null here since there's no file context // Push a null here since there's no file context
@ -520,8 +447,7 @@ int main(int argc, char * argv[], char * envp[])
err->context.push_front(new error_context("")); err->context.push_front(new error_context(""));
err->reveal_context(std::cerr, "Error"); err->reveal_context(std::cerr, "Error");
std::cerr << err->what() << std::endl; std::cerr << err->what() << std::endl;
delete err; checked_delete(err);
return 1;
} }
catch (fatal * err) { catch (fatal * err) {
std::cout.flush(); std::cout.flush();
@ -531,17 +457,25 @@ int main(int argc, char * argv[], char * envp[])
err->context.push_front(new error_context("")); err->context.push_front(new error_context(""));
err->reveal_context(std::cerr, "Fatal"); err->reveal_context(std::cerr, "Fatal");
std::cerr << err->what() << std::endl; std::cerr << err->what() << std::endl;
delete err; checked_delete(err);
return 1;
} }
#endif
catch (const std::exception& err) { catch (const std::exception& err) {
std::cout.flush(); std::cout.flush();
std::cerr << "Error: " << err.what() << std::endl; std::cerr << "Error: " << err.what() << std::endl;
return 1;
} }
catch (int status) { catch (int _status) {
status = _status;
}
IF_VERIFY() {
INFO("Ledger ended (Boost/libstdc++ may still hold memory)");
ledger::shutdown_memory_tracing();
} else {
INFO("Ledger ended");
}
return status; return status;
}
} }
// main.cc ends here. // main.cc ends here.

1133
option.cc

File diff suppressed because it is too large Load diff

View file

@ -1,45 +1,53 @@
/*
* Copyright (c) 2003-2007, 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 _OPTION_H #ifndef _OPTION_H
#define _OPTION_H #define _OPTION_H
#include "utils.h" #include "valexpr.h"
namespace ledger { namespace ledger {
typedef void (*handler_t)(const char * arg); void process_option(const string& name, expr::scope_t& scope,
const char * arg = NULL);
struct option_t { void process_environment(const char ** envp, const string& tag,
const char * long_opt; expr::scope_t& scope);
char short_opt;
bool wants_arg; void process_arguments(int argc, char ** argv, const bool anywhere,
handler_t handler; expr::scope_t& scope,
bool handled; std::list<string>& args);
};
DECLARE_EXCEPTION(error, option_error); DECLARE_EXCEPTION(error, option_error);
bool process_option(option_t * options, const string& opt,
const char * arg = NULL);
void process_arguments(option_t * options, int argc, char ** argv,
const bool anywhere, std::list<string>& args);
void process_environment(option_t * options, const char ** envp,
const string& tag);
class config_t;
class report_t;
extern config_t * config;
extern report_t * report;
#define CONFIG_OPTIONS_SIZE 97
extern option_t config_options[CONFIG_OPTIONS_SIZE];
void option_help(std::ostream& out);
#define OPT_BEGIN(tag, chars) \
void opt_ ## tag(const char * optarg)
#define OPT_END(tag)
} // namespace ledger } // namespace ledger
#endif // _OPTION_H #endif // _OPTION_H

View file

@ -127,7 +127,7 @@ unsigned int parse_ledger_data(config_t& config,
if (boost::filesystem::exists(config.cache_file)) { if (boost::filesystem::exists(config.cache_file)) {
boost::filesystem::ifstream stream(config.cache_file); boost::filesystem::ifstream stream(config.cache_file);
if (cache_parser && cache_parser->test(stream)) { if (cache_parser && cache_parser->test(stream)) {
path price_db_orig = journal->price_db; optional<path> price_db_orig = journal->price_db;
journal->price_db = config.price_db; journal->price_db = config.price_db;
entry_count += cache_parser->parse(stream, config, journal, entry_count += cache_parser->parse(stream, config, journal,
NULL, &config.data_file); NULL, &config.data_file);
@ -145,13 +145,13 @@ unsigned int parse_ledger_data(config_t& config,
acct = journal->find_account(config.account); acct = journal->find_account(config.account);
journal->price_db = config.price_db; journal->price_db = config.price_db;
if (! journal->price_db.empty() && if (journal->price_db &&
boost::filesystem::exists(journal->price_db)) { boost::filesystem::exists(*journal->price_db)) {
if (parse_journal_file(journal->price_db, config, journal)) { if (parse_journal_file(*journal->price_db, config, journal)) {
throw new error("Entries not allowed in price history file"); throw new error("Entries not allowed in price history file");
} else { } else {
DEBUG("ledger.config.cache", DEBUG("ledger.config.cache",
"read price database " << journal->price_db); "read price database " << *journal->price_db);
journal->sources.pop_back(); journal->sources.pop_back();
} }
} }
@ -172,8 +172,8 @@ unsigned int parse_ledger_data(config_t& config,
else if (boost::filesystem::exists(config.data_file)) { else if (boost::filesystem::exists(config.data_file)) {
entry_count += parse_journal_file(config.data_file, config, journal, entry_count += parse_journal_file(config.data_file, config, journal,
acct); acct);
if (! journal->price_db.empty()) if (journal->price_db)
journal->sources.push_back(journal->price_db); journal->sources.push_back(*journal->price_db);
} }
} }

575
report.cc
View file

@ -1,413 +1,232 @@
/*
* Copyright (c) 2003-2007, 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 "report.h" #include "report.h"
namespace ledger { namespace ledger {
report_t::report_t() report_t::~report_t()
{ {
ledger::amount_expr = "@a"; TRACE_DTOR(report_t);
ledger::total_expr = "@O";
predicate = "";
secondary_predicate = "";
display_predicate = "";
descend_expr = "";
budget_flags = BUDGET_NO_BUDGET;
head_entries = 0;
tail_entries = 0;
show_collapsed = false;
show_subtotal = false;
show_totals = false;
show_related = false;
show_all_related = false;
show_inverted = false;
show_empty = false;
days_of_the_week = false;
by_payee = false;
comm_as_payee = false;
code_as_payee = false;
show_revalued = false;
show_revalued_only = false;
keep_price = false;
keep_date = false;
keep_tag = false;
entry_sort = false;
sort_all = false;
} }
void void report_t::apply_transforms(expr::scope_t& scope)
report_t::regexps_to_predicate(const string& command,
std::list<string>::const_iterator begin,
std::list<string>::const_iterator end,
const bool account_regexp,
const bool add_account_short_masks,
const bool logical_and)
{ {
string regexps[2]; #if 0
typedef tuple<shared_ptr<transform_t>, value_t> transform_details_tuple;
assert(begin != end); foreach (transform_details_tuple& transform_details, transforms) {
expr::call_scope_t call_args(scope);
// Treat the remaining command-line arguments as regular call_args.set_args(transform_details.get<1>());
// expressions, used for refining report results. (*transform_details.get<0>())(call_args);
for (std::list<string>::const_iterator i = begin;
i != end;
i++)
if ((*i)[0] == '-') {
if (! regexps[1].empty())
regexps[1] += "|";
regexps[1] += (*i).substr(1);
}
else if ((*i)[0] == '+') {
if (! regexps[0].empty())
regexps[0] += "|";
regexps[0] += (*i).substr(1);
}
else {
if (! regexps[0].empty())
regexps[0] += "|";
regexps[0] += *i;
}
for (int i = 0; i < 2; i++) {
if (regexps[i].empty())
continue;
if (! predicate.empty())
predicate += logical_and ? "&" : "|";
int add_predicate = 0; // 1 adds /.../, 2 adds ///.../
if (i == 1) {
predicate += "!";
}
else if (add_account_short_masks) {
if (regexps[i].find(':') != string::npos ||
regexps[i].find('.') != string::npos ||
regexps[i].find('*') != string::npos ||
regexps[i].find('+') != string::npos ||
regexps[i].find('[') != string::npos ||
regexps[i].find('(') != string::npos) {
show_subtotal = true;
add_predicate = 1;
} else {
add_predicate = 2;
}
}
else {
add_predicate = 1;
}
if (i != 1 && command == "b" && account_regexp) {
if (! show_related && ! show_all_related) {
if (! display_predicate.empty())
display_predicate += "&";
if (! show_empty)
display_predicate += "T&";
if (add_predicate == 2)
display_predicate += "//";
display_predicate += "/(?:";
display_predicate += regexps[i];
display_predicate += ")/";
}
else if (! show_empty) {
if (! display_predicate.empty())
display_predicate += "&";
display_predicate += "T";
}
}
if (! account_regexp)
predicate += "/";
predicate += "/(?:";
predicate += regexps[i];
predicate += ")/";
} }
#endif
} }
void report_t::process_options(const string& command, value_t report_t::abbrev(expr::call_scope_t& args)
strings_list::iterator arg,
strings_list::iterator args_end)
{ {
// Configure some other options depending on report type if (args.size() < 2)
throw_(std::logic_error, "usage: abbrev(STRING, WIDTH [, STYLE, ABBREV_LEN])");
if (command == "p" || command == "e" || command == "w") { string str = args[0].as_string();
show_related = long wid = args[1];
show_all_related = true;
#if 0
elision_style_t style = session.elision_style;
if (args.size() == 3)
style = static_cast<elision_style_t>(args[2].as_long());
#endif
long abbrev_len = session.abbrev_length;
if (args.size() == 4)
abbrev_len = args[3].as_long();
#if 0
return value_t(abbreviate(str, wid, style, true,
static_cast<int>(abbrev_len)), true);
#else
return value_t();
#endif
}
value_t report_t::ftime(expr::call_scope_t& args)
{
if (args.size() < 1)
throw_(std::logic_error, "usage: ftime(DATE [, DATE_FORMAT])");
datetime_t date = args[0].as_datetime();
string date_format;
if (args.size() == 2)
date_format = args[1].as_string();
#if 0
// jww (2007-04-18): Need to setup an output facet here
else
date_format = moment_t::output_format;
return value_t(date.as_string(date_format), true);
#else
return NULL_VALUE;
#endif
}
#if 0
optional<value_t>
report_t::resolve(const string& name, expr::call_scope_t& args)
{
const char * p = name.c_str();
switch (*p) {
case 'a':
if (name == "abbrev") {
return abbrev(args);
} }
else if (command == "E") {
show_subtotal = true;
}
else if (show_related) {
if (command == "r") {
show_inverted = true;
} else {
show_subtotal = true;
show_all_related = true;
}
}
if (command != "b" && command != "r")
amount_t::keep_base = true;
// Process remaining command-line arguments
if (command != "e") {
// Treat the remaining command-line arguments as regular
// expressions, used for refining report results.
std::list<string>::iterator i = arg;
for (; i != args_end; i++)
if (*i == "--")
break; break;
if (i != arg) case 'f':
regexps_to_predicate(command, arg, i, true, if (name == "ftime") {
(command == "b" && ! show_subtotal && return ftime(args);
display_predicate.empty()));
if (i != args_end && ++i != args_end)
regexps_to_predicate(command, i, args_end);
} }
break;
// Setup the default value for the display predicate
if (display_predicate.empty()) {
if (command == "b") {
if (! show_empty)
display_predicate = "T";
if (! show_subtotal) {
if (! display_predicate.empty())
display_predicate += "&";
display_predicate += "l<=1";
} }
} return expr::scope_t::resolve(name, args);
else if (command == "E") {
display_predicate = "t";
}
else if (command == "r" && ! show_empty) {
display_predicate = "a";
}
}
DEBUG("ledger.config.predicates", "Predicate: " << predicate);
DEBUG("ledger.config.predicates", "Display P: " << display_predicate);
// Setup the values of %t and %T, used in format strings
if (! amount_expr.empty())
ledger::amount_expr = amount_expr;
if (! total_expr.empty())
ledger::total_expr = total_expr;
// Now setup the various formatting strings
if (! date_output_format.empty())
output_time_format = date_output_format;
amount_t::keep_price = keep_price;
amount_t::keep_date = keep_date;
amount_t::keep_tag = keep_tag;
if (! report_period.empty() && ! sort_all)
entry_sort = true;
} }
#endif
item_handler<transaction_t> * expr::ptr_op_t report_t::lookup(const string& name)
report_t::chain_xact_handlers(const string& command,
item_handler<transaction_t> * base_formatter,
journal_t * journal,
account_t * master,
std::list<item_handler<transaction_t> *>& ptrs)
{ {
bool remember_components = false; const char * p = name.c_str();
switch (*p) {
item_handler<transaction_t> * formatter = NULL; case 'o':
if (std::strncmp(p, "option_", 7) == 0) {
ptrs.push_back(formatter = base_formatter); p = p + 7;
switch (*p) {
// format_transactions write each transaction received to the case 'a':
// output stream. #if 0
if (! (command == "b" || command == "E")) { if (std::strcmp(p, "accounts") == 0)
// truncate_entries cuts off a certain number of _entries_ from return MAKE_FUNCTOR(report_t::option_accounts);
// being displayed. It does not affect calculation.
if (head_entries || tail_entries)
ptrs.push_back(formatter =
new truncate_entries(formatter,
head_entries, tail_entries));
// filter_transactions will only pass through transactions
// matching the `display_predicate'.
if (! display_predicate.empty())
ptrs.push_back(formatter =
new filter_transactions(formatter,
display_predicate));
// calc_transactions computes the running total. When this
// appears will determine, for example, whether filtered
// transactions are included or excluded from the running total.
ptrs.push_back(formatter = new calc_transactions(formatter));
// component_transactions looks for reported transaction that
// match the given `descend_expr', and then reports the
// transactions which made up the total for that reported
// transaction.
if (! descend_expr.empty()) {
std::list<string> descend_exprs;
string::size_type beg = 0;
for (string::size_type pos = descend_expr.find(';');
pos != string::npos;
beg = pos + 1, pos = descend_expr.find(';', beg))
descend_exprs.push_back(string(descend_expr, beg, pos - beg));
descend_exprs.push_back(string(descend_expr, beg));
for (std::list<string>::reverse_iterator i =
descend_exprs.rbegin();
i != descend_exprs.rend();
i++)
ptrs.push_back(formatter =
new component_transactions(formatter, *i));
remember_components = true;
}
// reconcile_transactions will pass through only those
// transactions which can be reconciled to a given balance
// (calculated against the transactions which it receives).
if (! reconcile_balance.empty()) {
datetime_t cutoff = current_moment;
if (! reconcile_date.empty())
cutoff = parse_datetime(reconcile_date);
ptrs.push_back(formatter =
new reconcile_transactions
(formatter, value_t(reconcile_balance), cutoff));
}
// filter_transactions will only pass through transactions
// matching the `secondary_predicate'.
if (! secondary_predicate.empty())
ptrs.push_back(formatter =
new filter_transactions(formatter,
secondary_predicate));
// sort_transactions will sort all the transactions it sees, based
// on the `sort_order' value expression.
if (! sort_string.empty()) {
if (entry_sort)
ptrs.push_back(formatter =
new sort_entries(formatter, sort_string));
else else
ptrs.push_back(formatter = #endif
new sort_transactions(formatter, sort_string)); if (std::strcmp(p, "amount") == 0)
} return MAKE_FUNCTOR(report_t::option_amount);
break;
// changed_value_transactions adds virtual transactions to the case 'b':
// list to account for changes in market value of commodities, if (std::strcmp(p, "bar") == 0)
// which otherwise would affect the running total unpredictably. return MAKE_FUNCTOR(report_t::option_bar);
if (show_revalued) break;
ptrs.push_back(formatter =
new changed_value_transactions(formatter,
show_revalued_only));
// collapse_transactions causes entries with multiple transactions #if 0
// to appear as entries with a subtotaled transaction for each case 'c':
// commodity used. if (std::strcmp(p, "clean") == 0)
if (show_collapsed) return MAKE_FUNCTOR(report_t::option_clean);
ptrs.push_back(formatter = new collapse_transactions(formatter)); else if (std::strcmp(p, "compact") == 0)
return MAKE_FUNCTOR(report_t::option_compact);
break;
#endif
// subtotal_transactions combines all the transactions it receives case 'e':
// into one subtotal entry, which has one transaction for each #if 0
// commodity in each account. if (std::strcmp(p, "entries") == 0)
// return MAKE_FUNCTOR(report_t::option_entries);
// period_transactions is like subtotal_transactions, but it else if (std::strcmp(p, "eval") == 0)
// subtotals according to time periods rather than totalling return MAKE_FUNCTOR(report_t::option_eval);
// everything. else if (std::strcmp(p, "exclude") == 0)
// return MAKE_FUNCTOR(report_t::option_remove);
// dow_transactions is like period_transactions, except that it #endif
// reports all the transactions that fall on each subsequent day break;
// of the week.
if (show_subtotal)
ptrs.push_back(formatter =
new subtotal_transactions(formatter, remember_components));
if (days_of_the_week) case 'f':
ptrs.push_back(formatter = #if 0
new dow_transactions(formatter, remember_components)); if (std::strcmp(p, "foo") == 0)
else if (by_payee) return MAKE_FUNCTOR(report_t::option_foo);
ptrs.push_back(formatter = else
new by_payee_transactions(formatter, remember_components)); #endif
if (std::strcmp(p, "format") == 0)
return MAKE_FUNCTOR(report_t::option_format);
break;
// interval_transactions groups transactions together based on a case 'i':
// time period, such as weekly or monthly. #if 0
if (! report_period.empty()) { if (std::strcmp(p, "include") == 0)
ptrs.push_back(formatter = return MAKE_FUNCTOR(report_t::option_select);
new interval_transactions(formatter, report_period, #endif
remember_components)); break;
ptrs.push_back(formatter = new sort_transactions(formatter, "d"));
case 'l':
#if 0
if (! *(p + 1) || std::strcmp(p, "limit") == 0)
return MAKE_FUNCTOR(report_t::option_limit);
#endif
break;
#if 0
case 'm':
if (std::strcmp(p, "merge") == 0)
return MAKE_FUNCTOR(report_t::option_merge);
break;
#endif
case 'r':
#if 0
if (std::strcmp(p, "remove") == 0)
return MAKE_FUNCTOR(report_t::option_remove);
#endif
break;
#if 0
case 's':
if (std::strcmp(p, "select") == 0)
return MAKE_FUNCTOR(report_t::option_select);
else if (std::strcmp(p, "split") == 0)
return MAKE_FUNCTOR(report_t::option_split);
break;
#endif
case 't':
if (! *(p + 1))
return MAKE_FUNCTOR(report_t::option_amount);
else if (std::strcmp(p, "total") == 0)
return MAKE_FUNCTOR(report_t::option_total);
break;
case 'T':
if (! *(p + 1))
return MAKE_FUNCTOR(report_t::option_total);
break;
} }
} }
break;
// invert_transactions inverts the value of the transactions it
// receives.
if (show_inverted)
ptrs.push_back(formatter = new invert_transactions(formatter));
// related_transactions will pass along all transactions related
// to the transaction received. If `show_all_related' is true,
// then all the entry's transactions are passed; meaning that if
// one transaction of an entry is to be printed, all the
// transaction for that entry will be printed.
if (show_related)
ptrs.push_back(formatter =
new related_transactions(formatter,
show_all_related));
// This filter_transactions will only pass through transactions
// matching the `predicate'.
if (! predicate.empty())
ptrs.push_back(formatter = new filter_transactions(formatter, predicate));
// budget_transactions takes a set of transactions from a data
// file and uses them to generate "budget transactions" which
// balance against the reported transactions.
//
// forecast_transactions is a lot like budget_transactions, except
// that it adds entries only for the future, and does not balance
// them against anything but the future balance.
if (budget_flags) {
budget_transactions * handler
= new budget_transactions(formatter, budget_flags);
handler->add_period_entries(journal->period_entries);
ptrs.push_back(formatter = handler);
// Apply this before the budget handler, so that only matching
// transactions are calculated toward the budget. The use of
// filter_transactions above will further clean the results so
// that no automated transactions that don't match the filter get
// reported.
if (! predicate.empty())
ptrs.push_back(formatter = new filter_transactions(formatter, predicate));
}
else if (! forecast_limit.empty()) {
forecast_transactions * handler
= new forecast_transactions(formatter, forecast_limit);
handler->add_period_entries(journal->period_entries);
ptrs.push_back(formatter = handler);
// See above, under budget_transactions.
if (! predicate.empty())
ptrs.push_back(formatter = new filter_transactions(formatter, predicate));
} }
if (comm_as_payee) return expr::symbol_scope_t::lookup(name);
ptrs.push_back(formatter = new set_comm_as_payee(formatter));
else if (code_as_payee)
ptrs.push_back(formatter = new set_code_as_payee(formatter));
return formatter;
} }
} // namespace ledger } // namespace ledger

223
report.h
View file

@ -1,79 +1,194 @@
/*
* Copyright (c) 2003-2007, 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 _REPORT_H #ifndef _REPORT_H
#define _REPORT_H #define _REPORT_H
#include "ledger.h" #include "session.h"
#include <iostream>
#include <memory>
#include <list>
namespace ledger { namespace ledger {
class report_t typedef std::list<string> strings_list;
{
public:
path output_file;
string predicate; class report_t : public expr::symbol_scope_t
string secondary_predicate; {
string display_predicate; public:
string report_period; optional<path> output_file;
string report_period_sort;
string format_string; string format_string;
string sort_string;
string amount_expr; string amount_expr;
string total_expr; string total_expr;
string descend_expr;
string forecast_limit;
string reconcile_balance;
string reconcile_date;
string date_output_format; string date_output_format;
unsigned long budget_flags; unsigned long budget_flags;
int head_entries; string account;
int tail_entries; optional<path> pager;
bool show_collapsed;
bool show_subtotal;
bool show_totals; bool show_totals;
bool show_related; bool raw_mode;
bool show_all_related;
bool show_inverted;
bool show_empty;
bool days_of_the_week;
bool by_payee;
bool comm_as_payee;
bool code_as_payee;
bool show_revalued;
bool show_revalued_only;
bool keep_price;
bool keep_date;
bool keep_tag;
bool entry_sort;
bool sort_all;
report_t(); session_t& session;
#if 0
transform_t * last_transform;
void regexps_to_predicate(const string& command, std::list<tuple<shared_ptr<transform_t>, value_t> > transforms;
std::list<string>::const_iterator begin, #endif
std::list<string>::const_iterator end,
const bool account_regexp = false,
const bool add_account_short_masks = false,
const bool logical_and = true);
void process_options(const string& command, explicit report_t(session_t& _session)
strings_list::iterator arg, : expr::symbol_scope_t(downcast<expr::scope_t>(_session)),
strings_list::iterator args_end); show_totals(false),
raw_mode(false),
session(_session)
#if 0
,
last_transform(NULL)
#endif
{
TRACE_CTOR(report_t, "session_t&");
#if 0
eval("t=total,TOT=0,T()=(TOT=TOT+t,TOT)");
#endif
}
item_handler<transaction_t> * virtual ~report_t();
chain_xact_handlers(const string& command,
item_handler<transaction_t> * base_formatter, void apply_transforms(expr::scope_t& scope);
journal_t * journal,
account_t * master, //
std::list<item_handler<transaction_t> *>& ptrs); // Utility functions for value expressions
//
value_t ftime(expr::call_scope_t& args);
value_t abbrev(expr::call_scope_t& args);
//
// Config options
//
void eval(const string& expr) {
#if 0
expr(expr).compile((xml::document_t *)NULL, this);
#endif
}
value_t option_eval(expr::call_scope_t& args) {
eval(args[0].as_string());
return NULL_VALUE;
}
value_t option_amount(expr::call_scope_t& args) {
eval(string("t=") + args[0].as_string());
return NULL_VALUE;
}
value_t option_total(expr::call_scope_t& args) {
eval(string("T()=") + args[0].as_string());
return NULL_VALUE;
}
value_t option_format(expr::call_scope_t& args) {
format_string = args[0].as_string();
return NULL_VALUE;
}
value_t option_raw(expr::call_scope_t& args) {
raw_mode = true;
return NULL_VALUE;
}
value_t option_foo(expr::call_scope_t& args) {
std::cout << "This is foo" << std::endl;
return NULL_VALUE;
}
value_t option_bar(expr::call_scope_t& args) {
std::cout << "This is bar: " << args[0] << std::endl;
return NULL_VALUE;
}
//
// Transform options
//
#if 0
value_t option_select(expr::call_scope_t& args) {
transforms.push_back(new select_transform(args[0].as_string()));
return NULL_VALUE;
}
value_t option_limit(expr::call_scope_t& args) {
string expr = (string("//xact[") +
args[0].as_string() + "]");
transforms.push_back(new select_transform(expr));
return NULL_VALUE;
}
value_t option_remove(expr::call_scope_t& args) {
transforms.push_back(new remove_transform(args[0].as_string()));
return NULL_VALUE;
}
value_t option_accounts(expr::call_scope_t& args) {
transforms.push_back(new accounts_transform);
return NULL_VALUE;
}
value_t option_compact(expr::call_scope_t& args) {
transforms.push_back(new compact_transform);
return NULL_VALUE;
}
value_t option_clean(expr::call_scope_t& args) {
transforms.push_back(new clean_transform);
return NULL_VALUE;
}
value_t option_entries(expr::call_scope_t& args) {
transforms.push_back(new entries_transform);
return NULL_VALUE;
}
value_t option_split(expr::call_scope_t& args) {
transforms.push_back(new split_transform);
return NULL_VALUE;
}
value_t option_merge(expr::call_scope_t& args) {
transforms.push_back(new merge_transform);
return NULL_VALUE;
}
#endif
//
// Scope members
//
virtual expr::ptr_op_t lookup(const string& name);
}; };
string abbrev(const string& str, unsigned int width,
const bool is_account);
} // namespace ledger } // namespace ledger
#endif // _REPORT_H #endif // _REPORT_H

313
session.cc Normal file
View file

@ -0,0 +1,313 @@
/*
* Copyright (c) 2003-2007, 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 "session.h"
namespace ledger {
session_t * session_t::current = NULL;
#if 0
boost::mutex session_t::session_mutex;
#endif
static void initialize();
static void shutdown();
void set_session_context(session_t * session)
{
#if 0
session_t::session_mutex.lock();
#endif
if (session && ! session_t::current) {
initialize();
}
else if (! session && session_t::current) {
shutdown();
#if 0
session_t::session_mutex.unlock();
#endif
}
session_t::current = session;
}
void release_session_context()
{
#if 0
session_t::session_mutex.unlock();
#endif
}
session_t::session_t()
: symbol_scope_t(),
register_format
("%((//entry)%{date} %-.20{payee}"
"%((./xact)%32|%-22{abbrev(account, 22)} %12.67t %12.80T\n))"),
wide_register_format
("%D %-.35P %-.38A %22.108t %!22.132T\n%/"
"%48|%-.38A %22.108t %!22.132T\n"),
print_format
#if 1
("%(/%(/%{date} %-.20{payee}\n%(: %-34{account} %12t\n)\n))"),
#else
("\n%d %Y%C%P\n %-34W %12o%n\n%/ %-34W %12o%n\n"),
#endif
balance_format
("%(/%(//%20t %{\" \" * rdepth}%{rname}\n))--------------------\n%20t\n"),
equity_format
("%((/)%{ftime(now, date_format)} %-.20{\"Opening Balance\"}\n%((.//account[value != 0]) %-34{fullname} %12{value}\n)\n)"),
plot_amount_format
("%D %(@S(@t))\n"),
plot_total_format
("%D %(@S(@T))\n"),
write_hdr_format
("%d %Y%C%P\n"),
write_xact_format
(" %-34W %12o%n\n"),
prices_format
("%[%Y/%m/%d %H:%M:%S %Z] %-10A %12t %12T\n"),
pricesdb_format
("P %[%Y/%m/%d %H:%M:%S] %A %t\n"),
pricing_leeway(24 * 3600),
download_quotes(false),
use_cache(false),
cache_dirty(false),
now(now),
elision_style(ABBREVIATE),
abbrev_length(2),
ansi_codes(false),
ansi_invert(false)
{
TRACE_CTOR(session_t, "xml::xpath_t::scope_t&");
}
std::size_t session_t::read_journal(std::istream& in,
const path& pathname,
xml::builder_t& builder)
{
#if 0
if (! master)
master = journal->master;
#endif
foreach (parser_t& parser, parsers)
if (parser.test(in))
return parser.parse(in, pathname, builder);
return 0;
}
std::size_t session_t::read_journal(const path& pathname,
xml::builder_t& builder)
{
#if 0
journal->sources.push_back(pathname);
#endif
if (! exists(pathname))
throw_(std::logic_error, "Cannot read file" << pathname);
ifstream stream(pathname);
return read_journal(stream, pathname, builder);
}
void session_t::read_init()
{
if (! init_file)
return;
if (! exists(*init_file))
throw_(std::logic_error, "Cannot read init file" << *init_file);
ifstream init(*init_file);
// jww (2006-09-15): Read initialization options here!
}
std::size_t session_t::read_data(xml::builder_t& builder,
journal_t * journal,
const string& master_account)
{
if (data_file.empty())
throw_(parse_error, "No journal file was specified (please use -f)");
TRACE_START(parser, 1, "Parsing journal file");
std::size_t entry_count = 0;
DEBUG("ledger.cache", "3. use_cache = " << use_cache);
if (use_cache && cache_file) {
DEBUG("ledger.cache", "using_cache " << cache_file->string());
cache_dirty = true;
if (exists(*cache_file)) {
push_variable<optional<path> >
save_price_db(journal->price_db, price_db);
entry_count += read_journal(*cache_file, builder);
if (entry_count > 0)
cache_dirty = false;
}
}
if (entry_count == 0) {
account_t * acct = NULL;
if (! master_account.empty())
acct = journal->find_account(master_account);
journal->price_db = price_db;
if (journal->price_db && exists(*journal->price_db)) {
if (read_journal(*journal->price_db, builder)) {
throw_(parse_error, "Entries not allowed in price history file");
} else {
DEBUG("ledger.cache",
"read price database " << journal->price_db->string());
journal->sources.pop_back();
}
}
DEBUG("ledger.cache", "rejected cache, parsing " << data_file.string());
if (data_file == "-") {
use_cache = false;
journal->sources.push_back("<stdin>");
entry_count += read_journal(std::cin, "<stdin>", builder);
}
else if (exists(data_file)) {
entry_count += read_journal(data_file, builder);
if (journal->price_db)
journal->sources.push_back(*journal->price_db);
}
}
VERIFY(journal->valid());
TRACE_STOP(parser, 1);
return entry_count;
}
#if 0
optional<value_t>
session_t::resolve(const string& name, xml::xpath_t::scope_t& locals)
{
const char * p = name.c_str();
switch (*p) {
case 'd':
#if 0
if (name == "date_format") {
// jww (2007-04-18): What to do here?
return value_t(moment_t::output_format, true);
}
#endif
break;
case 'n':
switch (*++p) {
case 'o':
if (name == "now")
return value_t(now);
break;
}
break;
case 'r':
if (name == "register_format")
return value_t(register_format, true);
break;
}
return xml::xpath_t::scope_t::resolve(name, locals);
}
#endif
xml::xpath_t::ptr_op_t session_t::lookup(const string& name)
{
const char * p = name.c_str();
switch (*p) {
case 'o':
if (std::strncmp(p, "option_", 7) == 0) {
p = p + 7;
switch (*p) {
case 'd':
if (std::strcmp(p, "debug_") == 0)
return MAKE_FUNCTOR(session_t::option_debug_);
break;
case 'f':
if ((*(p + 1) == '_' && ! *(p + 2)) ||
std::strcmp(p, "file_") == 0)
return MAKE_FUNCTOR(session_t::option_file_);
break;
case 't':
if (std::strcmp(p, "trace_") == 0)
return MAKE_FUNCTOR(session_t::option_trace_);
break;
case 'v':
if (! *(p + 1) || std::strcmp(p, "verbose") == 0)
return MAKE_FUNCTOR(session_t::option_verbose);
else if (std::strcmp(p, "verify") == 0)
return MAKE_FUNCTOR(session_t::option_verify);
break;
}
}
break;
}
return xml::xpath_t::symbol_scope_t::lookup(name);
}
// jww (2007-04-26): All of Ledger should be accessed through a
// session_t object
static void initialize()
{
amount_t::initialize();
value_t::initialize();
xml::xpath_t::initialize();
}
static void shutdown()
{
xml::xpath_t::shutdown();
value_t::shutdown();
amount_t::shutdown();
}
} // namespace ledger

198
session.h Normal file
View file

@ -0,0 +1,198 @@
/*
* Copyright (c) 2003-2007, 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 _SESSION_H
#define _SESSION_H
#include "valexpr.h"
#include "journal.h"
#include "parser.h"
namespace ledger {
class session_t : public expr::symbol_scope_t
{
public:
static session_t * current;
path data_file;
optional<path> init_file;
optional<path> cache_file;
optional<path> price_db;
string register_format;
string wide_register_format;
string print_format;
string balance_format;
string equity_format;
string plot_amount_format;
string plot_total_format;
string write_hdr_format;
string write_xact_format;
string prices_format;
string pricesdb_format;
unsigned long pricing_leeway;
bool download_quotes;
bool use_cache;
bool cache_dirty;
datetime_t now;
#if 0
elision_style_t elision_style;
#endif
int abbrev_length;
bool ansi_codes;
bool ansi_invert;
ptr_list<journal_t> journals;
ptr_list<parser_t> parsers;
session_t();
virtual ~session_t() {
TRACE_DTOR(session_t);
}
journal_t * create_journal() {
journal_t * journal = new journal_t;
journals.push_back(journal);
return journal;
}
void close_journal(journal_t * journal) {
for (ptr_list<journal_t>::iterator i = journals.begin();
i != journals.end();
i++)
if (&*i == journal) {
journals.erase(i);
return;
}
assert(false);
checked_delete(journal);
}
#if 0
std::size_t read_journal(std::istream& in,
const path& pathname,
xml::builder_t& builder);
std::size_t read_journal(const path& pathname,
xml::builder_t& builder);
void read_init();
std::size_t read_data(xml::builder_t& builder,
journal_t * journal,
const string& master_account = "");
#endif
void register_parser(parser_t * parser) {
parsers.push_back(parser);
}
void unregister_parser(parser_t * parser) {
for (ptr_list<parser_t>::iterator i = parsers.begin();
i != parsers.end();
i++)
if (&*i == parser) {
parsers.erase(i);
return;
}
assert(false);
checked_delete(parser);
}
//
// Scope members
//
virtual expr::ptr_op_t lookup(const string& name);
//
// Debug options
//
value_t option_trace_(expr::scope_t& locals) {
return NULL_VALUE;
}
value_t option_debug_(expr::scope_t& locals) {
return NULL_VALUE;
}
value_t option_verify(expr::scope_t&) {
return NULL_VALUE;
}
value_t option_verbose(expr::scope_t&) {
#if defined(LOGGING_ON)
if (_log_level < LOG_INFO)
_log_level = LOG_INFO;
#endif
return NULL_VALUE;
}
//
// Option handlers
//
value_t option_file_(expr::call_scope_t& args) {
assert(args.size() == 1);
data_file = args[0].as_string();
return NULL_VALUE;
}
#if 0
#if defined(USE_BOOST_PYTHON)
value_t option_import_(expr::call_scope_t& args) {
python_import(optarg);
return NULL_VALUE;
}
value_t option_import_stdin(expr::call_scope_t& args) {
python_eval(std::cin, PY_EVAL_MULTI);
return NULL_VALUE;
}
#endif
#endif
};
/**
* This sets the current session context, transferring all static
* globals to point at the data structures related to this session.
* Although Ledger itself is not thread-safe, by locking, switching
* session context, then unlocking after the operation is done,
* multiple threads can sequentially make use of the library. Thus, a
* session_t maintains all of the information relating to a single
* usage of the Ledger library.
*/
void set_session_context(session_t * session = NULL);
} // namespace ledger
#endif // _SESSION_H

View file

@ -64,7 +64,7 @@ static value_expr parse_amount_expr(std::istream& in, amount_t& amount,
throw new parse_error("Amount expression failed to compute"); throw new parse_error("Amount expression failed to compute");
#if 0 #if 0
if (expr->kind == value_expr_t::CONSTANT) { if (expr->kind == expr::node_t::CONSTANT) {
expr = NULL; expr = NULL;
} else { } else {
DEBUG_IF("ledger.textual.parse") { DEBUG_IF("ledger.textual.parse") {

File diff suppressed because it is too large Load diff

684
valexpr.h
View file

@ -13,6 +13,56 @@ class entry_t;
class transaction_t; class transaction_t;
class account_t; class account_t;
namespace expr {
#if 0
struct context_t
{
const entry_t * entry() {
return NULL;
}
const transaction_t * xact() {
return NULL;
}
const account_t * account() {
return NULL;
}
};
struct entry_context_t : public context_t
{
const entry_t * entry_;
const entry_t * entry() {
return entry_;
}
};
struct xact_context_t : public context_t
{
const transaction_t * xact_;
const entry_t * entry() {
return xact_->entry;
}
const transaction_t * xact() {
return xact_;
}
const account_t * account() {
return xact_->account;
}
};
struct account_context_t : public context_t
{
const account_t * account_;
const account_t * account() {
return account_;
}
};
#endif
struct details_t struct details_t
{ {
const entry_t * entry; const entry_t * entry;
@ -36,11 +86,217 @@ struct details_t
#endif #endif
}; };
struct value_expr_t struct op_t;
typedef intrusive_ptr<op_t> ptr_op_t;
class call_scope_t;
typedef function<value_t (call_scope_t&)> function_t;
#define MAKE_FUNCTOR(x) expr::op_t::wrap_functor(bind(&x, this, _1))
#define WRAP_FUNCTOR(x) expr::op_t::wrap_functor(x)
class scope_t : public noncopyable
{
public:
enum type_t {
CHILD_SCOPE,
SYMBOL_SCOPE,
CALL_SCOPE,
CONTEXT_SCOPE
} type_;
explicit scope_t(type_t _type) : type_(_type) {
TRACE_CTOR(expr::scope_t, "type_t");
}
virtual ~scope_t() {
TRACE_DTOR(expr::scope_t);
}
const type_t type() const {
return type_;
}
virtual void define(const string& name, ptr_op_t def) = 0;
void define(const string& name, const value_t& val);
virtual ptr_op_t lookup(const string& name) = 0;
value_t resolve(const string& name) {
#if 0
return lookup(name)->calc(*this);
#else
return value_t();
#endif
}
virtual optional<scope_t&> find_scope(const type_t _type,
bool skip_this = false) = 0;
virtual optional<scope_t&> find_first_scope(const type_t _type1,
const type_t _type2,
bool skip_this = false) = 0;
template <typename T>
T& find_scope(bool skip_this = false) {
assert(false);
}
template <typename T>
optional<T&> maybe_find_scope(bool skip_this = false) {
assert(false);
}
};
class child_scope_t : public scope_t
{
scope_t * parent;
public:
explicit child_scope_t(type_t _type = CHILD_SCOPE)
: scope_t(_type), parent(NULL) {
TRACE_CTOR(expr::child_scope_t, "type_t");
}
explicit child_scope_t(scope_t& _parent, type_t _type = CHILD_SCOPE)
: scope_t(_type), parent(&_parent) {
TRACE_CTOR(expr::child_scope_t, "scope_t&, type_t");
}
virtual ~child_scope_t() {
TRACE_DTOR(expr::child_scope_t);
}
public:
virtual void define(const string& name, ptr_op_t def) {
if (parent)
parent->define(name, def);
}
virtual ptr_op_t lookup(const string& name) {
if (parent)
return parent->lookup(name);
return ptr_op_t();
}
virtual optional<scope_t&> find_scope(type_t _type,
bool skip_this = false) {
for (scope_t * ptr = (skip_this ? parent : this); ptr; ) {
if (ptr->type() == _type)
return *ptr;
ptr = polymorphic_downcast<child_scope_t *>(ptr)->parent;
}
return none;
}
virtual optional<scope_t&> find_first_scope(const type_t _type1,
const type_t _type2,
bool skip_this = false) {
for (scope_t * ptr = (skip_this ? parent : this); ptr; ) {
if (ptr->type() == _type1 || ptr->type() == _type2)
return *ptr;
ptr = polymorphic_downcast<child_scope_t *>(ptr)->parent;
}
return none;
}
};
class symbol_scope_t : public child_scope_t
{
typedef std::map<const string, ptr_op_t> symbol_map;
symbol_map symbols;
public:
explicit symbol_scope_t()
: child_scope_t(SYMBOL_SCOPE) {
TRACE_CTOR(expr::symbol_scope_t, "");
}
explicit symbol_scope_t(scope_t& _parent)
: child_scope_t(_parent, SYMBOL_SCOPE) {
TRACE_CTOR(expr::symbol_scope_t, "scope_t&");
}
virtual ~symbol_scope_t() {
TRACE_DTOR(expr::symbol_scope_t);
}
virtual void define(const string& name, ptr_op_t def);
void define(const string& name, const value_t& val) {
scope_t::define(name, val);
}
virtual ptr_op_t lookup(const string& name);
};
class call_scope_t : public child_scope_t
{
value_t args;
public:
explicit call_scope_t(scope_t& _parent)
: child_scope_t(_parent, CALL_SCOPE) {
TRACE_CTOR(expr::call_scope_t, "scope_t&");
}
virtual ~call_scope_t() {
TRACE_DTOR(expr::call_scope_t);
}
void set_args(const value_t& _args) {
args = _args;
}
value_t& value() {
return args;
}
value_t& operator[](const int index) {
return args[index];
}
const value_t& operator[](const int index) const {
return args[index];
}
void push_back(const value_t& val) {
args.push_back(val);
}
void pop_back() {
args.pop_back();
}
const std::size_t size() const {
return args.size();
}
};
class context_scope_t : public child_scope_t
{
public:
value_t current_element;
std::size_t element_index;
std::size_t sequence_size;
explicit context_scope_t(scope_t& _parent,
const value_t& _element = NULL_VALUE,
const std::size_t _element_index = 0,
const std::size_t _sequence_size = 0)
: child_scope_t(_parent, CONTEXT_SCOPE), current_element(_element),
element_index(_element_index), sequence_size(_sequence_size)
{
TRACE_CTOR(expr::context_scope_t, "scope_t&, const value_t&, ...");
}
virtual ~context_scope_t() {
TRACE_DTOR(expr::context_scope_t);
}
const std::size_t index() const {
return element_index;
}
const std::size_t size() const {
return sequence_size;
}
value_t& value() {
return current_element;
}
};
struct op_t : public noncopyable
{ {
enum kind_t { enum kind_t {
// Constants // Constants
CONSTANT, VALUE,
ARG_INDEX, ARG_INDEX,
CONSTANTS, CONSTANTS,
@ -70,6 +326,8 @@ struct value_expr_t
TOTAL_EXPR, TOTAL_EXPR,
// Functions // Functions
FUNCTION,
F_NOW, F_NOW,
F_ARITH_MEAN, F_ARITH_MEAN,
F_QUANTITY, F_QUANTITY,
@ -123,79 +381,228 @@ struct value_expr_t
kind_t kind; kind_t kind;
mutable short refc; mutable short refc;
value_expr_t * left; ptr_op_t left_;
union { variant<unsigned int, // used by ARG_INDEX and O_ARG
value_t * value; value_t, // used by constant VALUE
mask_t * mask; mask_t, // used by constant MASK
unsigned int arg_index; // used by ARG_INDEX and O_ARG function_t, // used by terminal FUNCTION
value_expr_t * right; #if 0
}; node_t::nameid_t, // used by NODE_ID and ATTR_ID
#endif
ptr_op_t> // used by all binary operators
data;
value_expr_t(const kind_t _kind) explicit op_t(const kind_t _kind) : kind(_kind), refc(0){
: kind(_kind), refc(0), left(NULL), right(NULL) { TRACE_CTOR(expr::op_t, "const kind_t");
DEBUG("ledger.memory.ctors", "ctor value_expr_t " << this);
} }
~value_expr_t(); ~op_t() {
TRACE_DTOR(expr::op_t);
void release() const { DEBUG("ledger.xpath.memory", "Destroying " << this);
DEBUG("ledger.valexpr.memory", assert(refc == 0);
"Releasing " << this << ", refc now " << refc - 1);
assert(refc > 0);
if (--refc == 0)
delete this;
} }
value_expr_t * acquire() {
DEBUG("ledger.valexpr.memory", bool is_long() const {
return data.type() == typeid(unsigned int);
}
unsigned int& as_long() {
assert(kind == ARG_INDEX || kind == O_ARG);
return boost::get<unsigned int>(data);
}
const unsigned int& as_long() const {
return const_cast<op_t *>(this)->as_long();
}
void set_long(unsigned int val) {
data = val;
}
bool is_value() const {
if (kind == VALUE) {
assert(data.type() == typeid(value_t));
return true;
}
return false;
}
value_t& as_value() {
assert(is_value());
return boost::get<value_t>(data);
}
const value_t& as_value() const {
return const_cast<op_t *>(this)->as_value();
}
void set_value(const value_t& val) {
data = val;
}
bool is_string() const {
if (kind == VALUE) {
assert(data.type() == typeid(value_t));
return boost::get<value_t>(data).is_string();
}
return false;
}
string& as_string() {
assert(is_string());
return boost::get<value_t>(data).as_string_lval();
}
const string& as_string() const {
return const_cast<op_t *>(this)->as_string();
}
void set_string(const string& val) {
data = value_t(val);
}
bool is_function() const {
return kind == FUNCTION;
}
function_t& as_function() {
assert(kind == FUNCTION);
return boost::get<function_t>(data);
}
const function_t& as_function() const {
return const_cast<op_t *>(this)->as_function();
}
void set_function(const function_t& val) {
data = val;
}
#if 0
bool is_name() const {
return data.type() == typeid(node_t::nameid_t);
}
node_t::nameid_t& as_name() {
assert(kind == NODE_ID || kind == ATTR_ID);
return boost::get<node_t::nameid_t>(data);
}
const node_t::nameid_t& as_name() const {
return const_cast<op_t *>(this)->as_name();
}
void set_name(const node_t::nameid_t& val) {
data = val;
}
#endif
ptr_op_t& as_op() {
assert(kind > TERMINALS);
return boost::get<ptr_op_t>(data);
}
const ptr_op_t& as_op() const {
return const_cast<op_t *>(this)->as_op();
}
void acquire() const {
DEBUG("ledger.xpath.memory",
"Acquiring " << this << ", refc now " << refc + 1); "Acquiring " << this << ", refc now " << refc + 1);
assert(refc >= 0); assert(refc >= 0);
refc++; refc++;
return this;
} }
const value_expr_t * acquire() const { void release() const {
DEBUG("ledger.valexpr.memory", DEBUG("ledger.xpath.memory",
"Acquiring " << this << ", refc now " << refc + 1); "Releasing " << this << ", refc now " << refc - 1);
refc++; assert(refc > 0);
return this; if (--refc == 0)
checked_delete(this);
} }
void set_left(value_expr_t * expr) { ptr_op_t& left() {
return left_;
}
const ptr_op_t& left() const {
assert(kind > TERMINALS); assert(kind > TERMINALS);
if (left) return left_;
left->release(); }
left = expr ? expr->acquire() : NULL; void set_left(const ptr_op_t& expr) {
assert(kind > TERMINALS);
left_ = expr;
} }
void set_right(value_expr_t * expr) { ptr_op_t& right() {
assert(kind > TERMINALS); assert(kind > TERMINALS);
if (right) return as_op();
right->release();
right = expr ? expr->acquire() : NULL;
} }
const ptr_op_t& right() const {
assert(kind > TERMINALS);
return as_op();
}
void set_right(const ptr_op_t& expr) {
assert(kind > TERMINALS);
data = expr;
}
static ptr_op_t new_node(kind_t _kind, ptr_op_t _left = NULL,
ptr_op_t _right = NULL);
ptr_op_t copy(ptr_op_t _left = NULL, ptr_op_t _right = NULL) const {
return new_node(kind, _left, _right);
}
static ptr_op_t wrap_value(const value_t& val);
static ptr_op_t wrap_functor(const function_t& fobj);
ptr_op_t compile(scope_t& scope);
value_t current_value(scope_t& scope);
#if 0
node_t& current_xml_node(scope_t& scope);
#endif
value_t calc(scope_t& scope);
void compute(value_t& result, void compute(value_t& result,
const details_t& details = details_t(), const details_t& details = details_t(),
value_expr_t * context = NULL) const; ptr_op_t context = NULL) const;
value_t compute(const details_t& details = details_t(), value_t compute(const details_t& details = details_t(),
value_expr_t * context = NULL) const { ptr_op_t context = NULL) const {
value_t temp; value_t temp;
compute(temp, details, context); compute(temp, details, context);
return temp; return temp;
} }
private: struct print_context_t
value_expr_t(const value_expr_t&) { {
DEBUG("ledger.memory.ctors", "ctor value_expr_t (copy) " << this); scope_t& scope;
const bool relaxed;
const ptr_op_t& op_to_find;
unsigned long * start_pos;
unsigned long * end_pos;
print_context_t(scope_t& _scope,
const bool _relaxed = false,
const ptr_op_t& _op_to_find = ptr_op_t(),
unsigned long * _start_pos = NULL,
unsigned long * _end_pos = NULL)
: scope(_scope), relaxed(_relaxed), op_to_find(_op_to_find),
start_pos(_start_pos), end_pos(_end_pos) {}
};
bool print(std::ostream& out, print_context_t& context) const;
void dump(std::ostream& out, const int depth) const;
friend inline void intrusive_ptr_add_ref(op_t * op) {
op->acquire();
}
friend inline void intrusive_ptr_release(op_t * op) {
op->release();
} }
}; };
#if 0
class op_predicate {
ptr_op_t op;
public:
explicit op_predicate(ptr_op_t _op) : op(_op) {}
bool operator()(scope_t& scope) {
return op->calc(scope).to_boolean();
}
};
#endif
class valexpr_context : public error_context { class valexpr_context : public error_context {
public: public:
const ledger::value_expr_t * expr; ptr_op_t expr;
const ledger::value_expr_t * error_node; ptr_op_t error_node;
valexpr_context(const ledger::value_expr_t * _expr, valexpr_context(const ptr_op_t _expr,
const string& desc = "") throw(); const string& desc = "") throw();
virtual ~valexpr_context() throw(); virtual ~valexpr_context() throw();
@ -217,53 +624,6 @@ class value_expr_error : public error {
virtual ~value_expr_error() throw() {} virtual ~value_expr_error() throw() {}
}; };
struct scope_t
{
scope_t * parent;
typedef std::map<const string, value_expr_t *> symbol_map;
typedef std::pair<const string, value_expr_t *> symbol_pair;
symbol_map symbols;
scope_t(scope_t * _parent = NULL) : parent(_parent) {
DEBUG("ledger.memory.ctors", "ctor scope_t");
}
~scope_t() {
DEBUG("ledger.memory.dtors", "dtor scope_t");
for (symbol_map::iterator i = symbols.begin();
i != symbols.end();
i++)
(*i).second->release();
}
void define(const string& name, value_expr_t * def) {
DEBUG("ledger.valexpr.syms",
"Defining '" << name << "' = " << def);
std::pair<symbol_map::iterator, bool> result
= symbols.insert(symbol_pair(name, def));
if (! result.second) {
symbols.erase(name);
std::pair<symbol_map::iterator, bool> result
= symbols.insert(symbol_pair(name, def));
if (! result.second) {
def->release();
throw new compute_error(string("Redefinition of '") +
name + "' in same scope");
}
}
def->acquire();
}
value_expr_t * lookup(const string& name) {
symbol_map::const_iterator i = symbols.find(name);
if (i != symbols.end())
return (*i).second;
else if (parent)
return parent->lookup(name);
return NULL;
}
};
extern std::auto_ptr<scope_t> global_scope; extern std::auto_ptr<scope_t> global_scope;
extern datetime_t terminus; extern datetime_t terminus;
@ -271,9 +631,9 @@ extern bool initialized;
void init_value_expr(); void init_value_expr();
bool compute_amount(value_expr_t * expr, amount_t& amt, bool compute_amount(const ptr_op_t expr, amount_t& amt,
const transaction_t * xact, const transaction_t * xact,
value_expr_t * context = NULL); const ptr_op_t context = NULL);
#define PARSE_VALEXPR_NORMAL 0x00 #define PARSE_VALEXPR_NORMAL 0x00
#define PARSE_VALEXPR_PARTIAL 0x01 #define PARSE_VALEXPR_PARTIAL 0x01
@ -281,11 +641,11 @@ bool compute_amount(value_expr_t * expr, amount_t& amt,
#define PARSE_VALEXPR_NO_MIGRATE 0x04 #define PARSE_VALEXPR_NO_MIGRATE 0x04
#define PARSE_VALEXPR_NO_REDUCE 0x08 #define PARSE_VALEXPR_NO_REDUCE 0x08
value_expr_t * parse_value_expr(std::istream& in, ptr_op_t parse_value_expr(std::istream& in,
scope_t * scope = NULL, scope_t * scope = NULL,
const short flags = PARSE_VALEXPR_RELAXED); const short flags = PARSE_VALEXPR_RELAXED);
inline value_expr_t * inline ptr_op_t
parse_value_expr(const string& str, parse_value_expr(const string& str,
scope_t * scope = NULL, scope_t * scope = NULL,
const short flags = PARSE_VALEXPR_RELAXED) { const short flags = PARSE_VALEXPR_RELAXED) {
@ -301,29 +661,29 @@ parse_value_expr(const string& str,
} }
} }
inline value_expr_t * inline ptr_op_t
parse_value_expr(const char * p, parse_value_expr(const char * p,
scope_t * scope = NULL, scope_t * scope = NULL,
const short flags = PARSE_VALEXPR_RELAXED) { const short flags = PARSE_VALEXPR_RELAXED) {
return parse_value_expr(string(p), scope, flags); return parse_value_expr(string(p), scope, flags);
} }
void dump_value_expr(std::ostream& out, const value_expr_t * node, void dump_value_expr(std::ostream& out, const ptr_op_t node,
const int depth = 0); const int depth = 0);
bool print_value_expr(std::ostream& out, bool print_value_expr(std::ostream& out,
const value_expr_t * node, const ptr_op_t node,
const bool relaxed = true, const bool relaxed = true,
const value_expr_t * node_to_find = NULL, const ptr_op_t node_to_find = NULL,
unsigned long * start_pos = NULL, unsigned long * start_pos = NULL,
unsigned long * end_pos = NULL); unsigned long * end_pos = NULL);
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
inline void guarded_compute(const value_expr_t * expr, inline void guarded_compute(const ptr_op_t expr,
value_t& result, value_t& result,
const details_t& details = details_t(), const details_t& details = details_t(),
value_expr_t * context = NULL) { const ptr_op_t context = NULL) {
try { try {
expr->compute(result, details); expr->compute(result, details);
} }
@ -333,45 +693,81 @@ inline void guarded_compute(const value_expr_t * expr,
err->context.push_back(new valexpr_context(expr)); err->context.push_back(new valexpr_context(expr));
error_context * last = err->context.back(); error_context * last = err->context.back();
if (valexpr_context * ctxt = dynamic_cast<valexpr_context *>(last)) { if (valexpr_context * ctxt = dynamic_cast<valexpr_context *>(last)) {
ctxt->expr = expr->acquire(); ctxt->expr = expr;
ctxt->desc = "While computing value expression:"; ctxt->desc = "While computing value expression:";
} }
throw err; throw err;
} }
} }
inline value_t guarded_compute(const value_expr_t * expr, inline value_t guarded_compute(const ptr_op_t expr,
const details_t& details = details_t(), const details_t& details = details_t(),
value_expr_t * context = NULL) { ptr_op_t context = NULL) {
value_t temp; value_t temp;
guarded_compute(expr, temp, details, context); guarded_compute(expr, temp, details, context);
return temp; return temp;
} }
template<>
inline symbol_scope_t&
scope_t::find_scope<symbol_scope_t>(bool skip_this) {
optional<scope_t&> scope = find_scope(SYMBOL_SCOPE, skip_this);
assert(scope);
return downcast<symbol_scope_t>(*scope);
}
template<>
inline call_scope_t&
scope_t::find_scope<call_scope_t>(bool skip_this) {
optional<scope_t&> scope = find_scope(CALL_SCOPE, skip_this);
assert(scope);
return downcast<call_scope_t>(*scope);
}
template<>
inline context_scope_t&
scope_t::find_scope<context_scope_t>(bool skip_this) {
optional<scope_t&> scope = find_scope(CONTEXT_SCOPE, skip_this);
assert(scope);
return downcast<context_scope_t>(*scope);
}
#define FIND_SCOPE(scope_type, scope_ref) \
downcast<scope_t>(scope_ref).find_scope<scope_type>()
#define CALL_SCOPE(scope_ref) \
FIND_SCOPE(call_scope_t, scope_ref)
#define SYMBOL_SCOPE(scope_ref) \
FIND_SCOPE(symbol_scope_t, scope_ref)
#define CONTEXT_SCOPE(scope_ref) \
FIND_SCOPE(context_scope_t, scope_ref)
} // namespace expr
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
class value_expr class value_expr
{ {
value_expr_t * ptr; expr::ptr_op_t ptr;
public: public:
string expr; string expr;
typedef expr::details_t details_t;
value_expr() : ptr(NULL) {} value_expr() : ptr(NULL) {}
value_expr(const string& _expr) : expr(_expr) { value_expr(const string& _expr) : expr(_expr) {
DEBUG("ledger.memory.ctors", "ctor value_expr"); DEBUG("ledger.memory.ctors", "ctor value_expr");
if (! _expr.empty()) if (! _expr.empty())
ptr = parse_value_expr(expr)->acquire(); ptr = expr::parse_value_expr(expr);
else else
ptr = NULL; ptr = expr::ptr_op_t();
} }
value_expr(value_expr_t * _ptr) value_expr(const expr::ptr_op_t _ptr) : ptr(_ptr) {
: ptr(_ptr ? _ptr->acquire(): NULL) {
DEBUG("ledger.memory.ctors", "ctor value_expr"); DEBUG("ledger.memory.ctors", "ctor value_expr");
} }
value_expr(const value_expr& other) value_expr(const value_expr& other) : ptr(other.ptr), expr(other.expr) {
: ptr(other.ptr ? other.ptr->acquire() : NULL),
expr(other.expr) {
DEBUG("ledger.memory.ctors", "ctor value_expr"); DEBUG("ledger.memory.ctors", "ctor value_expr");
} }
virtual ~value_expr() { virtual ~value_expr() {
@ -382,10 +778,10 @@ public:
value_expr& operator=(const string& _expr) { value_expr& operator=(const string& _expr) {
expr = _expr; expr = _expr;
reset(parse_value_expr(expr)); reset(expr::parse_value_expr(expr));
return *this; return *this;
} }
value_expr& operator=(value_expr_t * _expr) { value_expr& operator=(expr::ptr_op_t _expr) {
expr = ""; expr = "";
reset(_expr); reset(_expr);
return *this; return *this;
@ -402,50 +798,48 @@ public:
operator string() const throw() { operator string() const throw() {
return expr; return expr;
} }
operator value_expr_t *() const throw() { operator const expr::ptr_op_t() const throw() {
return ptr; return ptr;
} }
value_expr_t& operator*() const throw() { const expr::op_t& operator*() const throw() {
return *ptr; return *ptr;
} }
value_expr_t * operator->() const throw() { const expr::ptr_op_t operator->() const throw() {
return ptr; return ptr;
} }
value_expr_t * get() const throw() { return ptr; } const expr::ptr_op_t get() const throw() { return ptr; }
value_expr_t * release() throw() { const expr::ptr_op_t release() throw() {
value_expr_t * tmp = ptr; const expr::ptr_op_t tmp = ptr;
ptr = 0; ptr = expr::ptr_op_t();
return tmp; return tmp;
} }
void reset(value_expr_t * p = 0) throw() { void reset(const expr::ptr_op_t p = expr::ptr_op_t()) throw() {
if (p != ptr) { ptr = p;
if (ptr)
ptr->release();
ptr = p ? p->acquire() : NULL;
}
} }
virtual void compute(value_t& result, virtual void compute(value_t& result,
const details_t& details = details_t(), const details_t& details = details_t(),
value_expr_t * context = NULL) { expr::ptr_op_t context = NULL) {
guarded_compute(ptr, result, details, context); guarded_compute(ptr, result, details, context);
} }
virtual value_t compute(const details_t& details = details_t(), virtual value_t compute(const details_t& details = details_t(),
value_expr_t * context = NULL) { expr::ptr_op_t context = NULL) {
value_t temp; value_t temp;
guarded_compute(ptr, temp, details, context); guarded_compute(ptr, temp, details, context);
return temp; return temp;
} }
friend bool print_value_expr(std::ostream& out, friend bool print_value_expr(std::ostream& out,
const value_expr_t * node, const expr::ptr_op_t node,
const value_expr_t * node_to_find, const expr::ptr_op_t node_to_find,
unsigned long * start_pos, unsigned long * start_pos,
unsigned long * end_pos); unsigned long * end_pos);
}; };
typedef value_expr::details_t details_t; // jww (2008-07-20): remove
extern value_expr amount_expr; extern value_expr amount_expr;
extern value_expr total_expr; extern value_expr total_expr;
@ -471,14 +865,14 @@ inline value_t compute_total(const details_t& details = details_t()) {
return total_expr->compute(details); return total_expr->compute(details);
} }
value_expr_t * parse_boolean_expr(std::istream& in, scope_t * scope, expr::ptr_op_t parse_boolean_expr(std::istream& in, expr::scope_t * scope,
const short flags); const short flags);
inline void parse_value_definition(const string& str, inline void parse_value_definition(const string& str,
scope_t * scope = NULL) { expr::scope_t * scope = NULL) {
std::istringstream def(str); std::istringstream def(str);
value_expr expr value_expr expr
(parse_boolean_expr(def, scope ? scope : global_scope.get(), (parse_boolean_expr(def, scope ? scope : expr::global_scope.get(),
PARSE_VALEXPR_RELAXED)); PARSE_VALEXPR_RELAXED));
} }
@ -487,28 +881,26 @@ inline void parse_value_definition(const string& str,
template <typename T> template <typename T>
class item_predicate class item_predicate
{ {
public: public:
const value_expr_t * predicate; value_expr predicate;
item_predicate(const string& _predicate) : predicate(NULL) { item_predicate() {
DEBUG("ledger.memory.ctors", "ctor item_predicate<T>"); TRACE_CTOR(item_predicate, "ctor item_predicate<T>()");
if (! _predicate.empty())
predicate = parse_value_expr(_predicate)->acquire();
} }
item_predicate(const value_expr_t * _predicate = NULL) item_predicate(const value_expr& _predicate) : predicate(_predicate) {
: predicate(_predicate->acquire()) { TRACE_CTOR(item_predicate, "ctor item_predicate<T>(const value_expr&)");
DEBUG("ledger.memory.ctors", "ctor item_predicate<T>"); }
item_predicate(const string& _predicate) : predicate(_predicate) {
TRACE_CTOR(item_predicate, "ctor item_predicate<T>(const string&)");
} }
~item_predicate() { ~item_predicate() {
DEBUG("ledger.memory.dtors", "dtor item_predicate<T>"); TRACE_DTOR(item_predicate);
if (predicate)
predicate->release();
} }
bool operator()(const T& item) const { bool operator()(const T& item) const {
return (! predicate || return (! predicate ||
predicate->compute(details_t(item)).strip_annotations()); predicate->compute(value_expr::details_t(item)).strip_annotations());
} }
}; };

16
walk.cc
View file

@ -16,14 +16,14 @@ bool compare_items<transaction_t>::operator()(const transaction_t * left,
transaction_xdata_t& lxdata(transaction_xdata(*left)); transaction_xdata_t& lxdata(transaction_xdata(*left));
if (! (lxdata.dflags & TRANSACTION_SORT_CALC)) { if (! (lxdata.dflags & TRANSACTION_SORT_CALC)) {
guarded_compute(sort_order, lxdata.sort_value, details_t(*left)); sort_order.compute(lxdata.sort_value, details_t(*left));
lxdata.sort_value.reduce(); lxdata.sort_value.reduce();
lxdata.dflags |= TRANSACTION_SORT_CALC; lxdata.dflags |= TRANSACTION_SORT_CALC;
} }
transaction_xdata_t& rxdata(transaction_xdata(*right)); transaction_xdata_t& rxdata(transaction_xdata(*right));
if (! (rxdata.dflags & TRANSACTION_SORT_CALC)) { if (! (rxdata.dflags & TRANSACTION_SORT_CALC)) {
guarded_compute(sort_order, rxdata.sort_value, details_t(*right)); sort_order.compute(rxdata.sort_value, details_t(*right));
rxdata.sort_value.reduce(); rxdata.sort_value.reduce();
rxdata.dflags |= TRANSACTION_SORT_CALC; rxdata.dflags |= TRANSACTION_SORT_CALC;
} }
@ -799,13 +799,13 @@ bool compare_items<account_t>::operator()(const account_t * left,
account_xdata_t& lxdata(account_xdata(*left)); account_xdata_t& lxdata(account_xdata(*left));
if (! (lxdata.dflags & ACCOUNT_SORT_CALC)) { if (! (lxdata.dflags & ACCOUNT_SORT_CALC)) {
guarded_compute(sort_order, lxdata.sort_value, details_t(*left)); sort_order.compute(lxdata.sort_value, details_t(*left));
lxdata.dflags |= ACCOUNT_SORT_CALC; lxdata.dflags |= ACCOUNT_SORT_CALC;
} }
account_xdata_t& rxdata(account_xdata(*right)); account_xdata_t& rxdata(account_xdata(*right));
if (! (rxdata.dflags & ACCOUNT_SORT_CALC)) { if (! (rxdata.dflags & ACCOUNT_SORT_CALC)) {
guarded_compute(sort_order, rxdata.sort_value, details_t(*right)); sort_order.compute(rxdata.sort_value, details_t(*right));
rxdata.dflags |= ACCOUNT_SORT_CALC; rxdata.dflags |= ACCOUNT_SORT_CALC;
} }
@ -842,7 +842,7 @@ void sum_accounts(account_t& account)
} }
void sort_accounts(account_t& account, void sort_accounts(account_t& account,
const value_expr_t * sort_order, const value_expr& sort_order,
accounts_deque& accounts) accounts_deque& accounts)
{ {
for (accounts_map::iterator i = account.accounts.begin(); for (accounts_map::iterator i = account.accounts.begin();
@ -856,13 +856,13 @@ void sort_accounts(account_t& account,
void walk_accounts(account_t& account, void walk_accounts(account_t& account,
item_handler<account_t>& handler, item_handler<account_t>& handler,
const value_expr_t * sort_order) const optional<value_expr>& sort_order)
{ {
handler(account); handler(account);
if (sort_order) { if (sort_order) {
accounts_deque accounts; accounts_deque accounts;
sort_accounts(account, sort_order, accounts); sort_accounts(account, *sort_order, accounts);
for (accounts_deque::const_iterator i = accounts.begin(); for (accounts_deque::const_iterator i = accounts.begin();
i != accounts.end(); i != accounts.end();
i++) { i++) {
@ -884,7 +884,7 @@ void walk_accounts(account_t& account,
if (! sort_string.empty()) { if (! sort_string.empty()) {
value_expr sort_order; value_expr sort_order;
sort_order.reset(parse_value_expr(sort_string)); sort_order.reset(parse_value_expr(sort_string));
walk_accounts(account, handler, sort_order.get()); walk_accounts(account, handler, sort_order);
} else { } else {
walk_accounts(account, handler); walk_accounts(account, handler);
} }

40
walk.h
View file

@ -38,12 +38,9 @@ struct item_handler {
template <typename T> template <typename T>
class compare_items { class compare_items {
const value_expr_t * sort_order; value_expr sort_order;
public: public:
compare_items(const value_expr_t * _sort_order) compare_items(value_expr _sort_order) : sort_order(_sort_order) {}
: sort_order(_sort_order) {
assert(sort_order);
}
bool operator()(const T * left, const T * right); bool operator()(const T * left, const T * right);
}; };
@ -55,8 +52,8 @@ bool compare_items<T>::operator()(const T * left, const T * right)
value_t left_result; value_t left_result;
value_t right_result; value_t right_result;
guarded_compute(sort_order, left_result, details_t(*left)); sort_order.compute(left_result, details_t(*left));
guarded_compute(sort_order, right_result, details_t(*right)); sort_order.compute(right_result, details_t(*right));
return left_result < right_result; return left_result < right_result;
} }
@ -245,25 +242,18 @@ class sort_transactions : public item_handler<transaction_t>
typedef std::deque<transaction_t *> transactions_deque; typedef std::deque<transaction_t *> transactions_deque;
transactions_deque transactions; transactions_deque transactions;
const value_expr_t * sort_order; const value_expr sort_order;
public: public:
sort_transactions(item_handler<transaction_t> * handler, sort_transactions(item_handler<transaction_t> * handler,
const value_expr_t * _sort_order) const value_expr& _sort_order)
: item_handler<transaction_t>(handler), : item_handler<transaction_t>(handler),
sort_order(_sort_order->acquire()) {} sort_order(_sort_order) {}
sort_transactions(item_handler<transaction_t> * handler, sort_transactions(item_handler<transaction_t> * handler,
const string& _sort_order) const string& _sort_order)
: item_handler<transaction_t>(handler) { : item_handler<transaction_t>(handler),
assert(! _sort_order.empty()); sort_order(_sort_order) {}
sort_order = parse_value_expr(_sort_order)->acquire();
}
virtual ~sort_transactions() {
assert(sort_order);
sort_order->release();
}
virtual void post_accumulated_xacts(); virtual void post_accumulated_xacts();
@ -284,7 +274,7 @@ class sort_entries : public item_handler<transaction_t>
public: public:
sort_entries(item_handler<transaction_t> * handler, sort_entries(item_handler<transaction_t> * handler,
const value_expr_t * _sort_order) const value_expr& _sort_order)
: sorter(handler, _sort_order) {} : sorter(handler, _sort_order) {}
sort_entries(item_handler<transaction_t> * handler, sort_entries(item_handler<transaction_t> * handler,
@ -312,7 +302,7 @@ class filter_transactions : public item_handler<transaction_t>
public: public:
filter_transactions(item_handler<transaction_t> * handler, filter_transactions(item_handler<transaction_t> * handler,
const value_expr_t * predicate) const value_expr& predicate)
: item_handler<transaction_t>(handler), pred(predicate) {} : item_handler<transaction_t>(handler), pred(predicate) {}
filter_transactions(item_handler<transaction_t> * handler, filter_transactions(item_handler<transaction_t> * handler,
@ -392,7 +382,7 @@ class component_transactions : public item_handler<transaction_t>
public: public:
component_transactions(item_handler<transaction_t> * handler, component_transactions(item_handler<transaction_t> * handler,
const value_expr_t * predicate) const value_expr& predicate)
: item_handler<transaction_t>(handler), pred(predicate) {} : item_handler<transaction_t>(handler), pred(predicate) {}
component_transactions(item_handler<transaction_t> * handler, component_transactions(item_handler<transaction_t> * handler,
@ -656,7 +646,7 @@ class forecast_transactions : public generate_transactions
public: public:
forecast_transactions(item_handler<transaction_t> * handler, forecast_transactions(item_handler<transaction_t> * handler,
const value_expr_t * predicate) const value_expr& predicate)
: generate_transactions(handler), pred(predicate) {} : generate_transactions(handler), pred(predicate) {}
forecast_transactions(item_handler<transaction_t> * handler, forecast_transactions(item_handler<transaction_t> * handler,
@ -721,11 +711,11 @@ void sum_accounts(account_t& account);
typedef std::deque<account_t *> accounts_deque; typedef std::deque<account_t *> accounts_deque;
void sort_accounts(account_t& account, void sort_accounts(account_t& account,
const value_expr_t * sort_order, const value_expr& sort_order,
accounts_deque& accounts); accounts_deque& accounts);
void walk_accounts(account_t& account, void walk_accounts(account_t& account,
item_handler<account_t>& handler, item_handler<account_t>& handler,
const value_expr_t * sort_order = NULL); const optional<value_expr>& sort_order = none);
void walk_accounts(account_t& account, void walk_accounts(account_t& account,
item_handler<account_t>& handler, item_handler<account_t>& handler,
const string& sort_string); const string& sort_string);