diff options
-rw-r--r-- | amount.cc | 4 | ||||
-rw-r--r-- | ledger.cc | 194 | ||||
-rw-r--r-- | ledger.h | 13 | ||||
-rw-r--r-- | parse.cc | 176 | ||||
-rw-r--r-- | reports.cc | 119 |
5 files changed, 325 insertions, 181 deletions
@@ -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; @@ -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); @@ -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); }; @@ -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; } @@ -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 |