summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--amount.cc53
-rw-r--r--balance.cc13
-rw-r--r--ledger.cc25
-rw-r--r--ledger.h8
-rw-r--r--ledger.texi43
-rw-r--r--main.cc22
-rw-r--r--parse.cc170
8 files changed, 185 insertions, 151 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;
-
- 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
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<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.
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<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
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,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);
}