From 7ce12f8cfe0beaecea3de7e073aa3c9751742a5d Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 1 Oct 2003 21:55:40 +0000 Subject: [PATCH] *** empty log message *** --- Makefile | 2 +- amount.cc | 49 ++++++++------- balance.cc | 13 +--- ledger.cc | 25 +++----- ledger.h | 8 +-- ledger.texi | 43 ++++++++++++- main.cc | 22 +------ parse.cc | 172 +++++++++++++++++++++++++++++++--------------------- 8 files changed, 184 insertions(+), 150 deletions(-) diff --git a/Makefile b/Makefile index fb535183..d6a971f4 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/amount.cc b/amount.cc index 41837767..695c4226 100644 --- a/amount.cc +++ b/amount.cc @@ -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; + using namespace std; - char buf[256]; - buf[0] = '\0'; + if (! get_quotes) + break; - if (FILE * fp = popen((std::string("getquote ") + - amt->comm_symbol()).c_str(), "r")) { - if (feof(fp) || ! fgets(buf , 255, fp)) { - fclose(fp); - 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(_other); - assert(quantity_comm == other.quantity_comm); - mpz_add(quantity, quantity, other.quantity); + const gmp_amount * val = dynamic_cast(value); + assert(quantity_comm == val->quantity_comm); + mpz_add(quantity, quantity, val->quantity); } } // namespace ledger diff --git a/balance.cc b/balance.cc index e27ff872..53f3ecf6 100644 --- a/balance.cc +++ b/balance.cc @@ -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 } } diff --git a/ledger.cc b/ledger.cc index 98383511..7f4f161d 100644 --- a/ledger.cc +++ b/ledger.cc @@ -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::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. diff --git a/ledger.h b/ledger.h index bb619800..3c5badb5 100644 --- a/ledger.h +++ b/ledger.h @@ -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 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 diff --git a/main.cc b/main.cc index 81d46f0f..4ce787d5 100644 --- a/main.cc +++ b/main.cc @@ -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"); diff --git a/parse.cc b/parse.cc index 7a4c5b07..1c2cb556 100644 --- a/parse.cc +++ b/parse.cc @@ -36,49 +36,119 @@ 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::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 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 (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 (! balance.amounts.empty() && balance.amounts.size() == 2) { + for (std::list::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; } } } + // 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::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 || ! (*x)->exempt_or_necessary) + continue; + + // Reflect 19% of the exempt or necessary transaction in the + // Huququ'llah account. + + amount * divisor = create_amount("0.19"); + amount * temp = (*x)->cost->value(); + + 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); + + // Balance the above transaction by recording the inverse in + // Expenses:Huququ'llah. + + 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); + + 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:" @@ -88,43 +158,7 @@ static void finalize_entry(entry * curr, bool compute_balances) return; } -#ifdef HUQUQULLAH - if (main_ledger.compute_huquq) { - for (std::list::iterator x = curr->xacts.begin(); - x != curr->xacts.end(); - x++) { - if (! (*x)->exempt_or_necessary || ! (*x)->cost) - 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. - - amount * temp = (*x)->cost->value(); - - transaction * t - = new transaction(main_ledger.huquq_account, - temp->value(main_ledger.huquq)); - curr->xacts.push_back(t); - - if (compute_balances) - t->acct->balance.credit(t->cost); - - // 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); - - if (compute_balances) - t->acct->balance.credit(t->cost); - - delete temp; - } - } -#endif + // If it's OK, add it to the general ledger's list of entries. main_ledger.entries.push_back(curr); }