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 \
reconcile.cc \
report.cc \
session.cc \
startup.cc \
textual.cc \
valexpr.cc \

190
binary.cc
View file

@ -179,6 +179,28 @@ void read_string(const char *& data, string * str)
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)
{
@ -207,6 +229,16 @@ void write_string(std::ostream& out, const string& str)
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)
{
commodity_t::ident_t ident;
@ -260,50 +292,53 @@ inline void read_mask(const char *& data, mask_t *& mask)
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)) {
expr = NULL;
return;
}
if (! read_bool(data))
return expr::ptr_op_t();
value_expr_t::kind_t kind;
expr::op_t::kind_t 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) {
read_value_expr(data, expr->left);
if (expr->left) expr->left->acquire();
}
if (kind > expr::op_t::TERMINALS)
expr->set_left(read_value_expr(data));
switch (expr->kind) {
case value_expr_t::O_ARG:
case value_expr_t::INDEX:
read_long(data, expr->arg_index);
case expr::op_t::O_ARG:
case expr::op_t::INDEX: {
long temp;
read_long(data, temp);
expr->set_long(temp);
break;
case value_expr_t::CONSTANT:
expr->value = new value_t;
read_value(data, *expr->value);
}
case expr::op_t::VALUE: {
value_t temp;
read_value(data, temp);
expr->set_value(temp);
break;
}
case value_expr_t::F_CODE_MASK:
case value_expr_t::F_PAYEE_MASK:
case value_expr_t::F_NOTE_MASK:
case value_expr_t::F_ACCOUNT_MASK:
case value_expr_t::F_SHORT_ACCOUNT_MASK:
case value_expr_t::F_COMMODITY_MASK:
case expr::op_t::F_CODE_MASK:
case expr::op_t::F_PAYEE_MASK:
case expr::op_t::F_NOTE_MASK:
case expr::op_t::F_ACCOUNT_MASK:
case expr::op_t::F_SHORT_ACCOUNT_MASK:
case expr::op_t::F_COMMODITY_MASK:
#if 0
if (read_bool(data))
read_mask(data, expr->mask);
#endif
break;
default:
if (kind > value_expr_t::TERMINALS) {
read_value_expr(data, expr->right);
if (expr->right) expr->right->acquire();
}
if (kind > expr::op_t::TERMINALS)
expr->set_right(read_value_expr(data));
break;
}
return expr;
}
@ -322,25 +357,29 @@ inline void read_transaction(const char *& data, transaction_t * xact)
read_string(data, xact->amount_expr.expr);
}
else {
value_expr_t * ptr = NULL;
read_value_expr(data, ptr);
assert(ptr);
expr::ptr_op_t ptr = read_value_expr(data);
assert(ptr.get());
xact->amount_expr.reset(ptr);
read_string(data, xact->amount_expr.expr);
}
if (read_bool(data)) {
xact->cost = new amount_t;
xact->cost = amount_t();
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 {
xact->cost = NULL;
xact->cost = none;
}
read_number(data, xact->state);
read_number(data, xact->flags);
xact->flags |= TRANSACTION_BULK_ALLOC;
read_string(data, &xact->note);
xact->set_flags(read_number<transaction_t::flags_t>(data));
xact->add_flags(TRANSACTION_BULK_ALLOC);
read_string(data, xact->note);
xact->beg_pos = read_long<unsigned long>(data);
read_long(data, xact->beg_line);
@ -350,7 +389,7 @@ inline void read_transaction(const char *& data, transaction_t * xact)
xact->data = NULL;
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,
@ -369,7 +408,7 @@ inline void read_entry_base(const char *& data, entry_base_t * entry,
i++) {
new(xact_pool) transaction_t;
read_transaction(data, xact_pool);
if (ignore_calculated && xact_pool->flags & TRANSACTION_CALCULATED)
if (ignore_calculated && xact_pool->has_flags(TRANSACTION_CALCULATED))
finalize = true;
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_number(data, entry->_date);
read_number(data, entry->_date_eff);
read_string(data, &entry->code);
read_string(data, &entry->payee);
read_string(data, entry->code);
read_string(data, entry->payee);
}
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;
read_entry_base(data, entry, xact_pool, ignore);
value_expr_t * expr;
read_value_expr(data, expr);
// the item_predicate constructor will acquire the reference
entry->predicate = new item_predicate<transaction_t>(expr);
entry->predicate = item_predicate<transaction_t>(read_value_expr(data));
}
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);
commodity_t::base_t * commodity = new commodity_t::base_t(str);
*base_commodities_next++ = commodity;
std::auto_ptr<commodity_t::base_t> commodity(new commodity_t::base_t(str));
read_string(data, str);
if (! str.empty())
@ -427,7 +462,7 @@ inline commodity_t::base_t * read_commodity_base(const char *& data)
read_number(data, flags);
commodity->set_flags(flags);
return commodity;
return *base_commodities_next++ = commodity.release();
}
inline void read_commodity_base_extra(const char *& data,
@ -596,8 +631,13 @@ unsigned int read_journal(std::istream& in,
// Make sure that the cache uses the same price database,
// otherwise it means that LEDGER_PRICE_DB has been changed, and
// we should ignore this cache file.
if (read_string(in) != journal->price_db)
return 0;
if (read_bool(in)) {
string pathname;
read_string(in, pathname);
if (! journal->price_db ||
journal->price_db->string() != std::string(pathname))
return 0;
}
}
// Read all of the data in at once, so that we're just dealing with
@ -812,44 +852,47 @@ void write_mask(std::ostream& out, mask_t * mask)
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) {
write_bool(out, false);
return;
}
write_bool(out, true);
write_number(out, expr->kind);
if (expr->kind > value_expr_t::TERMINALS)
write_value_expr(out, expr->left);
if (expr->kind > expr::op_t::TERMINALS)
write_value_expr(out, expr->left());
switch (expr->kind) {
case value_expr_t::O_ARG:
case value_expr_t::INDEX:
write_long(out, expr->arg_index);
case expr::op_t::O_ARG:
case expr::op_t::INDEX:
write_long(out, expr->as_long());
break;
case value_expr_t::CONSTANT:
write_value(out, *expr->value);
case expr::op_t::VALUE:
write_value(out, expr->as_value());
break;
case value_expr_t::F_CODE_MASK:
case value_expr_t::F_PAYEE_MASK:
case value_expr_t::F_NOTE_MASK:
case value_expr_t::F_ACCOUNT_MASK:
case value_expr_t::F_SHORT_ACCOUNT_MASK:
case value_expr_t::F_COMMODITY_MASK:
case expr::op_t::F_CODE_MASK:
case expr::op_t::F_PAYEE_MASK:
case expr::op_t::F_NOTE_MASK:
case expr::op_t::F_ACCOUNT_MASK:
case expr::op_t::F_SHORT_ACCOUNT_MASK:
case expr::op_t::F_COMMODITY_MASK:
#if 0
if (expr->mask) {
write_bool(out, true);
write_mask(out, expr->mask);
} else {
write_bool(out, false);
}
#endif
break;
default:
if (expr->kind > value_expr_t::TERMINALS)
write_value_expr(out, expr->right);
if (expr->kind > expr::op_t::TERMINALS)
write_value_expr(out, expr->right());
break;
}
@ -862,7 +905,7 @@ void write_transaction(std::ostream& out, transaction_t * xact,
write_number(out, xact->_date_eff);
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_amount(out, amount_t());
}
@ -882,16 +925,16 @@ void write_transaction(std::ostream& out, transaction_t * xact,
}
if (xact->cost &&
(! (ignore_calculated && xact->flags & TRANSACTION_CALCULATED))) {
(! (ignore_calculated && xact->has_flags(TRANSACTION_CALCULATED)))) {
write_bool(out, true);
write_amount(out, *xact->cost);
write_string(out, xact->cost_expr);
write_string(out, xact->cost_expr->expr);
} else {
write_bool(out, false);
}
write_number(out, xact->state);
write_number(out, xact->flags);
write_number(out, xact->flags());
write_string(out, xact->note);
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)
{
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)
@ -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
// 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();

View file

@ -205,6 +205,9 @@ inline string read_string(const char *& data) {
return temp;
}
void read_string(std::istream& in, optional<string>& str);
void read_string(const char *& data, optional<string>& str);
template <typename T>
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 optional<string>& str);
template <typename T>
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 << ',';
write_escaped_string(out, xact.entry->code);
if (xact.entry->code)
write_escaped_string(out, *xact.entry->code);
out << ',';
{

View file

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

View file

@ -468,8 +468,8 @@ void format_t::format(std::ostream& out_str, const details_t& details) const
else
stream << details.xact->amount.strip_annotations();
if (! details.xact->cost_expr.empty())
stream << details.xact->cost_expr;
if (details.xact->cost_expr)
stream << details.xact->cost_expr->expr;
else
stream << " @ " << amount_t(*details.xact->cost /
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: {
string temp;
if (details.entry && ! details.entry->code.empty()) {
if (details.entry && details.entry->code) {
temp += "(";
temp += details.entry->code;
temp += *details.entry->code;
temp += ") ";
}
out << temp;
@ -670,14 +670,14 @@ void format_t::format(std::ostream& out_str, const details_t& details) const
break;
case element_t::OPT_NOTE:
if (details.xact && ! details.xact->note.empty())
if (details.xact && details.xact->note)
out << " ; ";
// fall through...
case element_t::NOTE:
if (details.xact)
out << (elem->max_width == 0 ?
details.xact->note : truncate(details.xact->note,
details.xact->note : truncate(*details.xact->note,
elem->max_width));
break;
@ -705,11 +705,11 @@ void format_t::format(std::ostream& out_str, const details_t& details) const
details.account->fullname() :
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)
name = truncate(name, elem->max_width - 2, true);
if (details.xact->flags & TRANSACTION_BALANCE)
if (details.xact->has_flags(TRANSACTION_BALANCE))
name = string("[") + name + "]";
else
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 =
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";
}
else if (const period_entry_t * entry =
@ -850,13 +850,13 @@ void print_entry(std::ostream& out, const entry_base_t& entry_base,
cleaner);
}
bool disp_subaccounts_p(const account_t& account,
const item_predicate<account_t>& disp_pred,
const account_t *& to_show)
bool disp_subaccounts_p(const account_t& account,
const optional<item_predicate<account_t> >& disp_pred,
const account_t *& to_show)
{
bool display = false;
unsigned int counted = 0;
bool matches = disp_pred(account);
bool matches = disp_pred ? (*disp_pred)(account) : true;
value_t acct_total;
bool computed = false;
value_t result;
@ -866,7 +866,7 @@ bool disp_subaccounts_p(const account_t& account,
for (accounts_map::const_iterator i = account.accounts.begin();
i != account.accounts.end();
i++) {
if (! disp_pred(*(*i).second))
if (disp_pred && ! (*disp_pred)(*(*i).second))
continue;
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,
const item_predicate<account_t>& disp_pred)
const optional<item_predicate<account_t> >& disp_pred)
{
// Never display an account that has already been displayed.
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))
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)

View file

@ -154,16 +154,16 @@ void print_entry(std::ostream& out, const entry_base_t& entry,
const string& prefix = "");
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);
inline bool disp_subaccounts_p(const account_t& account) {
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,
const item_predicate<account_t>& disp_pred);
const optional<item_predicate<account_t> >& disp_pred);
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 "utils.h"
#include "valexpr.h"
#include "mask.h"
#include "format.h"
#include "acconf.h"
namespace ledger {
@ -13,22 +42,21 @@ bool transaction_t::use_effective_date = false;
transaction_t::~transaction_t()
{
DEBUG("ledger.memory.dtors", "dtor transaction_t");
if (cost) delete cost;
TRACE_DTOR(transaction_t);
}
datetime_t transaction_t::actual_date() const
{
if (! is_valid(_date) && entry)
if (! _date && entry)
return entry->actual_date();
return _date;
return *_date;
}
datetime_t transaction_t::effective_date() const
{
if (! is_valid(_date_eff) && entry)
if (! _date_eff && entry)
return entry->effective_date();
return _date_eff;
return *_date_eff;
}
bool transaction_t::valid() const
@ -43,15 +71,10 @@ bool transaction_t::valid() const
return false;
}
bool found = false;
for (transactions_list::const_iterator i = entry->transactions.begin();
i != entry->transactions.end();
i++)
if (*i == this) {
found = true;
break;
}
if (! found) {
transactions_list::const_iterator i =
std::find(entry->transactions.begin(),
entry->transactions.end(), this);
if (i == entry->transactions.end()) {
DEBUG("ledger.validate", "transaction_t: ! found");
return false;
}
@ -71,7 +94,7 @@ bool transaction_t::valid() const
return false;
}
if (flags & ~0x003f) {
if (flags() & ~0x003f) {
DEBUG("ledger.validate", "transaction_t: flags are bad");
return false;
}
@ -99,29 +122,30 @@ bool entry_base_t::finalize()
// and the per-unit price of unpriced commodities.
value_t balance;
bool no_amounts = true;
bool saw_null = false;
bool no_amounts = true;
bool saw_null = false;
for (transactions_list::const_iterator x = transactions.begin();
x != transactions.end();
x++)
if (! ((*x)->flags & TRANSACTION_VIRTUAL) ||
((*x)->flags & TRANSACTION_BALANCE)) {
amount_t * p = (*x)->cost ? (*x)->cost : &(*x)->amount;
if (! p->is_null()) {
if (! (*x)->has_flags(TRANSACTION_VIRTUAL) ||
(*x)->has_flags(TRANSACTION_BALANCE)) {
amount_t& p((*x)->cost ? *(*x)->cost : (*x)->amount);
if (! p.is_null()) {
if (no_amounts) {
balance = *p;
balance = p;
no_amounts = false;
} else {
balance += *p;
balance += p;
}
assert((*x)->amount);
if ((*x)->cost && (*x)->amount.commodity().annotated) {
annotated_commodity_t&
ann_comm(static_cast<annotated_commodity_t&>
((*x)->amount.commodity()));
if (ann_comm.details.price)
balance += ((*ann_comm.details.price) * (*x)->amount -
balance += (*ann_comm.details.price * (*x)->amount.number() -
*((*x)->cost));
}
} else {
@ -137,12 +161,12 @@ bool entry_base_t::finalize()
// account if one has been set.
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);
// The amount doesn't need to be set because the code below will
// balance this transaction against the other.
add_transaction(nxact);
nxact->flags |= TRANSACTION_CALCULATED;
nxact->add_flags(TRANSACTION_CALCULATED);
}
// 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
// the balance. This is done for the last eligible commodity.
if (! saw_null && balance && balance.is_type(value_t::BALANCE) &&
balance.as_balance_lval().amounts.size() == 2) {
transactions_list::const_iterator x = transactions.begin();
commodity_t& this_comm = (*x)->amount.commodity();
if (! saw_null && balance && balance.is_balance()) {
balance_t& bal(balance.as_balance_lval());
if (bal.amounts.size() == 2) {
transactions_list::const_iterator x = transactions.begin();
assert((*x)->amount);
commodity_t& this_comm = (*x)->amount.commodity();
balance_t::amounts_map::const_iterator this_amt =
bal.amounts.find(&this_comm);
balance_t::amounts_map::const_iterator other_amt =
bal.amounts.begin();
if (this_amt == other_amt)
other_amt++;
balance_t::amounts_map::const_iterator this_bal =
bal.amounts.find(&this_comm);
balance_t::amounts_map::const_iterator other_bal =
bal.amounts.begin();
if (this_bal == other_bal)
other_bal++;
if (this_amt != bal.amounts.end()) {
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++) {
if ((*x)->cost || ((*x)->flags & TRANSACTION_VIRTUAL) ||
! (*x)->amount || (*x)->amount.commodity() != this_comm)
if ((*x)->cost || (*x)->has_flags(TRANSACTION_VIRTUAL) ||
(*x)->amount.commodity() != this_comm)
continue;
assert((*x)->amount);
balance -= (*x)->amount;
entry_t * entry = dynamic_cast<entry_t *>(this);
@ -182,10 +204,10 @@ bool entry_base_t::finalize()
! (*x)->amount.commodity().annotated)
(*x)->amount.annotate_commodity
(annotation_t(per_unit_cost.abs(),
entry ? optional<datetime_t>(entry->actual_date()) : none,
entry ? optional<string>(entry->code) : none));
entry ? entry->actual_date() : optional<datetime_t>(),
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;
}
}
@ -199,13 +221,14 @@ bool entry_base_t::finalize()
for (transactions_list::const_iterator x = transactions.begin();
x != transactions.end();
x++) {
if (! (*x)->amount.is_null() ||
(((*x)->flags & TRANSACTION_VIRTUAL) &&
! ((*x)->flags & TRANSACTION_BALANCE)))
if ((*x)->amount ||
((*x)->has_flags(TRANSACTION_VIRTUAL) &&
! (*x)->has_flags(TRANSACTION_BALANCE)))
continue;
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;
// 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
// generated to balance them all.
balance_t * bal = NULL;
const balance_t * bal = NULL;
switch (balance.type()) {
case value_t::BALANCE_PAIR:
bal = &(balance.as_balance_pair_lval().quantity());
bal = &balance.as_balance_pair_lval().quantity();
// fall through...
case value_t::BALANCE:
if (! bal)
bal = &(balance.as_balance_lval());
bal = &balance.as_balance_lval();
if (bal->amounts.size() < 2) {
balance.cast(value_t::AMOUNT);
} else {
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++) {
amount_t amt = (*i).second;
amt.negate();
amount_t amt = (*i).second.negate();
if (first) {
(*x)->amount = amt;
@ -239,7 +262,7 @@ bool entry_base_t::finalize()
} else {
transaction_t * nxact = new transaction_t((*x)->account);
add_transaction(nxact);
nxact->flags |= TRANSACTION_CALCULATED;
nxact->add_flags(TRANSACTION_CALCULATED);
nxact->amount = amt;
}
@ -250,9 +273,8 @@ bool entry_base_t::finalize()
// fall through...
case value_t::AMOUNT:
(*x)->amount = balance.as_amount_lval();
(*x)->amount.in_place_negate();
(*x)->flags |= TRANSACTION_CALCULATED;
(*x)->amount = balance.as_amount().negate();
(*x)->add_flags(TRANSACTION_CALCULATED);
balance += (*x)->amount;
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),
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();
i != transactions.end();
i++)
@ -333,20 +354,6 @@ bool entry_t::valid() const
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)
{
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();
i != initial_xacts.end();
i++) {
if ((*predicate)(**i)) {
if (predicate(**i)) {
for (transactions_list::iterator t = transactions.begin();
t != transactions.end();
t++) {
amount_t amt;
assert((*t)->amount);
if (! (*t)->amount.commodity()) {
if (! post)
continue;
assert((*i)->amount);
amt = (*i)->amount * (*t)->amount;
} else {
if (post)
@ -371,13 +380,13 @@ void auto_entry_t::extend_entry(entry_base_t& entry, bool post)
}
account_t * account = (*t)->account;
string fullname = account->fullname();
string fullname = account->fullname();
assert(! fullname.empty());
if (fullname == "$account" || fullname == "@account")
account = (*i)->account;
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
// the automated entry's one.
@ -398,17 +407,16 @@ void auto_entry_t::extend_entry(entry_base_t& entry, bool post)
account_t::~account_t()
{
DEBUG("ledger.memory.dtors", "dtor account_t " << this);
//assert(! data);
TRACE_DTOR(account_t);
for (accounts_map::iterator i = accounts.begin();
i != accounts.end();
i++)
delete (*i).second;
checked_delete((*i).second);
}
account_t * account_t::find_account(const string& name,
const bool auto_create)
const bool auto_create)
{
accounts_map::const_iterator i = accounts.find(name);
if (i != accounts.end())
@ -442,7 +450,7 @@ account_t * account_t::find_account(const string& name,
account->journal = journal;
std::pair<accounts_map::iterator, bool> result
= accounts.insert(accounts_pair(first, account));
= accounts.insert(accounts_map::value_type(first, account));
assert(result.second);
} else {
account = (*i).second;
@ -479,8 +487,8 @@ string account_t::fullname() const
if (! _fullname.empty()) {
return _fullname;
} else {
const account_t * first = this;
string fullname = name;
const account_t * first = this;
string fullname = name;
while (first->parent) {
first = first->parent;
@ -526,29 +534,33 @@ bool account_t::valid() const
journal_t::~journal_t()
{
DEBUG("ledger.memory.dtors", "dtor journal_t");
TRACE_DTOR(journal_t);
assert(master);
delete master;
checked_delete(master);
// Don't bother unhooking each entry's transactions from the
// accounts they refer to, because all accounts are about to
// be deleted.
for (entries_list::iterator i = entries.begin();
i != entries.end();
i++)
i++) {
if (! item_pool ||
((char *) *i) < item_pool || ((char *) *i) >= item_pool_end)
delete *i;
else
reinterpret_cast<char *>(*i) < item_pool ||
reinterpret_cast<char *>(*i) >= item_pool_end) {
checked_delete(*i);
} else {
(*i)->~entry_t();
}
}
for (auto_entries_list::iterator i = auto_entries.begin();
i != auto_entries.end();
i++)
if (! item_pool ||
((char *) *i) < item_pool || ((char *) *i) >= item_pool_end)
delete *i;
reinterpret_cast<char *>(*i) < item_pool ||
reinterpret_cast<char *>(*i) >= item_pool_end)
checked_delete(*i);
else
(*i)->~auto_entry_t();
@ -556,13 +568,14 @@ journal_t::~journal_t()
i != period_entries.end();
i++)
if (! item_pool ||
((char *) *i) < item_pool || ((char *) *i) >= item_pool_end)
delete *i;
reinterpret_cast<char *>(*i) < item_pool ||
reinterpret_cast<char *>(*i) >= item_pool_end)
checked_delete(*i);
else
(*i)->~period_entry_t();
if (item_pool)
delete[] item_pool;
checked_array_delete(item_pool);
}
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();
i != entry->transactions.end();
i++)
if ((*i)->cost && (*i)->amount)
if ((*i)->cost) {
assert((*i)->amount);
(*i)->amount.commodity().add_price(entry->date(),
*(*i)->cost / (*i)->amount);
*(*i)->cost / (*i)->amount.number());
}
return true;
}
@ -621,18 +636,45 @@ bool journal_t::valid() const
return false;
}
for (commodity_pool_t::commodities_by_ident::const_iterator
i = amount_t::current_pool->commodities.begin();
i != amount_t::current_pool->commodities.end();
i++)
if (! (*i)->valid()) {
DEBUG("ledger.validate", "journal_t: commodity not valid");
return false;
}
return true;
}
void print_entry(std::ostream& out, const entry_base_t& entry_base,
const string& prefix)
{
string print_format;
if (dynamic_cast<const entry_t *>(&entry_base)) {
print_format = (prefix + "%D %X%C%P\n" +
prefix + " %-34A %12o\n%/" +
prefix + " %-34A %12o\n");
}
else if (const auto_entry_t * entry =
dynamic_cast<const auto_entry_t *>(&entry_base)) {
out << "= " << entry->predicate.predicate.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);
}
#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()
{
if (! desc.empty())

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

710
main.cc
View file

@ -1,232 +1,214 @@
#include <iostream>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <exception>
#include <iterator>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <cstring>
/*
* 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 "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
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "fdstream.hpp"
#include <fdstream.hpp>
#endif
#include "ledger.h"
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[])
static int read_and_report(ledger::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;
process_arguments(ledger::config_options, argc - 1, argv + 1, false, args);
strings_list args;
process_arguments(argc - 1, argv + 1, false, report, args);
if (args.empty()) {
option_help(std::cerr);
#if 0
help(std::cerr);
#endif
return 1;
}
strings_list::iterator arg = args.begin();
if (config.cache_file == "<none>")
config.use_cache = false;
if (! session.cache_file)
session.use_cache = false;
else
config.use_cache = config.data_file.empty() && config.price_db.empty();
DEBUG("ledger.config.cache", "1. use_cache = " << config.use_cache);
session.use_cache = ! session.data_file.empty() && session.price_db;
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
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
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,
const_cast<const char **>(envp), "LEDGER_");
xml::xpath_t::context_scope_t doc_scope(report, &temp);
#if 1
// 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) {
IF_INFO() {
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 << "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 << "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 << result.strip_annotations() << std::endl;
std::cout << expr.calc(doc_scope).strip_annotations() << std::endl;
return 0;
}
else if (command == "expr") {
// this gets done below...
}
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
{ TRACE_PUSH(parser, "Parsing journal file");
#endif
INFO_START(journal, "Read journal file");
if (parse_ledger_data(config, journal.get()) == 0)
throw new error("Please specify ledger file using -f"
" or LEDGER_FILE environment variable.");
xml::document_t xml_document(xml::LEDGER_NODE);
journal_t * journal = session.create_journal();
xml::journal_builder_t builder(xml_document, journal);
#if 0
TRACE_POP(parser, "Finished parsing"); }
#endif
if (! session.read_data(builder, journal, report.account))
throw_(parse_error, "Failed to locate any journal entries; "
"did you specify a valid file with -f?");
// process the command word and its following arguments
INFO_FINISH(journal);
string first_arg;
if (command == "w") {
if (arg != args.end())
first_arg = *arg++;
}
else if (command == "W") {
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;
}
TRACE_FINISH(entry_text, 1);
TRACE_FINISH(entry_date, 1);
TRACE_FINISH(entry_details, 1);
TRACE_FINISH(entry_xacts, 1);
TRACE_FINISH(entries, 1);
TRACE_FINISH(parsing_total, 1);
// Configure the output stream
@ -235,22 +217,20 @@ appending the output of this command to your Ledger file if you so choose."
#endif
std::ostream * out = &std::cout;
if (! report.output_file.empty()) {
out = new ofstream(report.output_file);
if (report.output_file) {
out = new ofstream(*report.output_file);
}
#ifdef HAVE_UNIX_PIPES
else if (! config.pager.empty()) {
else if (report.pager) {
status = pipe(pfd);
if (status == -1)
throw new error("Failed to create pipe");
throw_(std::logic_error, "Failed to create pipe");
status = fork();
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
const char *arg0;
// Duplicate pipe's reading end into stdin
status = dup2(pfd[0], STDIN_FILENO);
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
// rightmost '/' character in the pager pathname. See manpage
// for strrchr.
arg0 = std::strrchr(config.pager.c_str(), '/');
if (arg0)
arg0++;
else
arg0 = config.pager.c_str(); // No slashes in pager.
execlp(config.pager.c_str(), arg0, (char *)0);
execlp(report.pager->native_file_string().c_str(),
basename(*report.pager).c_str(), (char *)0);
perror("execl");
exit(1);
}
@ -282,236 +257,188 @@ appending the output of this command to your Ledger file if you so choose."
}
#endif
// Are we handling the parse or expr commands? Do so now.
report.define("ostream", value_t(out));
if (command == "expr") {
value_expr expr(ledger::parse_value_expr(*arg));
// Are we handling the expr commands? Do so now.
if (config.verbose_mode) {
std::cout << "Value expression tree:" << std::endl;
ledger::dump_value_expr(std::cout, expr.get());
std::cout << std::endl;
std::cout << "Value expression parsed was:" << std::endl;
ledger::print_value_expr(std::cout, expr.get());
std::cout << std::endl << std::endl;
std::cout << "Result of computation: ";
xml::xpath_t::context_scope_t doc_scope(report, &xml_document);
if (verb == "expr") {
xml::xpath_t expr(*arg);
IF_INFO() {
*out << "Value expression tree:" << std::endl;
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());
std::cout << result.strip_annotations() << std::endl;
*out << expr.calc(doc_scope).strip_annotations() << std::endl;
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())
format = &report.format_string;
else if (command == "b")
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
format = &config.print_format;
// Walk the entries based on the report type and the options
item_handler<transaction_t> * formatter;
std::list<item_handler<transaction_t> *> formatter_ptrs;
if (command == "b" || command == "E")
formatter = new set_account_value;
else if (command == "p" || command == "e")
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
formatter = new format_transactions(*out, *format);
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") {
#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.
if (command == "b") {
#if 0
TRACE_PUSH(main, "Walking journal accounts");
#endif
format_account acct_formatter(*out, *format, report.display_predicate);
sum_accounts(*journal->master);
walk_accounts(*journal->master, acct_formatter, report.sort_string);
acct_formatter.flush();
if (account_has_xdata(*journal->master)) {
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));
foreach (const value_t& value, xpath.find_all(doc_scope)) {
if (value.is_xml_node())
value.as_xml_node()->print(std::cout);
else
std::cout << value;
std::cout << std::endl;
}
*out << "Compiled results:" << std::endl;
}
#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);
sum_accounts(*journal->master);
walk_accounts(*journal->master, acct_formatter, report.sort_string);
acct_formatter.flush();
xml::compile_node(xml_document, report);
#if 0
TRACE_POP(main, "Finished account walk");
#endif
foreach (const value_t& value, xpath.find_all(doc_scope)) {
if (value.is_xml_node())
value.as_xml_node()->print(std::cout);
else
std::cout << value;
std::cout << std::endl;
}
return 0;
}
#if DEBUG_LEVEL >= BETA
#if 0
{ TRACE_PUSH(cleanup, "Cleaning up allocated memory");
#endif
// Apply transforms to the hierarchical document structure
clear_transaction_xdata xact_cleaner;
walk_entries(journal->entries, xact_cleaner);
INFO_START(transforms, "Applied transforms");
report.apply_transforms(doc_scope);
INFO_FINISH(transforms);
clear_account_xdata acct_cleaner;
walk_accounts(*journal->master, acct_cleaner);
// Create an argument scope containing the report command's
// arguments, and then invoke the command.
if (! report.output_file.empty())
delete out;
xml::xpath_t::call_scope_t command_args(doc_scope);
for (std::list<item_handler<transaction_t> *>::iterator i
= formatter_ptrs.begin();
i != formatter_ptrs.end();
i++)
delete *i;
for (strings_list::iterator i = arg; i != args.end(); i++)
command_args.push_back(value_t(*i, true));
#if 0
TRACE_POP(cleanup, "Finished cleaning"); }
#endif
#endif
INFO_START(command, "Did user command '" << verb << "'");
command(command_args);
INFO_FINISH(command);
// Write out the binary cache, if need be
if (config.use_cache && config.cache_dirty &&
! config.cache_file.empty()) {
#if 0
TRACE_PUSH(binary_cache, "Writing journal file");
#endif
ofstream stream(config.cache_file);
binary::write_journal(stream, journal.get());
if (session.use_cache && session.cache_dirty && session.cache_file) {
TRACE_START(binary_cache, 1, "Wrote binary journal file");
#if 0
TRACE_POP(binary_cache, "Finished writing");
ofstream stream(*session.cache_file);
write_binary_journal(stream, journal);
#endif
TRACE_FINISH(binary_cache, 1);
}
// If the user specified a pager, wait for it to exit now
#ifdef HAVE_UNIX_PIPES
if (! config.pager.empty()) {
delete out;
if (! report.output_file && report.pager) {
checked_delete(out);
close(pfd[1]);
// Wait for child to finish
wait(&status);
if (status & 0xffff != 0)
throw new error("Something went wrong in the pager");
throw_(std::logic_error, "Something went wrong in the pager");
}
#endif
else if (DO_VERIFY() && report.output_file) {
checked_delete(out);
}
return 0;
}
int main(int argc, char * argv[], char * envp[])
{
// This variable must be defined here so that any memory it holds is still
// available should the subsequent exception handlers catch an inner
// exception and need to report something on the invalid state of the
// journal (such as an unbalanced entry).
std::auto_ptr<journal_t> journal;
int status = 1;
for (int i = 1; i < argc; i++)
if (argv[i][0] == '-') {
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 {
#if DEBUG_LEVEL < BETA
ledger::do_cleanup = false;
#endif
config_t config;
report_t report;
ledger::config = &config;
ledger::report = &report;
std::ios::sync_with_stdio(false);
amount_t::initialize();
value_t::initialize();
boost::filesystem::path::default_name_check
(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
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
int status = parse_and_report(config, journal, report, argc, argv, envp);
value_t::shutdown();
amount_t::shutdown();
#if 0
TRACE_POP(main, "Ledger done");
#ifdef HAVE_LIBOFX
session->register_parser(new ofx_parser_t);
#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) {
std::cout.flush();
// 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->reveal_context(std::cerr, "Error");
std::cerr << err->what() << std::endl;
delete err;
return 1;
checked_delete(err);
}
catch (fatal * err) {
std::cout.flush();
@ -531,17 +457,25 @@ int main(int argc, char * argv[], char * envp[])
err->context.push_front(new error_context(""));
err->reveal_context(std::cerr, "Fatal");
std::cerr << err->what() << std::endl;
delete err;
return 1;
checked_delete(err);
}
#endif
catch (const std::exception& err) {
std::cout.flush();
std::cerr << "Error: " << err.what() << std::endl;
return 1;
}
catch (int status) {
return 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;
}
// main.cc ends here.

1139
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
#define _OPTION_H
#include "utils.h"
#include "valexpr.h"
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 {
const char * long_opt;
char short_opt;
bool wants_arg;
handler_t handler;
bool handled;
};
void process_environment(const char ** envp, const string& tag,
expr::scope_t& scope);
void process_arguments(int argc, char ** argv, const bool anywhere,
expr::scope_t& scope,
std::list<string>& args);
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
#endif // _OPTION_H

View file

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

589
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"
namespace ledger {
report_t::report_t()
report_t::~report_t()
{
ledger::amount_expr = "@a";
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;
TRACE_DTOR(report_t);
}
void
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)
void report_t::apply_transforms(expr::scope_t& scope)
{
string regexps[2];
#if 0
typedef tuple<shared_ptr<transform_t>, value_t> transform_details_tuple;
assert(begin != end);
// Treat the remaining command-line arguments as regular
// expressions, used for refining report results.
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 += ")/";
foreach (transform_details_tuple& transform_details, transforms) {
expr::call_scope_t call_args(scope);
call_args.set_args(transform_details.get<1>());
(*transform_details.get<0>())(call_args);
}
#endif
}
void report_t::process_options(const string& command,
strings_list::iterator arg,
strings_list::iterator args_end)
value_t report_t::abbrev(expr::call_scope_t& args)
{
// 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") {
show_related =
show_all_related = true;
}
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;
string str = args[0].as_string();
long wid = args[1];
#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);
}
break;
case 'f':
if (name == "ftime") {
return ftime(args);
}
break;
}
return expr::scope_t::resolve(name, args);
}
#endif
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 == "--")
expr::ptr_op_t report_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 'a':
#if 0
if (std::strcmp(p, "accounts") == 0)
return MAKE_FUNCTOR(report_t::option_accounts);
else
#endif
if (std::strcmp(p, "amount") == 0)
return MAKE_FUNCTOR(report_t::option_amount);
break;
if (i != arg)
regexps_to_predicate(command, arg, i, true,
(command == "b" && ! show_subtotal &&
display_predicate.empty()));
if (i != args_end && ++i != args_end)
regexps_to_predicate(command, i, args_end);
}
case 'b':
if (std::strcmp(p, "bar") == 0)
return MAKE_FUNCTOR(report_t::option_bar);
break;
// Setup the default value for the display predicate
#if 0
case 'c':
if (std::strcmp(p, "clean") == 0)
return MAKE_FUNCTOR(report_t::option_clean);
else if (std::strcmp(p, "compact") == 0)
return MAKE_FUNCTOR(report_t::option_compact);
break;
#endif
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";
case 'e':
#if 0
if (std::strcmp(p, "entries") == 0)
return MAKE_FUNCTOR(report_t::option_entries);
else if (std::strcmp(p, "eval") == 0)
return MAKE_FUNCTOR(report_t::option_eval);
else if (std::strcmp(p, "exclude") == 0)
return MAKE_FUNCTOR(report_t::option_remove);
#endif
break;
case 'f':
#if 0
if (std::strcmp(p, "foo") == 0)
return MAKE_FUNCTOR(report_t::option_foo);
else
#endif
if (std::strcmp(p, "format") == 0)
return MAKE_FUNCTOR(report_t::option_format);
break;
case 'i':
#if 0
if (std::strcmp(p, "include") == 0)
return MAKE_FUNCTOR(report_t::option_select);
#endif
break;
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;
}
}
else if (command == "E") {
display_predicate = "t";
}
else if (command == "r" && ! show_empty) {
display_predicate = "a";
}
break;
}
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;
}
item_handler<transaction_t> *
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;
item_handler<transaction_t> * formatter = NULL;
ptrs.push_back(formatter = base_formatter);
// format_transactions write each transaction received to the
// output stream.
if (! (command == "b" || command == "E")) {
// truncate_entries cuts off a certain number of _entries_ from
// 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
ptrs.push_back(formatter =
new sort_transactions(formatter, sort_string));
}
// changed_value_transactions adds virtual transactions to the
// list to account for changes in market value of commodities,
// which otherwise would affect the running total unpredictably.
if (show_revalued)
ptrs.push_back(formatter =
new changed_value_transactions(formatter,
show_revalued_only));
// collapse_transactions causes entries with multiple transactions
// to appear as entries with a subtotaled transaction for each
// commodity used.
if (show_collapsed)
ptrs.push_back(formatter = new collapse_transactions(formatter));
// subtotal_transactions combines all the transactions it receives
// into one subtotal entry, which has one transaction for each
// commodity in each account.
//
// period_transactions is like subtotal_transactions, but it
// subtotals according to time periods rather than totalling
// everything.
//
// dow_transactions is like period_transactions, except that it
// reports all the transactions that fall on each subsequent day
// of the week.
if (show_subtotal)
ptrs.push_back(formatter =
new subtotal_transactions(formatter, remember_components));
if (days_of_the_week)
ptrs.push_back(formatter =
new dow_transactions(formatter, remember_components));
else if (by_payee)
ptrs.push_back(formatter =
new by_payee_transactions(formatter, remember_components));
// interval_transactions groups transactions together based on a
// time period, such as weekly or monthly.
if (! report_period.empty()) {
ptrs.push_back(formatter =
new interval_transactions(formatter, report_period,
remember_components));
ptrs.push_back(formatter = new sort_transactions(formatter, "d"));
}
}
// 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)
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;
return expr::symbol_scope_t::lookup(name);
}
} // namespace ledger

233
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
#define _REPORT_H
#include "ledger.h"
#include <iostream>
#include <memory>
#include <list>
#include "session.h"
namespace ledger {
class report_t
typedef std::list<string> strings_list;
class report_t : public expr::symbol_scope_t
{
public:
path output_file;
public:
optional<path> output_file;
string format_string;
string amount_expr;
string total_expr;
string date_output_format;
string predicate;
string secondary_predicate;
string display_predicate;
string report_period;
string report_period_sort;
string format_string;
string sort_string;
string amount_expr;
string total_expr;
string descend_expr;
string forecast_limit;
string reconcile_balance;
string reconcile_date;
string date_output_format;
unsigned long budget_flags;
unsigned long budget_flags;
string account;
optional<path> pager;
int head_entries;
int tail_entries;
bool show_totals;
bool raw_mode;
bool show_collapsed;
bool show_subtotal;
bool show_totals;
bool show_related;
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;
session_t& session;
#if 0
transform_t * last_transform;
report_t();
std::list<tuple<shared_ptr<transform_t>, value_t> > transforms;
#endif
void regexps_to_predicate(const string& command,
std::list<string>::const_iterator begin,
std::list<string>::const_iterator end,
const bool account_regexp = false,
const bool add_account_short_masks = false,
const bool logical_and = true);
explicit report_t(session_t& _session)
: expr::symbol_scope_t(downcast<expr::scope_t>(_session)),
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
}
void process_options(const string& command,
strings_list::iterator arg,
strings_list::iterator args_end);
virtual ~report_t();
item_handler<transaction_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);
void apply_transforms(expr::scope_t& scope);
//
// 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
#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");
#if 0
if (expr->kind == value_expr_t::CONSTANT) {
if (expr->kind == expr::node_t::CONSTANT) {
expr = NULL;
} else {
DEBUG_IF("ledger.textual.parse") {

File diff suppressed because it is too large Load diff

698
valexpr.h
View file

@ -13,9 +13,59 @@ class entry_t;
class transaction_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
{
const entry_t * entry;
const entry_t * entry;
const transaction_t * xact;
const account_t * account;
@ -36,11 +86,217 @@ struct details_t
#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 {
// Constants
CONSTANT,
VALUE,
ARG_INDEX,
CONSTANTS,
@ -70,6 +326,8 @@ struct value_expr_t
TOTAL_EXPR,
// Functions
FUNCTION,
F_NOW,
F_ARITH_MEAN,
F_QUANTITY,
@ -121,82 +379,231 @@ struct value_expr_t
LAST
};
kind_t kind;
mutable short refc;
value_expr_t * left;
kind_t kind;
mutable short refc;
ptr_op_t left_;
union {
value_t * value;
mask_t * mask;
unsigned int arg_index; // used by ARG_INDEX and O_ARG
value_expr_t * right;
};
variant<unsigned int, // used by ARG_INDEX and O_ARG
value_t, // used by constant VALUE
mask_t, // used by constant MASK
function_t, // used by terminal FUNCTION
#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)
: kind(_kind), refc(0), left(NULL), right(NULL) {
DEBUG("ledger.memory.ctors", "ctor value_expr_t " << this);
explicit op_t(const kind_t _kind) : kind(_kind), refc(0){
TRACE_CTOR(expr::op_t, "const kind_t");
}
~value_expr_t();
~op_t() {
TRACE_DTOR(expr::op_t);
void release() const {
DEBUG("ledger.valexpr.memory",
"Releasing " << this << ", refc now " << refc - 1);
assert(refc > 0);
if (--refc == 0)
delete this;
DEBUG("ledger.xpath.memory", "Destroying " << this);
assert(refc == 0);
}
value_expr_t * acquire() {
DEBUG("ledger.valexpr.memory",
"Acquiring " << this << ", refc now " << refc + 1);
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);
assert(refc >= 0);
refc++;
return this;
}
const value_expr_t * acquire() const {
DEBUG("ledger.valexpr.memory",
"Acquiring " << this << ", refc now " << refc + 1);
refc++;
return this;
void release() const {
DEBUG("ledger.xpath.memory",
"Releasing " << this << ", refc now " << refc - 1);
assert(refc > 0);
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);
if (left)
left->release();
left = expr ? expr->acquire() : NULL;
return left_;
}
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);
if (right)
right->release();
right = expr ? expr->acquire() : NULL;
return as_op();
}
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,
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_expr_t * context = NULL) const {
ptr_op_t context = NULL) const {
value_t temp;
compute(temp, details, context);
return temp;
}
private:
value_expr_t(const value_expr_t&) {
DEBUG("ledger.memory.ctors", "ctor value_expr_t (copy) " << this);
struct print_context_t
{
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 {
public:
const ledger::value_expr_t * expr;
const ledger::value_expr_t * error_node;
ptr_op_t expr;
ptr_op_t error_node;
valexpr_context(const ledger::value_expr_t * _expr,
const string& desc = "") throw();
valexpr_context(const ptr_op_t _expr,
const string& desc = "") throw();
virtual ~valexpr_context() throw();
virtual void describe(std::ostream& out) const throw();
@ -217,53 +624,6 @@ class value_expr_error : public error {
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 datetime_t terminus;
@ -271,9 +631,9 @@ extern bool initialized;
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,
value_expr_t * context = NULL);
const ptr_op_t context = NULL);
#define PARSE_VALEXPR_NORMAL 0x00
#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_REDUCE 0x08
value_expr_t * parse_value_expr(std::istream& in,
ptr_op_t parse_value_expr(std::istream& in,
scope_t * scope = NULL,
const short flags = PARSE_VALEXPR_RELAXED);
inline value_expr_t *
inline ptr_op_t
parse_value_expr(const string& str,
scope_t * scope = NULL,
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,
scope_t * scope = NULL,
const short flags = PARSE_VALEXPR_RELAXED) {
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);
bool print_value_expr(std::ostream& out,
const value_expr_t * node,
const ptr_op_t node,
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 * end_pos = NULL);
//////////////////////////////////////////////////////////////////////
inline void guarded_compute(const value_expr_t * expr,
value_t& result,
const details_t& details = details_t(),
value_expr_t * context = NULL) {
inline void guarded_compute(const ptr_op_t expr,
value_t& result,
const details_t& details = details_t(),
const ptr_op_t context = NULL) {
try {
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));
error_context * last = err->context.back();
if (valexpr_context * ctxt = dynamic_cast<valexpr_context *>(last)) {
ctxt->expr = expr->acquire();
ctxt->expr = expr;
ctxt->desc = "While computing value expression:";
}
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(),
value_expr_t * context = NULL) {
ptr_op_t context = NULL) {
value_t temp;
guarded_compute(expr, temp, details, context);
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
{
value_expr_t * ptr;
expr::ptr_op_t ptr;
public:
string expr;
typedef expr::details_t details_t;
value_expr() : ptr(NULL) {}
value_expr(const string& _expr) : expr(_expr) {
DEBUG("ledger.memory.ctors", "ctor value_expr");
if (! _expr.empty())
ptr = parse_value_expr(expr)->acquire();
ptr = expr::parse_value_expr(expr);
else
ptr = NULL;
ptr = expr::ptr_op_t();
}
value_expr(value_expr_t * _ptr)
: ptr(_ptr ? _ptr->acquire(): NULL) {
value_expr(const expr::ptr_op_t _ptr) : ptr(_ptr) {
DEBUG("ledger.memory.ctors", "ctor value_expr");
}
value_expr(const value_expr& other)
: ptr(other.ptr ? other.ptr->acquire() : NULL),
expr(other.expr) {
value_expr(const value_expr& other) : ptr(other.ptr), expr(other.expr) {
DEBUG("ledger.memory.ctors", "ctor value_expr");
}
virtual ~value_expr() {
@ -382,10 +778,10 @@ public:
value_expr& operator=(const string& _expr) {
expr = _expr;
reset(parse_value_expr(expr));
reset(expr::parse_value_expr(expr));
return *this;
}
value_expr& operator=(value_expr_t * _expr) {
value_expr& operator=(expr::ptr_op_t _expr) {
expr = "";
reset(_expr);
return *this;
@ -402,50 +798,48 @@ public:
operator string() const throw() {
return expr;
}
operator value_expr_t *() const throw() {
operator const expr::ptr_op_t() const throw() {
return ptr;
}
value_expr_t& operator*() const throw() {
const expr::op_t& operator*() const throw() {
return *ptr;
}
value_expr_t * operator->() const throw() {
const expr::ptr_op_t operator->() const throw() {
return ptr;
}
value_expr_t * get() const throw() { return ptr; }
value_expr_t * release() throw() {
value_expr_t * tmp = ptr;
ptr = 0;
const expr::ptr_op_t get() const throw() { return ptr; }
const expr::ptr_op_t release() throw() {
const expr::ptr_op_t tmp = ptr;
ptr = expr::ptr_op_t();
return tmp;
}
void reset(value_expr_t * p = 0) throw() {
if (p != ptr) {
if (ptr)
ptr->release();
ptr = p ? p->acquire() : NULL;
}
void reset(const expr::ptr_op_t p = expr::ptr_op_t()) throw() {
ptr = p;
}
virtual void compute(value_t& result,
const details_t& details = details_t(),
value_expr_t * context = NULL) {
expr::ptr_op_t context = NULL) {
guarded_compute(ptr, result, details, context);
}
virtual value_t compute(const details_t& details = details_t(),
value_expr_t * context = NULL) {
expr::ptr_op_t context = NULL) {
value_t temp;
guarded_compute(ptr, temp, details, context);
return temp;
}
friend bool print_value_expr(std::ostream& out,
const value_expr_t * node,
const value_expr_t * node_to_find,
const expr::ptr_op_t node,
const expr::ptr_op_t node_to_find,
unsigned long * start_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 total_expr;
@ -471,14 +865,14 @@ inline value_t compute_total(const details_t& details = details_t()) {
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);
inline void parse_value_definition(const string& str,
scope_t * scope = NULL) {
expr::scope_t * scope = NULL) {
std::istringstream def(str);
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));
}
@ -487,28 +881,26 @@ inline void parse_value_definition(const string& str,
template <typename T>
class item_predicate
{
public:
const value_expr_t * predicate;
public:
value_expr predicate;
item_predicate(const string& _predicate) : predicate(NULL) {
DEBUG("ledger.memory.ctors", "ctor item_predicate<T>");
if (! _predicate.empty())
predicate = parse_value_expr(_predicate)->acquire();
item_predicate() {
TRACE_CTOR(item_predicate, "ctor item_predicate<T>()");
}
item_predicate(const value_expr_t * _predicate = NULL)
: predicate(_predicate->acquire()) {
DEBUG("ledger.memory.ctors", "ctor item_predicate<T>");
item_predicate(const value_expr& _predicate) : predicate(_predicate) {
TRACE_CTOR(item_predicate, "ctor item_predicate<T>(const value_expr&)");
}
item_predicate(const string& _predicate) : predicate(_predicate) {
TRACE_CTOR(item_predicate, "ctor item_predicate<T>(const string&)");
}
~item_predicate() {
DEBUG("ledger.memory.dtors", "dtor item_predicate<T>");
if (predicate)
predicate->release();
TRACE_DTOR(item_predicate);
}
bool operator()(const T& item) const {
return (! predicate ||
predicate->compute(details_t(item)).strip_annotations());
predicate->compute(value_expr::details_t(item)).strip_annotations());
}
};

24
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));
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.dflags |= TRANSACTION_SORT_CALC;
}
transaction_xdata_t& rxdata(transaction_xdata(*right));
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.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));
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;
}
account_xdata_t& rxdata(account_xdata(*right));
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;
}
@ -841,9 +841,9 @@ void sum_accounts(account_t& account)
xdata.total_count += xdata.count;
}
void sort_accounts(account_t& account,
const value_expr_t * sort_order,
accounts_deque& accounts)
void sort_accounts(account_t& account,
const value_expr& sort_order,
accounts_deque& accounts)
{
for (accounts_map::iterator i = account.accounts.begin();
i != account.accounts.end();
@ -854,15 +854,15 @@ void sort_accounts(account_t& account,
compare_items<account_t>(sort_order));
}
void walk_accounts(account_t& account,
item_handler<account_t>& handler,
const value_expr_t * sort_order)
void walk_accounts(account_t& account,
item_handler<account_t>& handler,
const optional<value_expr>& sort_order)
{
handler(account);
if (sort_order) {
accounts_deque accounts;
sort_accounts(account, sort_order, accounts);
sort_accounts(account, *sort_order, accounts);
for (accounts_deque::const_iterator i = accounts.begin();
i != accounts.end();
i++) {
@ -884,7 +884,7 @@ void walk_accounts(account_t& account,
if (! sort_string.empty()) {
value_expr sort_order;
sort_order.reset(parse_value_expr(sort_string));
walk_accounts(account, handler, sort_order.get());
walk_accounts(account, handler, sort_order);
} else {
walk_accounts(account, handler);
}

50
walk.h
View file

@ -38,12 +38,9 @@ struct item_handler {
template <typename T>
class compare_items {
const value_expr_t * sort_order;
value_expr sort_order;
public:
compare_items(const value_expr_t * _sort_order)
: sort_order(_sort_order) {
assert(sort_order);
}
compare_items(value_expr _sort_order) : sort_order(_sort_order) {}
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 right_result;
guarded_compute(sort_order, left_result, details_t(*left));
guarded_compute(sort_order, right_result, details_t(*right));
sort_order.compute(left_result, details_t(*left));
sort_order.compute(right_result, details_t(*right));
return left_result < right_result;
}
@ -244,26 +241,19 @@ class sort_transactions : public item_handler<transaction_t>
{
typedef std::deque<transaction_t *> transactions_deque;
transactions_deque transactions;
const value_expr_t * sort_order;
transactions_deque transactions;
const value_expr sort_order;
public:
sort_transactions(item_handler<transaction_t> * handler,
const value_expr_t * _sort_order)
const value_expr& _sort_order)
: item_handler<transaction_t>(handler),
sort_order(_sort_order->acquire()) {}
sort_order(_sort_order) {}
sort_transactions(item_handler<transaction_t> * handler,
const string& _sort_order)
: item_handler<transaction_t>(handler) {
assert(! _sort_order.empty());
sort_order = parse_value_expr(_sort_order)->acquire();
}
virtual ~sort_transactions() {
assert(sort_order);
sort_order->release();
}
: item_handler<transaction_t>(handler),
sort_order(_sort_order) {}
virtual void post_accumulated_xacts();
@ -284,7 +274,7 @@ class sort_entries : public item_handler<transaction_t>
public:
sort_entries(item_handler<transaction_t> * handler,
const value_expr_t * _sort_order)
const value_expr& _sort_order)
: sorter(handler, _sort_order) {}
sort_entries(item_handler<transaction_t> * handler,
@ -312,7 +302,7 @@ class filter_transactions : public item_handler<transaction_t>
public:
filter_transactions(item_handler<transaction_t> * handler,
const value_expr_t * predicate)
const value_expr& predicate)
: item_handler<transaction_t>(handler), pred(predicate) {}
filter_transactions(item_handler<transaction_t> * handler,
@ -392,7 +382,7 @@ class component_transactions : public item_handler<transaction_t>
public:
component_transactions(item_handler<transaction_t> * handler,
const value_expr_t * predicate)
const value_expr& predicate)
: item_handler<transaction_t>(handler), pred(predicate) {}
component_transactions(item_handler<transaction_t> * handler,
@ -656,7 +646,7 @@ class forecast_transactions : public generate_transactions
public:
forecast_transactions(item_handler<transaction_t> * handler,
const value_expr_t * predicate)
const value_expr& predicate)
: generate_transactions(handler), pred(predicate) {}
forecast_transactions(item_handler<transaction_t> * handler,
@ -720,12 +710,12 @@ void sum_accounts(account_t& account);
typedef std::deque<account_t *> accounts_deque;
void sort_accounts(account_t& account,
const value_expr_t * sort_order,
accounts_deque& accounts);
void walk_accounts(account_t& account,
item_handler<account_t>& handler,
const value_expr_t * sort_order = NULL);
void sort_accounts(account_t& account,
const value_expr& sort_order,
accounts_deque& accounts);
void walk_accounts(account_t& account,
item_handler<account_t>& handler,
const optional<value_expr>& sort_order = none);
void walk_accounts(account_t& account,
item_handler<account_t>& handler,
const string& sort_string);