diff options
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | amount.cc | 53 | ||||
-rw-r--r-- | balance.cc | 13 | ||||
-rw-r--r-- | ledger.cc | 25 | ||||
-rw-r--r-- | ledger.h | 8 | ||||
-rw-r--r-- | ledger.texi | 43 | ||||
-rw-r--r-- | main.cc | 22 | ||||
-rw-r--r-- | parse.cc | 170 |
8 files changed, 185 insertions, 151 deletions
@@ -17,7 +17,7 @@ OBJS = $(patsubst %.cc,%.o,$(CODE)) CFLAGS = -Wall -ansi -pedantic #DFLAGS = -O3 -fomit-frame-pointer -DFLAGS = -g # -O2 # -pg +DFLAGS = -g -O2 # -pg INCS = -I/usr/include/xmltok LIBS = -lgmpxx -lgmp -lpcre @@ -59,13 +59,10 @@ class gmp_amount : public amount virtual operator bool() const; - virtual void credit(const amount * other) { - *this += *other; - } virtual void negate() { mpz_ui_sub(quantity, 0, quantity); } - virtual void operator+=(const amount& other); + virtual void credit(const amount * other); virtual void parse(const char * num) { *this = num; @@ -211,30 +208,32 @@ amount * gmp_amount::street() const extern bool get_quotes; for (int cycles = 0; cycles < 10; cycles++) { - totals::iterator pi = main_ledger.prices.amounts.find(amt->comm_symbol()); + totals::iterator pi = + main_ledger.prices.amounts.find(amt->comm_symbol()); if (pi == main_ledger.prices.amounts.end()) { - if (get_quotes && amt->comm_symbol() != DEFAULT_COMMODITY) { - using namespace std; - - char buf[256]; - buf[0] = '\0'; - - if (FILE * fp = popen((std::string("getquote ") + - amt->comm_symbol()).c_str(), "r")) { - if (feof(fp) || ! fgets(buf , 255, fp)) { - fclose(fp); - break; - } + using namespace std; + + if (! get_quotes) + break; + + char buf[256]; + buf[0] = '\0'; + + if (FILE * fp = popen((std::string("getquote ") + + amt->comm_symbol()).c_str(), "r")) { + if (feof(fp) || ! fgets(buf , 255, fp)) { fclose(fp); + break; } + fclose(fp); + } - if (buf[0]) { - char * p = strchr(buf, '\n'); - if (p) *p = '\0'; + if (buf[0]) { + char * p = strchr(buf, '\n'); + if (p) *p = '\0'; - main_ledger.record_price((amt->comm_symbol() + "=" + buf).c_str()); - continue; - } + main_ledger.record_price((amt->comm_symbol() + "=" + buf).c_str()); + continue; } break; } else { @@ -598,11 +597,11 @@ amount& gmp_amount::operator=(const char * num) return *this; } -void gmp_amount::operator+=(const amount& _other) +void gmp_amount::credit(const amount * value) { - const gmp_amount& other = dynamic_cast<const gmp_amount&>(_other); - assert(quantity_comm == other.quantity_comm); - mpz_add(quantity, quantity, other.quantity); + const gmp_amount * val = dynamic_cast<const gmp_amount *>(value); + assert(quantity_comm == val->quantity_comm); + mpz_add(quantity, quantity, val->quantity); } } // namespace ledger @@ -86,23 +86,12 @@ void report_balances(int argc, char **argv, std::ostream& out) optind = 1; int c; - while (-1 != (c = getopt(argc, argv, "sSnFG:"))) { + while (-1 != (c = getopt(argc, argv, "sSnF"))) { switch (char(c)) { case 's': show_children = true; break; case 'S': show_empty = true; break; case 'n': no_subtotals = true; break; case 'F': full_names = true; break; - -#ifdef HUQUQULLAH - case 'G': { - double gold = std::atof(optarg); - gold = 1 / gold; - char buf[256]; - std::sprintf(buf, DEFAULT_COMMODITY "=%f troy", gold); - main_ledger.record_price(buf); - break; - } -#endif } } @@ -27,14 +27,20 @@ void entry::print(std::ostream& out, bool shortcut) const if (shortcut && (xacts.size() != 2 || - xacts.front()->cost->comm() != xacts.back()->cost->comm())) { + xacts.front()->cost->comm() != xacts.back()->cost->comm())) shortcut = false; - } for (std::list<transaction *>::const_iterator x = xacts.begin(); x != xacts.end(); x++) { - out << " "; +#ifdef HUQUQULLAH + if ((*x)->acct->exempt_or_necessary && + (! shortcut || ! ledger::matches(main_ledger.huquq_categories, + (*x)->acct->as_str()))) + out << " !"; + else +#endif + out << " "; out.width(30); out << std::left << (*x)->acct->as_str(); @@ -134,19 +140,6 @@ void totals::print(std::ostream& out, int width) const } } -amount * totals::value(const std::string& commodity) const -{ - // Render all of the amounts into the given commodity. This - // requires known prices for each commodity. - - amount * total = create_amount((commodity + " 0.00").c_str()); - - for (const_iterator i = amounts.begin(); i != amounts.end(); i++) - *total += *((*i).second); - - return total; -} - // Print out the entire ledger that was read in, sorted by date. // This can be used to "wash" ugly ledger files. @@ -1,5 +1,5 @@ #ifndef _LEDGER_H -#define _LEDGER_H "$Revision: 1.13 $" +#define _LEDGER_H "$Revision: 1.14 $" ////////////////////////////////////////////////////////////////////// // @@ -104,8 +104,8 @@ struct commodity commodity() : prefix(false), separate(true), thousands(false), european(false) {} - commodity(const std::string& sym, bool pre, bool sep, - bool thou, bool euro, int prec); + commodity(const std::string& sym, bool pre = false, bool sep = true, + bool thou = true, bool euro = false, int prec = 2); }; typedef std::map<const std::string, commodity *> commodities_t; @@ -135,7 +135,6 @@ class amount virtual void credit(const amount * other) = 0; virtual void negate() = 0; - virtual void operator+=(const amount& other) = 0; // String conversion routines @@ -263,7 +262,6 @@ struct totals void print(std::ostream& out, int width) const; // Returns an allocated entity - amount * value(const std::string& comm) const; amount * sum(const std::string& comm) { return amounts[comm]; } diff --git a/ledger.texi b/ledger.texi index cb180eb0..8e1f2d8b 100644 --- a/ledger.texi +++ b/ledger.texi @@ -1,5 +1,5 @@ \input texinfo @c -*-texinfo-*- -@comment $Id: ledger.texi,v 1.4 2003/10/01 04:42:13 johnw Exp $ +@comment $Id: ledger.texi,v 1.5 2003/10/01 21:55:40 johnw Exp $ @comment %**start of header @setfilename ledger.info @@ -287,6 +287,47 @@ Euro=DM 0.75 This is a roundabout way of reporting AAPL shares in their Deutsch Mark equivalent. +@section Accounts and Inventories + +Since @code{ledger}'s accounts and commodity system is so flexible, +you can have accounts that don't really exist, and use commodities +that no one else recognizes. For example, let's say you are buying +and selling various items in EverQuest, and want to keep track of them +using a ledger. Just add items of whatever quantity you wish into +your EverQuest account: + +@example +9/29 Get some stuff at the Inn + Places:Black's Tavern -3 Apples + Places:Black's Tavern -5 Steaks + EverQuest:Inventory +@end example + +Now your EverQuest:Inventory has 3 apples and 5 steaks in it. The +amounts are negative, because you are taking @emph{from} Black's +Tavern in order to credit your Inventory account. Note that you don't +have to use ``Places:Black's Tavern'' as the source account. You +could use ``EverQuest:System'' to represent the fact that you acquired +them online. The only purpose for choosing one kind of source account +over another is for generate more informative reports later on. The +more you know, the better analysis you can perform. + +If you later sell some of these items to another player, the entry +would look like: + +@example +10/2 Sturm Brightblade + EverQuest:Inventory -2 Steaks + EverQuest:Inventory 15 Gold +@end example + +Now you've turned 2 steaks into 15 gold, courtesy of your customer, +Sturm Brightblade. + +Note that if you're playing on a system where ``Gold'' is the standard +currency, you should use the @samp{-D} flag to tell @code{ledger} that +that is the default commodity. + @chapter Using @code{ledger} @chapter Computing Huqúqu'lláh @@ -112,11 +112,6 @@ static bool parse_date(const char * date_str, std::time_t * result) int main(int argc, char *argv[]) { - // Global defaults - - commodity * usd = new commodity("$", true, false, true, false, 2); - main_ledger.commodities.insert(commodities_entry("USD", usd)); - // Parse the command-line options std::istream * file = NULL; @@ -129,7 +124,7 @@ int main(int argc, char *argv[]) show_cleared = false; int c; - while (-1 != (c = getopt(argc, argv, "+b:e:d:cChHwf:i:p:P"))) { + while (-1 != (c = getopt(argc, argv, "+b:e:d:D:cChHwf:i:p:P"))) { switch (char(c)) { case 'b': case 'e': { @@ -288,24 +283,9 @@ int main(int argc, char *argv[]) if (compute_huquq) { main_ledger.compute_huquq = true; - main_ledger.huquq_commodity = new commodity("H", true, true, - true, false, 2); - - // The allocation causes it to be inserted into the - // main_ledger.commodities mapping. - new commodity("mithqal", false, true, true, false, 1); read_regexps(".huquq", main_ledger.huquq_categories); - main_ledger.record_price("H=" DEFAULT_COMMODITY "0.19"); - - bool save_use_warnings = use_warnings; - use_warnings = false; - main_ledger.record_price("troy=8.5410148523 mithqal"); - use_warnings = save_use_warnings; - - main_ledger.huquq = create_amount("H 1.00"); - main_ledger.huquq_account = main_ledger.find_account("Huququ'llah"); main_ledger.huquq_expenses_account = main_ledger.find_account("Expenses:Huququ'llah"); @@ -36,95 +36,129 @@ static void finalize_entry(entry * curr, bool compute_balances) { assert(curr); - // Certain shorcuts are allowed in the case of exactly two - // transactions. + // If there were no transactions, it's definitely an error! - if (! curr->xacts.empty() && curr->xacts.size() == 2) { - transaction * first = curr->xacts.front(); - transaction * second = curr->xacts.back(); + if (curr->xacts.empty()) { + std::cerr << "Error, line " << (linenum - 1) + << ": Entry has no transactions!" << std::endl; + return; + } - // If one transaction gives no value at all, then its value is - // the inverse of the computed value of the other. + // Scan through and compute the total balance for the entry. - if (! first->cost && second->cost) { - first->cost = second->cost->value(); - first->cost->negate(); + totals balance; - if (compute_balances) - first->acct->balance.credit(first->cost); - } - else if (! second->cost && first->cost) { - second->cost = first->cost->value(); - second->cost->negate(); + for (std::list<transaction *>::iterator x = curr->xacts.begin(); + x != curr->xacts.end(); + x++) + if ((*x)->cost) + balance.credit((*x)->cost->value()); - if (compute_balances) - second->acct->balance.credit(second->cost); - } - else if (first->cost && second->cost) { - // If one transaction is of a different commodity than the - // other, and it has no per-unit price, and its not of the - // default commodity, then determine its price by dividing the - // unit count into the total, to balance the transaction. - - if (first->cost->comm() != second->cost->comm()) { - if (! second->cost->has_price() && - second->cost->comm_symbol() != DEFAULT_COMMODITY) { - second->cost->set_value(first->cost); - } - else if (! first->cost->has_price() && - first->cost->comm_symbol() != DEFAULT_COMMODITY) { - first->cost->set_value(second->cost); + // 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)->cost->has_price() && + ! (*x)->cost->comm()->prefix && + (*x)->cost->comm()->separate) { + for (totals::iterator i = balance.amounts.begin(); + i != balance.amounts.end(); + i++) { + if ((*i).second->comm() != (*x)->cost->comm()) { + (*x)->cost->set_value((*i).second); + break; + } } + break; } } } - if (! curr->validate()) { - std::cerr << "Error, line " << (linenum - 1) - << ": Failed to balance the following transaction:" - << std::endl; - curr->print(std::cerr); - curr->validate(true); - return; - } + // 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)->cost) { + if (empty_allowed && ! balance.amounts.empty() && + balance.amounts.size() == 1) { + 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 (compute_balances) + (*x)->acct->balance.credit((*x)->cost); + } else { + std::cerr << "Error, line " << (linenum - 1) + << ": Transaction entry is lacking an amount." + << std::endl; + return; + } + } #ifdef HUQUQULLAH - if (main_ledger.compute_huquq) { - for (std::list<transaction *>::iterator x = curr->xacts.begin(); - x != curr->xacts.end(); - x++) { - if (! (*x)->exempt_or_necessary || ! (*x)->cost) - continue; + if (! main_ledger.compute_huquq || ! (*x)->exempt_or_necessary) + continue; - // Reflect the exempt or necessary transaction in the - // Huququ'llah account, using the H commodity, which is 19% of - // whichever DEFAULT_COMMODITY ledger was compiled with. + // Reflect 19% of the exempt or necessary transaction in the + // Huququ'llah account. - amount * temp = (*x)->cost->value(); + amount * divisor = create_amount("0.19"); + amount * temp = (*x)->cost->value(); - transaction * t - = new transaction(main_ledger.huquq_account, - temp->value(main_ledger.huquq)); - curr->xacts.push_back(t); + transaction * t = new transaction(main_ledger.huquq_account, + temp->value(divisor)); + curr->xacts.push_back(t); - if (compute_balances) - t->acct->balance.credit(t->cost); + if (compute_balances) + t->acct->balance.credit(t->cost); - // Balance the above transaction by recording the inverse in - // Expenses:Huququ'llah. + // Balance the above transaction by recording the inverse in + // Expenses:Huququ'llah. - t = new transaction(main_ledger.huquq_expenses_account, - temp->value(main_ledger.huquq)); - t->cost->negate(); - curr->xacts.push_back(t); + t = new transaction(main_ledger.huquq_expenses_account, + temp->value(divisor)); + t->cost->negate(); + curr->xacts.push_back(t); - if (compute_balances) - t->acct->balance.credit(t->cost); + if (compute_balances) + t->acct->balance.credit(t->cost); - delete temp; - } - } + delete temp; + delete divisor; #endif + } + + // Compute the balances again, just to make sure it all comes out + // right (i.e., to 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); + return; + } + + // If it's OK, add it to the general ledger's list of entries. main_ledger.entries.push_back(curr); } |