c
This commit is contained in:
parent
7ebe72c054
commit
854b719f1e
5 changed files with 325 additions and 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;
|
||||
|
|
|
|||
194
ledger.cc
194
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);
|
||||
|
|
|
|||
13
ledger.h
13
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);
|
||||
};
|
||||
|
||||
|
|
|
|||
176
parse.cc
176
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
119
reports.cc
119
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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue