brought back the "print" and "equity" reports; this time much better!

This commit is contained in:
John Wiegley 2004-07-31 07:42:34 -04:00
parent 2a9e778b7d
commit f570e6644f
14 changed files with 637 additions and 571 deletions

View file

@ -10,7 +10,7 @@
namespace ledger {
static void mpz_round(mpz_t value, int precision)
static void mpz_round(mpz_t out, mpz_t value, int precision)
{
mpz_t divisor;
@ -33,17 +33,17 @@ static void mpz_round(mpz_t value, int precision)
mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - precision);
mpz_add(remainder, divisor, remainder);
mpz_ui_sub(remainder, 0, remainder);
mpz_add(value, value, remainder);
mpz_add(out, value, remainder);
} else {
mpz_sub(value, value, remainder);
mpz_sub(out, value, remainder);
}
} else {
if (mpz_cmp(remainder, divisor) >= 0) {
mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - precision);
mpz_sub(remainder, divisor, remainder);
mpz_add(value, value, remainder);
mpz_add(out, value, remainder);
} else {
mpz_sub(value, value, remainder);
mpz_sub(out, value, remainder);
}
}
@ -357,7 +357,7 @@ amount_t amount_t::round(int precision) const
return *this;
} else {
amount_t temp = *this;
mpz_round(MPZ(temp.quantity),
mpz_round(MPZ(temp.quantity), MPZ(temp.quantity),
precision == -1 ? commodity->precision : precision);
return temp;
}
@ -380,8 +380,14 @@ std::ostream& operator<<(std::ostream& out, const amount_t& amt)
bool negative = false;
// Ensure the value is rounded to the commodity's precision before
// outputting it. NOTE: `rquotient' is used here as a temp variable!
if (amt.commodity->precision != MAX_PRECISION)
mpz_round(rquotient, MPZ(amt.quantity), amt.commodity->precision);
mpz_ui_pow_ui(divisor, 10, MAX_PRECISION);
mpz_tdiv_qr(quotient, remainder, MPZ(amt.quantity), divisor);
mpz_tdiv_qr(quotient, remainder, rquotient, divisor);
if (mpz_sgn(quotient) < 0 || mpz_sgn(remainder) < 0)
negative = true;
@ -392,10 +398,6 @@ std::ostream& operator<<(std::ostream& out, const amount_t& amt)
if (amt.commodity->precision == MAX_PRECISION) {
mpz_set(rquotient, remainder);
} else {
// Ensure the value is rounded to the commodity's precision before
// outputting it
mpz_round(MPZ(amt.quantity), amt.commodity->precision);
assert(MAX_PRECISION - amt.commodity->precision > 0);
mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - amt.commodity->precision);
mpz_tdiv_qr(rquotient, remainder, remainder, divisor);
@ -681,6 +683,19 @@ void (*commodity_t::updater)(commodity_t * commodity,
commodities_map commodity_t::commodities;
struct cleanup_commodities
{
~cleanup_commodities() {
for (commodities_map::iterator i
= commodity_t::commodities.begin();
i != commodity_t::commodities.end();
i++)
delete (*i).second;
}
};
static cleanup_commodities cleanup;
commodity_t * commodity_t::find_commodity(const std::string& symbol,
bool auto_create)
{

View file

@ -274,6 +274,27 @@ class balance_t
return amount(amt.commodity) >= amt;
}
bool operator==(const balance_t& bal) const {
amounts_map::const_iterator i, j;
for (i = amounts.begin(), j = bal.amounts.begin();
i != amounts.end() && j != bal.amounts.end();
i++, j++) {
if (! ((*i).first == (*j).first &&
(*i).second == (*j).second))
return false;
}
return i == amounts.end() && j == bal.amounts.end();
}
bool operator==(const amount_t& amt) const {
return amounts.size() == 1 && (*amounts.begin()).second == amt;
}
bool operator!=(const balance_t& bal) const {
return ! (*this == bal);
}
bool operator!=(const amount_t& amt) const {
return ! (*this == amt);
}
// unary negation
balance_t& negate() {
for (amounts_map::iterator i = amounts.begin();
@ -561,6 +582,25 @@ class balance_pair_t
return quantity >= amt;
}
bool operator==(const balance_pair_t& bal_pair) const {
return quantity == bal_pair.quantity;
}
bool operator==(const balance_t& bal) const {
return quantity == bal;
}
bool operator==(const amount_t& amt) const {
return quantity == amt;
}
bool operator!=(const balance_pair_t& bal_pair) const {
return ! (*this == bal_pair);
}
bool operator!=(const balance_t& bal) const {
return ! (*this == bal);
}
bool operator!=(const amount_t& amt) const {
return ! (*this == amt);
}
// unary negation
balance_pair_t& negate() {
quantity.negate();

View file

@ -15,7 +15,7 @@
namespace ledger {
unsigned long magic_number = 0xFFEED765;
static unsigned long format_version = 0x00020008;
static unsigned long format_version = 0x00020009;
static char buf[4096];

View file

@ -129,15 +129,11 @@ balance_t node_t::compute(const item_t * item) const
break;
case CLEARED:
#if 0
temp = amount_t(item->state == CLEARED ? 1 : 0);
#endif
temp = amount_t(item->state == entry_t::CLEARED ? 1 : 0);
break;
case REAL:
#if 0
temp = amount_t(item->flags & TRANSACTION_VIRTUAL ? 0 : 1);
#endif
break;
case INDEX:
@ -162,7 +158,7 @@ balance_t node_t::compute(const item_t * item) const
case F_PAYEE_MASK:
assert(mask);
temp = mask->match(item->payee);
temp = (mask->match(item->payee) || mask->match(item->note)) ? 1 : 0;
break;
case F_ACCOUNT_MASK:

6
expr.h
View file

@ -136,7 +136,10 @@ class value_predicate
} else {
item_t temp;
temp.date = xact->entry->date;
temp.state = xact->entry->state;
temp.code = xact->entry->code;
temp.payee = xact->entry->payee;
temp.flags = xact->flags;
temp.account = xact->account;
return predicate->compute(&temp);
}
@ -149,6 +152,8 @@ class value_predicate
item_t temp;
temp.date = entry->date;
temp.payee = entry->payee;
temp.state = entry->state;
temp.code = entry->code;
// Although there may be conflicting account masks for the whole
// set of transactions -- for example, /rent/&!/expenses/, which
@ -159,6 +164,7 @@ class value_predicate
for (transactions_list::const_iterator i = entry->transactions.begin();
i != entry->transactions.end();
i++) {
temp.flags = (*i)->flags;
temp.account = (*i)->account;
if (predicate->compute(&temp))
return true;

View file

@ -24,8 +24,8 @@ std::string maximal_account_name(const item_t * item, const item_t * parent)
return name;
}
node_t * format_t::value_expr = NULL;
node_t * format_t::total_expr = NULL;
std::auto_ptr<node_t> format_t::value_expr;
std::auto_ptr<node_t> format_t::total_expr;
element_t * format_t::parse_elements(const std::string& fmt)
{
@ -110,9 +110,12 @@ element_t * format_t::parse_elements(const std::string& fmt)
current->chars = "%Y/%m/%d";
break;
case 'X': current->type = element_t::CLEARED; break;
case 'C': current->type = element_t::CODE; break;
case 'p': current->type = element_t::PAYEE; break;
case 'n': current->type = element_t::ACCOUNT_NAME; break;
case 'N': current->type = element_t::ACCOUNT_FULLNAME; break;
case 'o': current->type = element_t::OPT_AMOUNT; break;
case 't': current->type = element_t::VALUE; break;
case 'T': current->type = element_t::TOTAL; break;
case '_': current->type = element_t::SPACER; break;
@ -174,28 +177,89 @@ void format_t::format_elements(std::ostream& out, const item_t * item,
}
break;
case element_t::CLEARED:
if (item->state == entry_t::CLEARED)
out << "* ";
else
out << "";
break;
case element_t::CODE:
if (! item->code.empty())
out << "(" << item->code << ") ";
else
out << "";
break;
case element_t::PAYEE:
out << (elem->max_width == 0 ?
item->payee : truncated(item->payee, elem->max_width));
break;
case element_t::ACCOUNT_NAME:
case element_t::ACCOUNT_FULLNAME:
if (item->account) {
std::string name = maximal_account_name(item, displayed_parent);
out << (elem->max_width == 0 ? name : truncated(name, elem->max_width));
std::string name = (elem->type == element_t::ACCOUNT_FULLNAME ?
item->account->fullname() :
maximal_account_name(item, displayed_parent));
if (elem->max_width > 0)
name = truncated(name, elem->max_width);
if (item->flags & TRANSACTION_VIRTUAL) {
if (item->flags & TRANSACTION_BALANCE)
name = "[" + name + "]";
else
name = "(" + name + ")";
}
out << name;
} else {
out << " ";
}
break;
case element_t::ACCOUNT_FULLNAME:
if (item->account)
out << (elem->max_width == 0 ?
item->account->fullname() :
truncated(item->account->fullname(), elem->max_width));
case element_t::OPT_AMOUNT: {
std::string disp;
bool use_disp = false;
if (std::find(displayed_parent->subitems.begin(),
displayed_parent->subitems.end(), item) !=
displayed_parent->subitems.end()) {
if (displayed_parent->subitems.size() == 2 &&
item == displayed_parent->subitems.back() &&
(displayed_parent->subitems.front()->value.quantity ==
displayed_parent->subitems.front()->value.cost) &&
(displayed_parent->subitems.front()->value ==
- displayed_parent->subitems.back()->value)) {
use_disp = true;
}
else if (displayed_parent->subitems.size() != 2 &&
item->value.quantity != item->value.cost &&
item->value.quantity.amounts.size() == 1 &&
item->value.cost.amounts.size() == 1 &&
((*item->value.quantity.amounts.begin()).first !=
(*item->value.cost.amounts.begin()).first)) {
amount_t unit_cost
= ((*item->value.cost.amounts.begin()).second /
(*item->value.quantity.amounts.begin()).second);
std::ostringstream stream;
stream << item->value.quantity << " @ " << unit_cost;
disp = stream.str();
use_disp = true;
}
}
if (use_disp)
out << disp;
else
out << " ";
item->value.quantity.write(out, elem->min_width,
elem->max_width > 0 ?
elem->max_width : elem->min_width);
// jww (2004-07-31): this should be handled differently
if (! item->note.empty())
out << " ; " << item->note;
break;
}
case element_t::VALUE: {
balance_t value = compute_value(item);

View file

@ -16,9 +16,12 @@ struct element_t
STRING,
VALUE_EXPR,
DATE_STRING,
CLEARED,
CODE,
PAYEE,
ACCOUNT_NAME,
ACCOUNT_FULLNAME,
OPT_AMOUNT,
VALUE,
TOTAL,
SPACER
@ -47,8 +50,8 @@ struct format_t
{
element_t * elements;
static node_t * value_expr;
static node_t * total_expr;
static std::auto_ptr<node_t> value_expr;
static std::auto_ptr<node_t> total_expr;
format_t(const std::string& _format) {
elements = parse_elements(_format);
@ -63,10 +66,10 @@ struct format_t
const item_t * displayed_parent = NULL) const;
static balance_t compute_value(const item_t * item) {
return value_expr ? value_expr->compute(item) : balance_t();
return value_expr.get() ? value_expr->compute(item) : balance_t();
}
static balance_t compute_total(const item_t * item) {
return total_expr ? total_expr->compute(item) : balance_t();
return total_expr.get() ? total_expr->compute(item) : balance_t();
}
};

169
item.cc
View file

@ -3,51 +3,6 @@
namespace ledger {
// jww (2004-07-21): If format.show_empty is set, then include all
// subaccounts, empty, balanced or no
item_t * walk_accounts(const account_t * account,
const node_t * predicate,
const bool show_subtotals)
{
item_t * item = new item_t;
item->account = account;
std::time_t latest = 0;
for (transactions_list::const_iterator i
= std::find_if(account->transactions.begin(),
account->transactions.end(),
value_predicate(predicate));
i != account->transactions.end();
i = std::find_if(++i, account->transactions.end(),
value_predicate(predicate))) {
if (std::difftime(latest, (*i)->entry->date) < 0)
latest = (*i)->entry->date;
item->value += *(*i);
if (show_subtotals)
item->total += *(*i);
}
item->date = latest;
for (accounts_map::const_iterator i = account->accounts.begin();
i != account->accounts.end();
i++) {
item_t * subitem = walk_accounts((*i).second, predicate, show_subtotals);
subitem->parent = item;
if (std::difftime(item->date, subitem->date) < 0)
item->date = subitem->date;
if (show_subtotals)
item->total += subitem->total;
if (show_subtotals ? subitem->total : subitem->value)
item->subitems.push_back(subitem);
}
return item;
}
static inline void sum_items(const item_t * top,
const bool show_subtotals,
item_t * item)
@ -64,27 +19,88 @@ static inline void sum_items(const item_t * top,
sum_items(*i, show_subtotals, item);
}
item_t * walk_items(const item_t * top,
const account_t * account,
const node_t * predicate,
const bool show_subtotals)
item_t * walk_accounts(const item_t * top,
account_t * account,
const node_t * predicate,
const bool show_subtotals,
const bool show_flattened)
{
item_t * item = new item_t;
item->account = account;
sum_items(top, show_subtotals, item);
if (top) {
sum_items(top, show_subtotals, item);
} else {
std::time_t latest = 0;
for (transactions_list::iterator i
= std::find_if(account->transactions.begin(),
account->transactions.end(),
value_predicate(predicate));
i != account->transactions.end();
i = std::find_if(++i, account->transactions.end(),
value_predicate(predicate))) {
if (std::difftime(latest, (*i)->entry->date) < 0)
latest = (*i)->entry->date;
for (accounts_map::const_iterator i = account->accounts.begin();
item->value += *(*i);
if (show_subtotals)
item->total += *(*i);
}
item->date = latest;
}
for (accounts_map::iterator i = account->accounts.begin();
i != account->accounts.end();
i++) {
item_t * subitem = walk_items(top, (*i).second, predicate, show_subtotals);
std::auto_ptr<item_t>
subitem(walk_accounts(top, (*i).second, predicate, show_subtotals,
show_flattened));
subitem->parent = item;
if (std::difftime(item->date, subitem->date) < 0)
item->date = subitem->date;
if (show_flattened) {
item_t * ptr = item;
balance_pair_t total;
for (items_deque::const_iterator i = subitem->subitems.begin();
i != subitem->subitems.end();
i++)
if (show_subtotals ? (*i)->total : (*i)->value) {
if (! account->parent) {
if (! total) {
item_t * temp = new item_t;
temp->date = top ? top->date : item->date;
temp->payee = "Opening balance";
item->subitems.push_back(temp);
ptr = temp;
}
total += show_subtotals ? (*i)->total : (*i)->value;
}
ptr->subitems.push_back(new item_t(*i));
ptr->subitems.back()->date = ptr->date;
ptr->subitems.back()->payee = ptr->payee;
}
if (total) {
item_t * temp = new item_t;
temp->date = ptr->date;
temp->payee = ptr->payee;
temp->account = account->find_account("Equity:Opening Balances");
temp->value = total;
temp->value.negate();
ptr->subitems.push_back(temp);
}
}
if (show_subtotals)
item->total += subitem->total;
if (show_subtotals ? subitem->total : subitem->value)
item->subitems.push_back(subitem);
if ((! show_flattened || account->parent) &&
show_subtotals ? subitem->total : subitem->value)
item->subitems.push_back(subitem.release());
}
return item;
@ -103,6 +119,7 @@ item_t * walk_entries(entries_list::const_iterator begin,
for (entries_list::const_iterator i = std::find_if(begin, end, pred_obj);
i != end;
i = std::find_if(++i, end, pred_obj)) {
transactions_list reckoned;
item_t * item = NULL;
for (transactions_list::const_iterator j
@ -115,39 +132,32 @@ item_t * walk_entries(entries_list::const_iterator begin,
assert(*i == (*j)->entry);
if (! item) {
item = new item_t;
item = new item_t(*i);
item->index = count++;
item->date = (*i)->date;
item->payee = (*i)->payee;
}
// If show_inverted is true, it implies show_related.
if (! show_inverted) {
item_t * subitem = new item_t;
subitem->parent = item;
subitem->date = item->date;
subitem->payee = item->payee;
subitem->account = (*j)->account;
subitem->value = *(*j);
item->value += subitem->value;
item->subitems.push_back(subitem);
if (! show_inverted &&
std::find(reckoned.begin(),
reckoned.end(), *j) == reckoned.end()) {
item->add_item(new item_t(*j));
reckoned.push_back(*j);
}
if (show_related)
for (transactions_list::iterator k = (*i)->transactions.begin();
k != (*i)->transactions.end();
k++)
if (*k != *j && ! ((*k)->flags & TRANSACTION_VIRTUAL)) {
item_t * subitem = new item_t;
subitem->parent = item;
subitem->date = item->date;
subitem->payee = item->payee;
subitem->account = (*k)->account;
subitem->value = *(*k);
if (show_inverted)
subitem->value.negate();
item->subitems.push_back(subitem);
}
k++) {
if (*k == *j || ((*k)->flags & TRANSACTION_AUTO) ||
std::find(reckoned.begin(),
reckoned.end(), *k) != reckoned.end())
continue;
item->add_item(new item_t(*k));
if (show_inverted)
item->subitems.back()->value.negate();
reckoned.push_back(*k);
}
}
if (item) {
@ -155,6 +165,9 @@ item_t * walk_entries(entries_list::const_iterator begin,
result = new item_t;
item->parent = result;
result->subitems.push_back(item);
if (std::difftime(result->date, item->date) < 0)
result->date = item->date;
}
}

64
item.h
View file

@ -14,17 +14,38 @@ typedef std::deque<item_t *> items_deque;
struct item_t
{
struct item_t * parent;
items_deque subitems;
struct item_t * parent;
items_deque subitems;
unsigned int index;
std::time_t date;
std::string payee;
const account_t * account;
balance_pair_t value;
balance_pair_t total;
unsigned int index;
std::time_t date;
entry_t::entry_state_t state;
std::string code;
std::string payee;
unsigned int flags;
const account_t * account;
balance_pair_t value;
balance_pair_t total;
std::string note;
item_t() : parent(NULL), index(0), date(-1), account(NULL) {}
item_t() : parent(NULL), index(0), date(-1),
state(entry_t::UNCLEARED), flags(0), account(NULL) {}
item_t(const item_t * item)
: parent(NULL), index(0), date(item->date), state(item->state),
code(item->code), payee(item->payee), flags(item->flags),
account(item->account), value(item->value), total(item->total),
note(item->note) {}
item_t(const entry_t * entry)
: parent(NULL), index(0), date(entry->date), state(entry->state),
code(entry->code), payee(entry->payee) {}
item_t(const transaction_t * xact)
: parent(NULL), index(0), date(xact->entry->date),
state(xact->entry->state), code(xact->entry->code),
payee(xact->entry->payee), flags(xact->flags),
account(xact->account), value(*xact), note(xact->note) {}
~item_t() {
for (items_deque::iterator i = subitems.begin();
@ -33,25 +54,28 @@ struct item_t
delete *i;
}
void add_item(item_t * item) {
item->parent = this;
value += item->value;
subitems.push_back(item);
}
void sort(const node_t * sort_order);
};
struct node_t;
item_t * walk_accounts(const account_t * account,
const node_t * predicate,
const bool show_subtotals);
item_t * walk_items(const item_t * top,
const account_t * account,
const node_t * predicate,
const bool show_subtotals);
item_t * walk_accounts(const item_t * top,
account_t * account,
const node_t * predicate = NULL,
const bool show_subtotals = true,
const bool show_flattened = false);
item_t * walk_entries(entries_list::const_iterator begin,
entries_list::const_iterator end,
const node_t * predicate,
const bool show_related,
const bool show_inverted);
const node_t * predicate = NULL,
const bool show_related = false,
const bool show_inverted = false);
} // namespace report

128
ledger.cc
View file

@ -1,4 +1,5 @@
#include "ledger.h"
#include "expr.h"
#include "textual.h"
#include "binary.h"
@ -52,6 +53,133 @@ bool ledger_t::remove_entry(entry_t * entry)
return true;
}
entry_t * ledger_t::derive_entry(int argc, char **argv) const
{
entry_t * added = new entry_t;
entry_t * matching = NULL;
int index = 0;
assert(index < argc);
if (! parse_date(argv[index++], &added->date)) {
std::cerr << "Error: Bad entry date: " << argv[index - 1]
<< std::endl;
return false;
}
if (index == argc) {
std::cerr << "Error: Too few arguments to 'entry'." << std::endl;
return false;
}
mask_t regexp(argv[index++]);
for (entries_list::const_reverse_iterator i = entries.rbegin();
i != entries.rend();
i++)
if (regexp.match((*i)->payee)) {
matching = *i;
break;
}
added->payee = matching ? matching->payee : regexp.pattern;
if (index == argc) {
std::cerr << "Error: Too few arguments to 'entry'." << std::endl;
return false;
}
if (argv[index][0] == '-' || std::isdigit(argv[index][0])) {
if (! matching) {
std::cerr << "Error: Missing account name for non-matching entry."
<< std::endl;
return false;
}
transaction_t * m_xact, * xact, * first;
m_xact = matching->transactions.front();
amount_t amt(argv[index++]);
first = xact = new transaction_t(added, m_xact->account, amt, amt);
if (xact->amount.commodity->symbol.empty()) {
xact->amount.commodity = m_xact->amount.commodity;
xact->cost.commodity = m_xact->amount.commodity;
}
added->add_transaction(xact);
m_xact = matching->transactions.back();
xact = new transaction_t(added, m_xact->account,
- first->amount, - first->amount);
added->add_transaction(xact);
if ((index + 1) < argc && std::string(argv[index]) == "-from")
if (account_t * acct = find_account(argv[++index]))
added->transactions.back()->account = acct;
} else {
while (index < argc && std::string(argv[index]) != "-from") {
mask_t acct_regex(argv[index++]);
account_t * acct = NULL;
commodity_t * cmdty = NULL;
if (matching) {
for (transactions_list::iterator x
= matching->transactions.begin();
x != matching->transactions.end();
x++) {
if (acct_regex.match((*x)->account->fullname())) {
acct = (*x)->account;
cmdty = (*x)->amount.commodity;
break;
}
}
}
if (! acct)
acct = find_account(acct_regex.pattern);
if (! acct) {
std::cerr << "Error: Could not find account name '"
<< acct_regex.pattern << "'." << std::endl;
return false;
}
if (index == argc) {
std::cerr << "Error: Too few arguments to 'entry'." << std::endl;
return false;
}
amount_t amt(argv[index]++);
transaction_t * xact = new transaction_t(added, acct, amt, amt);
if (! xact->amount.commodity)
xact->amount.commodity = cmdty;
added->add_transaction(xact);
}
if ((index + 1) < argc && std::string(argv[index]) == "-from") {
if (account_t * acct = find_account(argv[++index])) {
transaction_t * xact = new transaction_t(NULL, acct);
added->add_transaction(xact);
}
} else {
if (! matching) {
std::cerr << "Error: Could not figure out the account to draw from."
<< std::endl;
std::exit(1);
}
transaction_t * xact
= new transaction_t(added, matching->transactions.back()->account);
added->add_transaction(xact);
}
}
return added;
}
int parse_ledger_file(char * p, ledger_t * journal)
{
char * sep = std::strrchr(p, '=');

View file

@ -288,6 +288,7 @@ class commodity_t
#define TRANSACTION_NORMAL 0x0
#define TRANSACTION_VIRTUAL 0x1
#define TRANSACTION_BALANCE 0x2
#define TRANSACTION_AUTO 0x4
class transaction_t
{
@ -322,11 +323,13 @@ class entry_t
UNCLEARED, CLEARED, PENDING
};
std::time_t date;
enum entry_state_t state;
std::string code;
std::string payee;
transactions_list transactions;
std::time_t date;
entry_state_t state;
std::string code;
std::string payee;
transactions_list transactions;
entry_t() : date(-1), state(UNCLEARED) {}
~entry_t() {
for (transactions_list::iterator i = transactions.begin();
@ -429,9 +432,14 @@ class ledger_t
account_t * find_account(const std::string& name, bool auto_create = true) {
return master->find_account(name, auto_create);
}
account_t * find_account(const std::string& name) const {
return master->find_account(name, false);
}
bool add_entry(entry_t * entry);
bool remove_entry(entry_t * entry);
entry_t * derive_entry(int argc, char **argv) const;
};
int parse_ledger_file(char * p, ledger_t * journal);

508
main.cc
View file

@ -83,11 +83,15 @@ void balance_report(std::ostream& out,
//////////////////////////////////////////////////////////////////////
//
// The command-line register report
// The command-line register and print report
//
static const std::string reg_fmt
= "%10d %-.20p %-.22N %12.66t %12.80T\n%/%22_ %-.22N %12.66t %12.80T\n";
= "%10d %-.20p %-.22N %12.66t %12.80T\n\
%/ %-.22N %12.66t %12.80T\n";
static const std::string print_fmt
= "\n%10d %X%C%p\n %-34N %12o\n%/ %-34N %12o\n";
static bool show_commodities_revalued = false;
static bool show_commodities_revalued_only = false;
@ -151,11 +155,11 @@ void register_report(std::ostream& out,
bool first = true;
if ((*i)->subitems.size() > 1 && ! show_expanded) {
item_t summary;
summary.date = (*i)->date;
item_t summary(*i);
summary.parent = *i;
summary.account = &splits;
summary.value = 0;
for (items_deque::const_iterator j = (*i)->subitems.begin();
j != (*i)->subitems.end();
j++)
@ -171,7 +175,7 @@ void register_report(std::ostream& out,
if (show) {
if (! show_commodities_revalued_only)
first_line_format.format_elements(out, *i, top);
first_line_format.format_elements(out, &summary, top);
if (show_commodities_revalued)
last_reported = balance;
@ -215,139 +219,6 @@ void register_report(std::ostream& out,
}
bool add_new_entry(int index, int argc, char **argv, ledger_t * ledger)
{
masks_list regexps;
entry_t added;
entry_t * matching = NULL;
added.state = entry_t::UNCLEARED;
assert(index < argc);
if (! parse_date(argv[index++], &added.date)) {
std::cerr << "Error: Bad entry date: " << argv[index - 1]
<< std::endl;
return false;
}
if (index == argc) {
std::cerr << "Error: Too few arguments to 'entry'." << std::endl;
return false;
}
regexps.push_back(mask_t(argv[index++]));
for (entries_list::reverse_iterator i = ledger->entries.rbegin();
i != ledger->entries.rend();
i++)
if (matches(regexps, (*i)->payee)) {
matching = *i;
break;
}
added.payee = matching ? matching->payee : regexps.front().pattern;
if (index == argc) {
std::cerr << "Error: Too few arguments to 'entry'." << std::endl;
return false;
}
if (argv[index][0] == '-' || std::isdigit(argv[index][0])) {
if (! matching) {
std::cerr << "Error: Missing account name for non-matching entry."
<< std::endl;
return false;
}
transaction_t * m_xact, * xact, * first;
m_xact = matching->transactions.front();
amount_t amt(argv[index++]);
first = xact = new transaction_t(&added, m_xact->account, amt, amt);
if (xact->amount.commodity->symbol.empty()) {
xact->amount.commodity = m_xact->amount.commodity;
xact->cost.commodity = m_xact->amount.commodity;
}
added.add_transaction(xact);
m_xact = matching->transactions.back();
xact = new transaction_t(&added, m_xact->account,
- first->amount, - first->amount);
added.add_transaction(xact);
if ((index + 1) < argc && std::string(argv[index]) == "-from")
if (account_t * acct = ledger->find_account(argv[++index]))
added.transactions.back()->account = acct;
} else {
while (index < argc && std::string(argv[index]) != "-from") {
mask_t acct_regex(argv[index++]);
account_t * acct = NULL;
commodity_t * cmdty = NULL;
if (matching) {
for (transactions_list::iterator x
= matching->transactions.begin();
x != matching->transactions.end();
x++) {
if (acct_regex.match((*x)->account->fullname())) {
acct = (*x)->account;
cmdty = (*x)->amount.commodity;
break;
}
}
}
if (! acct)
acct = ledger->find_account(acct_regex.pattern);
if (! acct) {
std::cerr << "Error: Could not find account name '"
<< acct_regex.pattern << "'." << std::endl;
return false;
}
if (index == argc) {
std::cerr << "Error: Too few arguments to 'entry'." << std::endl;
return false;
}
amount_t amt(argv[index]++);
transaction_t * xact = new transaction_t(&added, acct, amt, amt);
if (! xact->amount.commodity)
xact->amount.commodity = cmdty;
added.add_transaction(xact);
}
if ((index + 1) < argc && std::string(argv[index]) == "-from") {
if (account_t * acct = ledger->find_account(argv[++index])) {
transaction_t * xact = new transaction_t(NULL, acct);
added.add_transaction(xact);
}
} else {
if (! matching) {
std::cerr << "Error: Could not figure out the account to draw from."
<< std::endl;
std::exit(1);
}
transaction_t * xact
= new transaction_t(&added, matching->transactions.back()->account);
added.add_transaction(xact);
}
}
// if (added.finalize())
print_textual_entry(std::cout, &added);
return true;
}
void set_price_conversion(const std::string& setting)
{
char buf[128];
@ -468,23 +339,24 @@ static void show_help(std::ostream& out)
<< " balance show balance totals" << std::endl
<< " register display a register for ACCOUNT" << std::endl
<< " print print all ledger entries" << std::endl
<< " equity generate equity ledger for all entries" << std::endl
<< " entry output a newly formed entry, based on arguments" << std::endl
<< " price show the last known price for matching commodities" << std::endl;
<< " equity output equity entries for specified accounts" << std::endl;
}
int main(int argc, char * argv[])
{
std::list<std::string> files;
std::auto_ptr<ledger::ledger_t> journal(new ledger::ledger_t);
std::list<std::string> files;
std::auto_ptr<ledger::node_t> predicate;
std::auto_ptr<ledger::node_t> display_predicate;
std::auto_ptr<ledger::node_t> sort_order;
std::string predicate_string;
ledger::node_t * predicate = NULL;
std::string format_string;
std::string sort_string;
ledger::node_t * sort_order = NULL;
std::string value_expr = "a";
std::string total_expr = "T";
ledger::ledger_t * journal = new ledger::ledger_t;
std::string predicate_string;
std::string display_predicate_string;
std::string format_string;
std::string sort_string;
std::string value_expr = "a";
std::string total_expr = "T";
bool show_subtotals = true;
bool show_expanded = false;
@ -521,11 +393,9 @@ int main(int argc, char * argv[])
if (access(p, R_OK) != -1) {
std::ifstream instr(p);
if (! ledger::read_binary_ledger(instr, std::getenv("LEDGER"),
journal)) {
// We need to throw away what we've read, and create a new
// ledger
delete journal;
journal = new ledger::ledger_t;
journal.get())) {
// Throw away what's been read, and create a new journal
journal.reset(new ledger::ledger_t);
} else {
ledger::cache_dirty = false;
}
@ -652,6 +522,14 @@ int main(int argc, char * argv[])
predicate_string += ")";
break;
case 'd':
if (! display_predicate_string.empty())
display_predicate_string += "&";
display_predicate_string += "(";
display_predicate_string += optarg;
display_predicate_string += ")";
break;
// Commodity reporting
case 'P':
ledger::price_db = optarg;
@ -747,14 +625,14 @@ int main(int argc, char * argv[])
if (files.empty()) {
if (char * p = std::getenv("LEDGER"))
for (p = std::strtok(p, ":"); p; p = std::strtok(NULL, ":"))
entry_count += parse_ledger_file(p, journal);
entry_count += parse_ledger_file(p, journal.get());
} else {
for (std::list<std::string>::iterator i = files.begin();
i != files.end(); i++) {
char buf[4096];
char * p = buf;
std::strcpy(p, (*i).c_str());
entry_count += parse_ledger_file(p, journal);
entry_count += parse_ledger_file(p, journal.get());
}
}
@ -765,7 +643,8 @@ int main(int argc, char * argv[])
const char * path = ledger::price_db.c_str();
std::ifstream db(path);
journal->sources.push_back(path);
entry_count += ledger::parse_textual_ledger(db, journal, journal->master);
entry_count += ledger::parse_textual_ledger(db, journal.get(),
journal->master);
}
}
catch (ledger::error& err) {
@ -780,52 +659,98 @@ int main(int argc, char * argv[])
}
}
// Read the command word, and handle the "entry" command specially,
// without any other processing.
// Read the command word, and then check and simplify it
const std::string command = argv[index++];
std::string command = argv[index++];
if (command == "entry")
return add_new_entry(index, argc, argv, journal) ? 0 : 1;
// Interpret the remaining arguments as regular expressions, used
// for refining report results.
for (; index < argc; index++) {
if (std::strcmp(argv[index], "--") == 0) {
index++;
break;
}
show_expanded = true;
if (! predicate_string.empty())
predicate_string += "&";
if (argv[index][0] == '-') {
predicate_string += "(!/";
predicate_string += argv[index] + 1;
} else {
predicate_string += "(/";
predicate_string += argv[index];
}
predicate_string += "/)";
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 == "entry")
command = "e";
else if (command == "equity")
command = "E";
else {
std::cerr << "Error: Unrecognized command '" << command << "'."
<< std::endl;
return 1;
}
for (; index < argc; index++) {
show_expanded = true;
// Process the remaining command-line arguments
if (! predicate_string.empty())
predicate_string += "&";
std::auto_ptr<ledger::entry_t> new_entry;
if (command == "entry") {
new_entry.reset(journal->derive_entry(argc - index, &argv[index]));
} else {
// Treat the remaining command-line arguments as regular
// expressions, used for refining report results.
if (argv[index][0] == '-') {
predicate_string += "(!//";
predicate_string += argv[index] + 1;
} else {
predicate_string += "(//";
predicate_string += argv[index];
bool have_regexps = index < argc;
bool first = true;
for (; index < argc; index++) {
if (std::strcmp(argv[index], "--") == 0) {
index++;
if (! first && index < argc)
predicate_string += ")";
break;
}
if (! show_expanded && command == "b")
show_expanded = true;
if (first) {
if (! predicate_string.empty())
predicate_string += "&(";
else
predicate_string += "(";
first = false;
} else {
predicate_string += "|";
}
if (argv[index][0] == '-') {
predicate_string += "!/";
predicate_string += argv[index] + 1;
} else {
predicate_string += "/";
predicate_string += argv[index];
}
predicate_string += "/";
}
predicate_string += "/)";
if (index < argc) {
if (! predicate_string.empty())
predicate_string += "&(";
else
predicate_string += "(";
}
first = true;
for (; index < argc; index++) {
if (! show_expanded && command == "b")
show_expanded = true;
if (first)
first = false;
else
predicate_string += "|";
if (argv[index][0] == '-') {
predicate_string += "!//";
predicate_string += argv[index] + 1;
} else {
predicate_string += "//";
predicate_string += argv[index];
}
predicate_string += "/";
}
if (have_regexps)
predicate_string += ")";
}
// Compile the predicate
@ -835,122 +760,110 @@ int main(int argc, char * argv[])
if (debug)
std::cerr << "predicate = " << predicate_string << std::endl;
#endif
predicate = ledger::parse_expr(predicate_string);
predicate.reset(ledger::parse_expr(predicate_string));
}
if (! display_predicate_string.empty()) {
#ifdef DEBUG
if (debug)
std::cerr << "display predicate = " << display_predicate_string
<< std::endl;
#endif
display_predicate.reset(ledger::parse_expr(display_predicate_string));
}
// Compile the sorting string
if (! sort_string.empty())
sort_order = ledger::parse_expr(sort_string);
sort_order.reset(ledger::parse_expr(sort_string));
// Setup the meaning of %t and %T encountered in format strings
ledger::format_t::value_expr = ledger::parse_expr(value_expr);
ledger::format_t::total_expr = ledger::parse_expr(total_expr);
ledger::format_t::value_expr.reset(ledger::parse_expr(value_expr));
ledger::format_t::total_expr.reset(ledger::parse_expr(total_expr));
// Now handle the command that was identified above.
if (command == "print") {
#if 0
if (ledger::item_t * top
= ledger::walk_entries(journal->entries.begin(),
journal->entries.end(), predicate,
show_related, show_inverted)) {
ledger::format_t * format = new ledger::format_t(format_string);
ledger::entry_report(std::cout, top, *format);
#ifdef DEBUG
delete top;
delete format;
#endif
}
#endif
if (command == "p" || command == "e") {
show_related = true;
show_expanded = true;
}
else if (command == "equity") {
#if 0
if (ledger::item_t * top
= ledger::walk_accounts(journal->master, predicate, show_subtotals)) {
ledger::format_t * format = new ledger::format_t(format_string);
ledger::entry_report(std::cout, top, predicate, *format);
#ifdef DEBUG
delete top;
delete format;
#endif
}
#endif
else if (command == "E") {
show_expanded = true;
}
else if (! sort_order && ! show_related &&
(command == "balance" || command == "bal")) {
if (ledger::item_t * top
= ledger::walk_accounts(journal->master, predicate, show_subtotals)) {
ledger::format_t * format
= new ledger::format_t(format_string.empty() ?
ledger::bal_fmt : format_string);
ledger::balance_report(std::cout, top, predicate, sort_order, *format,
show_expanded, show_subtotals);
#ifdef DEBUG
delete format;
delete top;
#endif
}
else if (show_related && command == "r") {
show_inverted = true;
}
else if (command == "balance" || command == "bal") {
if (ledger::item_t * list
= ledger::walk_entries(journal->entries.begin(),
journal->entries.end(), predicate,
show_related, show_inverted))
if (ledger::item_t * top
= ledger::walk_items(list, journal->master, predicate,
show_subtotals)) {
ledger::format_t * format
= new ledger::format_t(format_string.empty() ?
ledger::bal_fmt : format_string);
ledger::balance_report(std::cout, top, predicate, sort_order, *format,
show_expanded, show_subtotals);
#ifdef DEBUG
delete format;
delete top;
delete list;
#endif
}
}
else if (command == "register" || command == "reg") {
if (show_related)
show_inverted = true;
if (ledger::item_t * top
= ledger::walk_entries(journal->entries.begin(),
journal->entries.end(), predicate,
show_related, show_inverted)) {
std::auto_ptr<ledger::item_t> top;
std::auto_ptr<ledger::item_t> list;
if (command == "e") {
top.reset(new ledger::item_t);
ledger::item_t * item = new ledger::item_t(new_entry.get());
for (ledger::transactions_list::const_iterator i
= new_entry->transactions.begin();
i != new_entry->transactions.end();
i++)
item->add_item(new ledger::item_t(*i));
top->add_item(item);
}
else if ((! show_related || ! predicate.get()) &&
(command == "b" || command == "E")) {
top.reset(ledger::walk_accounts(NULL, journal->master, predicate.get(),
command != "E" && show_subtotals,
command == "E"));
}
else {
top.reset(ledger::walk_entries(journal->entries.begin(),
journal->entries.end(), predicate.get(),
show_related, show_inverted));
if (top.get() && command == "b" || command == "E") {
list.reset(top.release());
top.reset(ledger::walk_accounts(list.get(), journal->master,
predicate.get(),
command != "E" && show_subtotals,
command == "E"));
}
}
if (top.get()) {
const char * f;
if (! format_string.empty())
f = format_string.c_str();
else if (command == "b")
f = ledger::bal_fmt.c_str();
else if (command == "r")
f = ledger::reg_fmt.c_str();
else
f = ledger::print_fmt.c_str();
if (command == "b") {
std::auto_ptr<ledger::format_t> format(new ledger::format_t(f));
ledger::balance_report(std::cout, top.get(), display_predicate.get(),
sort_order.get(), *format, show_expanded,
show_subtotals);
} else {
std::string first_line_format;
std::string next_lines_format;
const char * f = (format_string.empty() ?
ledger::reg_fmt.c_str() : format_string.c_str());
if (const char * p = std::strstr(f, "%/")) {
first_line_format = std::string(f, 0, p - f);
next_lines_format = std::string(p + 2);
} else {
first_line_format = format_string;
next_lines_format = format_string;
first_line_format = next_lines_format = f;
}
ledger::format_t * format = new ledger::format_t(first_line_format);
ledger::format_t * nformat = new ledger::format_t(next_lines_format);
std::auto_ptr<ledger::format_t>
format(new ledger::format_t(first_line_format));
std::auto_ptr<ledger::format_t>
nformat(new ledger::format_t(next_lines_format));
ledger::register_report(std::cout, top, predicate, sort_order,
*format, *nformat, show_expanded);
#ifdef DEBUG
delete nformat;
delete format;
delete top;
#endif
ledger::register_report(std::cout, top.get(), display_predicate.get(),
sort_order.get(), *format, *nformat,
show_expanded);
}
}
else {
std::cerr << "Error: Unrecognized command '" << command << "'."
<< std::endl;
return 1;
}
// Save the cache, if need be
@ -958,31 +871,10 @@ int main(int argc, char * argv[])
if (const char * p = std::getenv("LEDGER_CACHE")) {
std::ofstream outstr(p);
assert(std::getenv("LEDGER"));
ledger::write_binary_ledger(outstr, journal, std::getenv("LEDGER"));
ledger::write_binary_ledger(outstr, journal.get(),
std::getenv("LEDGER"));
}
#ifdef DEBUG
delete journal;
if (predicate)
delete predicate;
if (sort_order)
delete sort_order;
if (ledger::format_t::value_expr)
delete ledger::format_t::value_expr;
if (ledger::format_t::total_expr)
delete ledger::format_t::total_expr;
// jww (2004-07-30): This should be moved into some kind of
// "ledger::shutdown" function.
for (ledger::commodities_map::iterator i
= ledger::commodity_t::commodities.begin();
i != ledger::commodity_t::commodities.end();
i++)
delete (*i).second;
#endif
return 0;
}

View file

@ -294,7 +294,8 @@ void automated_transaction_t::extend_entry(entry_t * entry)
amt = (*t)->amount;
transaction_t * xact
= new transaction_t(entry, (*t)->account, amt, amt, (*t)->flags);
= new transaction_t(entry, (*t)->account, amt, amt,
(*t)->flags | TRANSACTION_AUTO);
entry->add_transaction(xact);
}
}
@ -603,8 +604,9 @@ unsigned int parse_textual_ledger(std::istream& in, ledger_t * journal,
amt.parse(buf);
time_commodity = amt.commodity;
transaction_t * xact = new transaction_t(curr, last_account, amt, amt,
TRANSACTION_VIRTUAL);
transaction_t * xact
= new transaction_t(curr, last_account, amt, amt,
TRANSACTION_VIRTUAL);
curr->add_transaction(xact);
if (! finalize_entry(curr) || ! journal->add_entry(curr))
@ -777,123 +779,4 @@ unsigned int parse_textual_ledger(std::istream& in, ledger_t * journal,
return count;
}
//////////////////////////////////////////////////////////////////////
//
// Textual ledger printing code
//
void print_transaction(std::ostream& out, transaction_t * xact,
bool display_amount, bool display_cost)
{
std::ostringstream s;
s << *(xact->account);
std::string acct_name = s.str();
if (xact->flags & TRANSACTION_VIRTUAL) {
if (xact->flags & TRANSACTION_BALANCE)
acct_name = std::string("[") + acct_name + "]";
else
acct_name = std::string("(") + acct_name + ")";
}
out.width(30);
out.fill(' ');
out << std::left << acct_name;
if (xact->amount && display_amount) {
out << " ";
out.width(12);
out.fill(' ');
std::ostringstream s;
s << xact->amount;
out << std::right << s.str();
}
if (xact->amount && display_cost &&
xact->amount != xact->cost) {
out << " @ ";
out << xact->cost / xact->amount;
}
if (! xact->note.empty())
out << " ; " << xact->note;
out << std::endl;
}
void print_textual_entry(std::ostream& out, entry_t * entry, bool shortcut)
{
char buf[32];
std::strftime(buf, 31, "%Y/%m/%d ", std::gmtime(&entry->date));
out << buf;
if (entry->state == entry_t::CLEARED)
out << "* ";
if (! entry->code.empty())
out << '(' << entry->code << ") ";
if (! entry->payee.empty())
out << entry->payee;
out << std::endl;
const commodity_t * comm = NULL;
int size = 0;
for (transactions_list::const_iterator x
= entry->transactions.begin();
x != entry->transactions.end();
x++) {
if ((*x)->flags & TRANSACTION_VIRTUAL &&
! ((*x)->flags & TRANSACTION_BALANCE))
continue;
if (! comm)
comm = (*x)->amount.commodity;
else if (comm != (*x)->amount.commodity)
shortcut = false;
size++;
}
if (shortcut && size != 2)
shortcut = false;
for (transactions_list::const_iterator x
= entry->transactions.begin();
x != entry->transactions.end();
x++) {
out << " ";
print_transaction(out, *x,
(! shortcut || x == entry->transactions.begin() ||
((*x)->flags & TRANSACTION_VIRTUAL &&
! ((*x)->flags & TRANSACTION_BALANCE))),
size != 2);
}
out << std::endl;
}
void print_textual_ledger(std::ostream& out, ledger_t * journal,
bool shortcut)
{
for (entries_list::const_iterator i = journal->entries.begin();
i != journal->entries.end();
i++)
print_textual_entry(out, *i, shortcut);
}
} // namespace ledger
#ifdef PARSE_TEST
int main(int argc, char *argv[])
{
journal.sources.push_back(argv[1]);
std::ifstream stream(argv[1]);
ledger::ledger_t journal;
int count = parse_textual_ledger(stream, &journal, journal.master);
std::cout << "Read " << count << " entries." << std::endl;
print_textual_ledger(std::cout, &journal, true);
}
#endif // PARSE_TEST

View file

@ -13,12 +13,6 @@ extern bool parse_date_mask(const char * date_str, struct std::tm * result);
extern bool parse_date(const char * date_str, std::time_t * result,
const int year = -1);
extern void print_textual_ledger(std::ostream& out, ledger_t * ledger,
bool shortcut = true);
extern void print_textual_entry(std::ostream& out, entry_t * entry,
bool shortcut = true);
} // namespace ledger
#endif // _TEXTUAL_H