summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--amount.cc4
-rw-r--r--ledger.cc194
-rw-r--r--ledger.h13
-rw-r--r--parse.cc176
-rw-r--r--reports.cc119
5 files changed, 325 insertions, 181 deletions
diff --git a/amount.cc b/amount.cc
index 1f76d64f..dd2a3cc8 100644
--- a/amount.cc
+++ b/amount.cc
@@ -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;
diff --git a/ledger.cc b/ledger.cc
index b148ed3b..1a6e0ae0 100644
--- a/ledger.cc
+++ b/ledger.cc
@@ -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);
diff --git a/ledger.h b/ledger.h
index b7fe5e06..8e917447 100644
--- a/ledger.h
+++ b/ledger.h
@@ -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);
};
diff --git a/parse.cc b/parse.cc
index 5cdfb9e3..5b1ed5ab 100644
--- a/parse.cc
+++ b/parse.cc
@@ -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;
}
diff --git a/reports.cc b/reports.cc
index b050d33f..ad53e270 100644
--- a/reports.cc
+++ b/reports.cc
@@ -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