This commit is contained in:
John Wiegley 2003-10-11 22:59:43 +00:00
parent 7ebe72c054
commit 854b719f1e
5 changed files with 325 additions and 181 deletions

View file

@ -49,6 +49,10 @@ class gmp_amount : public amount
return quantity_comm;
}
virtual void set_commdty(commodity * comm) {
quantity_comm = comm;
}
virtual amount * copy() const;
virtual amount * value(amount *) const;
virtual amount * street(bool get_quotes) const;

194
ledger.cc
View file

@ -136,6 +136,184 @@ bool entry::validate(bool show_unaccounted) const
return balance.is_zero(); // must balance to 0.0
}
bool entry::finalize(bool do_compute)
{
// Scan through and compute the total balance for the entry. This
// is used for auto-calculating the value of entries with no cost,
// and the per-unit price of unpriced commodities.
totals balance;
for (std::list<transaction *>::iterator x = xacts.begin();
x != xacts.end();
x++)
if ((*x)->cost && ! (*x)->is_virtual) {
amount * value = (*x)->cost->value();
balance.credit(value);
delete value;
}
// If one transaction is of a different commodity than the others,
// and it has no per-unit price, determine its price by dividing
// the unit count into the value of the balance.
//
// NOTE: We don't do this for prefix-style or joined-symbol
// commodities. Also, do it for the last eligible commodity first,
// if it meets the criteria.
if (! balance.amounts.empty() && balance.amounts.size() == 2) {
for (std::list<transaction *>::iterator x = xacts.begin();
x != xacts.end();
x++) {
if ((*x)->is_virtual)
continue;
if (! (*x)->cost->has_price() &&
! (*x)->cost->commdty()->prefix &&
(*x)->cost->commdty()->separate) {
for (totals::iterator i = balance.amounts.begin();
i != balance.amounts.end();
i++) {
if ((*i).second->commdty() != (*x)->cost->commdty()) {
(*x)->cost->set_value((*i).second);
break;
}
}
break;
}
}
}
// Walk through each of the transactions, fixing up any that we
// can, and performing any on-the-fly calculations.
bool empty_allowed = true;
for (std::list<transaction *>::iterator x = xacts.begin();
x != xacts.end();
x++) {
if ((*x)->is_virtual || (*x)->cost)
continue;
if (! empty_allowed || balance.amounts.empty() ||
balance.amounts.size() != 1) {
std::cerr << "Error, line " //<< linenum
<< ": Transaction entry is lacking an amount."
<< std::endl;
return false;
}
empty_allowed = false;
// If one transaction gives no value at all -- and all the
// rest are of the same commodity -- then its value is the
// inverse of the computed value of the others.
totals::iterator i = balance.amounts.begin();
(*x)->cost = (*i).second->value();
(*x)->cost->negate();
if (do_compute)
(*x)->acct->balance.credit((*x)->cost);
}
// If virtual accounts are being supported, walk through the
// transactions and create new virtual transactions for all that
// apply.
for (book::virtual_map_iterator m = main_ledger->virtual_mapping.begin();
m != main_ledger->virtual_mapping.end();
m++) {
std::list<transaction *> xacts;
for (std::list<transaction *>::iterator x = xacts.begin();
x != xacts.end();
x++) {
if ((*x)->is_virtual ||
! ledger::matches(*((*m).first), (*x)->acct->as_str()))
continue;
for (std::list<transaction *>::iterator i = (*m).second->begin();
i != (*m).second->end();
i++) {
transaction * t;
if ((*i)->cost->commdty()) {
t = new transaction((*i)->acct, (*i)->cost);
} else {
amount * temp = (*x)->cost->value();
t = new transaction((*i)->acct, temp->value((*i)->cost));
delete temp;
}
t->is_virtual = (*i)->is_virtual;
t->must_balance = (*i)->must_balance;
// If there is already a virtual transaction for the
// account under consideration, and it's `must_balance'
// flag matches, then simply add this amount to that
// transaction.
bool added = false;
for (std::list<transaction *>::iterator y = xacts.begin();
y != xacts.end();
y++) {
if ((*y)->is_virtual && (*y)->acct == t->acct &&
(*y)->must_balance == t->must_balance) {
(*y)->cost->credit(t->cost);
delete t;
added = true;
break;
}
}
if (! added)
for (std::list<transaction *>::iterator y = xacts.begin();
y != xacts.end();
y++) {
if ((*y)->is_virtual && (*y)->acct == t->acct &&
(*y)->must_balance == t->must_balance) {
(*y)->cost->credit(t->cost);
delete t;
added = true;
break;
}
}
if (! added)
xacts.push_back(t);
}
}
// Add to the current entry any virtual transactions which were
// created. We have to do this afterward, otherwise the
// iteration above is screwed up if we try adding new
// transactions during the traversal.
for (std::list<transaction *>::iterator x = xacts.begin();
x != xacts.end();
x++) {
xacts.push_back(*x);
if (do_compute)
(*x)->acct->balance.credit((*x)->cost);
}
}
// Compute the balances again, just to make sure it all comes out
// right (i.e., zero for every commodity).
if (! validate()) {
std::cerr << "Error, line " //<< (linenum - 1)
<< ": Failed to balance the following transaction:"
<< std::endl;
validate(true);
return false;
}
return true;
}
bool entry::matches(const regexps_map& regexps) const
{
if (regexps.empty() || (ledger::matches(regexps, code) ||
@ -385,6 +563,22 @@ book::~book()
delete *i;
}
account * book::re_find_account(const std::string& regex)
{
mask acct_regex(regex);
for (entries_list_reverse_iterator i = entries.rbegin();
i != entries.rend();
i++)
for (std::list<transaction *>::iterator x = (*i)->xacts.begin();
x != (*i)->xacts.end();
x++)
if (acct_regex.match((*x)->acct->as_str()))
return (*x)->acct;
return NULL;
}
account * book::find_account(const std::string& name, bool create)
{
accounts_map_iterator i = accounts_cache.find(name);

View file

@ -1,5 +1,5 @@
#ifndef _LEDGER_H
#define _LEDGER_H "$Revision: 1.25 $"
#define _LEDGER_H "$Revision: 1.26 $"
//////////////////////////////////////////////////////////////////////
//
@ -66,6 +66,8 @@ class amount
virtual ~amount() {}
virtual commodity * commdty() const = 0;
virtual void set_commdty(commodity *) = 0;
virtual amount * copy() const = 0;
virtual amount * value(amount * pr = NULL) const = 0;
virtual amount * street(bool get_quotes) const = 0;
@ -179,6 +181,7 @@ class entry
bool matches(const regexps_map& regexps) const;
bool validate(bool show_unaccounted = false) const;
bool finalize(bool do_compute = false);
void print(std::ostream& out, bool shortcut = true) const;
};
@ -189,9 +192,10 @@ struct cmp_entry_date {
}
};
typedef std::vector<entry *> entries_list;
typedef entries_list::iterator entries_list_iterator;
typedef entries_list::const_iterator entries_list_const_iterator;
typedef std::vector<entry *> entries_list;
typedef entries_list::iterator entries_list_iterator;
typedef entries_list::reverse_iterator entries_list_reverse_iterator;
typedef entries_list::const_iterator entries_list_const_iterator;
class totals
@ -280,6 +284,7 @@ class book
}
void print(std::ostream& out, regexps_map& regexps, bool shortcut) const;
account * re_find_account(const std::string& regex);
account * find_account(const std::string& name, bool create = true);
};

176
parse.cc
View file

@ -232,185 +232,11 @@ entry * parse_entry(std::istream& in, book * ledger)
// If there were no transactions, throw away the entry
if (curr->xacts.empty()) {
if (curr->xacts.empty() || ! curr->finalize(do_compute)) {
delete curr;
return NULL;
}
// Scan through and compute the total balance for the entry. This
// is used for auto-calculating the value of entries with no cost,
// and the per-unit price of unpriced commodities.
totals balance;
for (std::list<transaction *>::iterator x = curr->xacts.begin();
x != curr->xacts.end();
x++)
if ((*x)->cost && ! (*x)->is_virtual) {
amount * value = (*x)->cost->value();
balance.credit(value);
delete value;
}
// If one transaction is of a different commodity than the others,
// and it has no per-unit price, determine its price by dividing
// the unit count into the value of the balance.
//
// NOTE: We don't do this for prefix-style or joined-symbol
// commodities. Also, do it for the last eligible commodity first,
// if it meets the criteria.
if (! balance.amounts.empty() && balance.amounts.size() == 2) {
for (std::list<transaction *>::iterator x = curr->xacts.begin();
x != curr->xacts.end();
x++) {
if ((*x)->is_virtual)
continue;
if (! (*x)->cost->has_price() &&
! (*x)->cost->commdty()->prefix &&
(*x)->cost->commdty()->separate) {
for (totals::iterator i = balance.amounts.begin();
i != balance.amounts.end();
i++) {
if ((*i).second->commdty() != (*x)->cost->commdty()) {
(*x)->cost->set_value((*i).second);
break;
}
}
break;
}
}
}
// Walk through each of the transactions, fixing up any that we
// can, and performing any on-the-fly calculations.
bool empty_allowed = true;
for (std::list<transaction *>::iterator x = curr->xacts.begin();
x != curr->xacts.end();
x++) {
if ((*x)->is_virtual || (*x)->cost)
continue;
if (! empty_allowed || balance.amounts.empty() ||
balance.amounts.size() != 1) {
std::cerr << "Error, line " << linenum
<< ": Transaction entry is lacking an amount."
<< std::endl;
return NULL;
}
empty_allowed = false;
// If one transaction gives no value at all -- and all the
// rest are of the same commodity -- then its value is the
// inverse of the computed value of the others.
totals::iterator i = balance.amounts.begin();
(*x)->cost = (*i).second->value();
(*x)->cost->negate();
if (do_compute)
(*x)->acct->balance.credit((*x)->cost);
}
// If virtual accounts are being supported, walk through the
// transactions and create new virtual transactions for all that
// apply.
for (book::virtual_map_iterator m = ledger->virtual_mapping.begin();
m != ledger->virtual_mapping.end();
m++) {
std::list<transaction *> xacts;
for (std::list<transaction *>::iterator x = curr->xacts.begin();
x != curr->xacts.end();
x++) {
if ((*x)->is_virtual ||
! ledger::matches(*((*m).first), (*x)->acct->as_str()))
continue;
for (std::list<transaction *>::iterator i = (*m).second->begin();
i != (*m).second->end();
i++) {
transaction * t;
if ((*i)->cost->commdty()) {
t = new transaction((*i)->acct, (*i)->cost);
} else {
amount * temp = (*x)->cost->value();
t = new transaction((*i)->acct, temp->value((*i)->cost));
delete temp;
}
t->is_virtual = (*i)->is_virtual;
t->must_balance = (*i)->must_balance;
// If there is already a virtual transaction for the
// account under consideration, and it's `must_balance'
// flag matches, then simply add this amount to that
// transaction.
bool added = false;
for (std::list<transaction *>::iterator y = xacts.begin();
y != xacts.end();
y++) {
if ((*y)->is_virtual && (*y)->acct == t->acct &&
(*y)->must_balance == t->must_balance) {
(*y)->cost->credit(t->cost);
delete t;
added = true;
break;
}
}
if (! added)
for (std::list<transaction *>::iterator y = curr->xacts.begin();
y != curr->xacts.end();
y++) {
if ((*y)->is_virtual && (*y)->acct == t->acct &&
(*y)->must_balance == t->must_balance) {
(*y)->cost->credit(t->cost);
delete t;
added = true;
break;
}
}
if (! added)
xacts.push_back(t);
}
}
// Add to the current entry any virtual transactions which were
// created. We have to do this afterward, otherwise the
// iteration above is screwed up if we try adding new
// transactions during the traversal.
for (std::list<transaction *>::iterator x = xacts.begin();
x != xacts.end();
x++) {
curr->xacts.push_back(*x);
if (do_compute)
(*x)->acct->balance.credit((*x)->cost);
}
}
// Compute the balances again, just to make sure it all comes out
// right (i.e., zero for every commodity).
if (! curr->validate()) {
std::cerr << "Error, line " << (linenum - 1)
<< ": Failed to balance the following transaction:"
<< std::endl;
curr->print(std::cerr);
curr->validate(true);
delete curr;
return NULL;
}
return curr;
}

View file

@ -535,8 +535,9 @@ int main(int argc, char * argv[])
// Compile the list of specified regular expressions, which can be
// specified after the command, or using the '-i FILE' option
for (; index < argc; index++)
regexps.push_back(mask(argv[index]));
if (command != "add")
for (; index < argc; index++)
regexps.push_back(mask(argv[index]));
// Parse the ledger
@ -590,6 +591,120 @@ int main(int argc, char * argv[])
else if (command == "equity") {
equity_ledger(std::cout, regexps);
}
else if (command == "add") {
entry added, * matching = NULL;
if (! parse_date(argv[index++], &added.date)) {
std::cerr << "Error: Bad add date: " << argv[index - 1]
<< std::endl;
return 1;
}
added.cleared = show_cleared;
if (index == argc) {
std::cerr << "Error: Too few arguments to 'add'." << std::endl;
return 1;
}
regexps.clear();
regexps.push_back(mask(argv[index++]));
for (entries_list_reverse_iterator i = main_ledger->entries.rbegin();
i != main_ledger->entries.rend();
i++) {
if ((*i)->matches(regexps)) {
matching = *i;
break;
}
}
added.desc = matching ? matching->desc : regexps.front().pattern;
if (index == argc) {
std::cerr << "Error: Too few arguments to 'add'." << std::endl;
return 1;
}
if (argv[index][0] == '-' || std::isdigit(argv[index][0])) {
if (! matching) {
std::cerr << "Error: Missing account name for non-matching entry."
<< std::endl;
return 1;
}
transaction * m_xact, * xact, * first;
m_xact = matching->xacts.front();
first = xact = new transaction();
xact->acct = m_xact->acct;
xact->cost = create_amount(argv[index++]);
xact->cost->set_commdty(m_xact->cost->commdty());
added.xacts.push_back(xact);
m_xact = matching->xacts.back();
xact = new transaction();
xact->acct = m_xact->acct;
xact->cost = first->cost->copy();
xact->cost->negate();
added.xacts.push_back(xact);
if ((index + 1) < argc && std::string(argv[index]) == "-from")
if (account * acct = main_ledger->re_find_account(argv[++index]))
added.xacts.back()->acct = acct;
} else {
while (index < argc && std::string(argv[index]) != "-from") {
transaction * xact = new transaction();
mask acct_regex(argv[index++]);
account * acct = NULL;
for (std::list<transaction *>::iterator x = matching->xacts.begin();
x != matching->xacts.end();
x++) {
if (acct_regex.match((*x)->acct->as_str())) {
acct = (*x)->acct;
break;
}
}
if (acct)
xact->acct = acct;
else
xact->acct = main_ledger->re_find_account(acct_regex.pattern);
if (! xact->acct) {
std::cerr << "Error: Could not find account name '"
<< acct_regex.pattern << "'." << std::endl;
return 1;
}
if (index == argc) {
std::cerr << "Error: Too few arguments to 'add'." << std::endl;
return 1;
}
xact->cost = create_amount(argv[index++]);
added.xacts.push_back(xact);
}
if ((index + 1) < argc && std::string(argv[index]) == "-from")
if (account * acct = main_ledger->re_find_account(argv[++index])) {
transaction * xact = new transaction();
xact->acct = acct;
xact->cost = NULL;
added.xacts.push_back(xact);
}
}
if (added.finalize())
added.print(std::cout);
}
#ifdef DEBUG
// Ordinarily, deleting the main ledger isn't necessary, since the