summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile5
-rw-r--r--amount.cc4
-rw-r--r--balance.cc118
-rw-r--r--gnucash.cc12
-rw-r--r--ledger.cc181
-rw-r--r--ledger.h101
-rw-r--r--ledger.texi97
-rw-r--r--main.cc86
-rw-r--r--parse.cc41
-rw-r--r--register.cc111
-rwxr-xr-xreport19
11 files changed, 521 insertions, 254 deletions
diff --git a/Makefile b/Makefile
index ff0b9856..fb139113 100644
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,7 @@ define GNUCASH
true
endef
-CODE = amount.cc ledger.cc parse.cc balance.cc main.cc
+CODE = amount.cc ledger.cc parse.cc balance.cc register.cc main.cc
ifdef GNUCASH
CODE := $(CODE) gnucash.cc
endif
@@ -10,7 +10,8 @@ endif
OBJS = $(patsubst %.cc,%.o,$(CODE))
CFLAGS = -Wall -ansi -pedantic -DHUQUQULLAH=1
-DFLAGS = -g
+#DFLAGS = -O3 -fomit-frame-pointer
+DFLAGS = -g # -pg
INCS = -I/usr/include/xmltok
LIBS = -lgmpxx -lgmp -lpcre
ifdef GNUCASH
diff --git a/amount.cc b/amount.cc
index 16575046..2d8271be 100644
--- a/amount.cc
+++ b/amount.cc
@@ -477,8 +477,8 @@ static commodity * parse_amount(mpz_t out, const char * num,
commodity * comm = NULL;
if (saw_commodity) {
- commodities_iterator item = commodities.find(symbol.c_str());
- if (item == commodities.end()) {
+ commodities_iterator item = main_ledger.commodities.find(symbol.c_str());
+ if (item == main_ledger.commodities.end()) {
comm = new commodity(symbol, prefix, separate,
thousands, european, precision);
} else {
diff --git a/balance.cc b/balance.cc
index 7867f0ab..e3b9e7ab 100644
--- a/balance.cc
+++ b/balance.cc
@@ -1,20 +1,20 @@
#include "ledger.h"
-#include <fstream>
#include <unistd.h>
namespace ledger {
-static bool show_cleared = false;
+extern bool show_cleared;
+
+extern std::time_t begin_date;
+extern bool have_beginning;
+extern std::time_t end_date;
+extern bool have_ending;
+
static bool show_children = false;
static bool show_empty = false;
static bool no_subtotals = false;
-static std::time_t begin_date;
-static bool have_beginning;
-static std::time_t end_date;
-static bool have_ending;
-
static void display_total(std::ostream& out, totals& total_balance,
const account * acct,
const std::map<account *, totals *>& balances,
@@ -68,20 +68,6 @@ static void display_total(std::ostream& out, totals& total_balance,
display_total(out, total_balance, (*i).second, balances, regexps);
}
-static void record_price(char * setting,
- std::map<const std::string, amount *>& prices)
-{
- char * c = setting;
- char * p = std::strchr(setting, '=');
- if (! p) {
- std::cerr << "Warning: Invalid price setting: " << setting << std::endl;
- } else {
- *p++ = '\0';
- amount * price = create_amount(p);
- prices.insert(std::pair<const std::string, amount *>(c, price));
- }
-}
-
//////////////////////////////////////////////////////////////////////
//
// Balance reporting code
@@ -89,86 +75,21 @@ static void record_price(char * setting,
void report_balances(int argc, char **argv, std::ostream& out)
{
- std::map<const std::string, amount *> prices;
- std::list<mask> regexps;
-
-#ifdef HUQUQULLAH
- if (compute_huquq) {
- prices.insert(std::pair<const std::string, amount *>
- ("H", create_amount("$0.19")));
- prices.insert(std::pair<const std::string, amount *>
- ("troy", create_amount("8.5410148523 mithqal")));
- }
-#endif
-
- have_beginning = false;
- have_ending = false;
-
- int c;
optind = 1;
- while (-1 != (c = getopt(argc, argv, "b:e:cCsSni:p:G:"))) {
+ int c;
+ while (-1 != (c = getopt(argc, argv, "sSnG:"))) {
switch (char(c)) {
- case 'b': {
- struct tm * when = getdate(optarg);
- if (! when) {
- std::cerr << "Error: Bad begin date string: " << optarg
- << std::endl;
- } else {
- begin_date = std::mktime(when);
- have_beginning = true;
- }
- break;
- }
- case 'e': {
- struct tm * when = getdate(optarg);
- if (! when) {
- std::cerr << "Error: Bad end date string: " << optarg
- << std::endl;
- } else {
- end_date = std::mktime(when);
- have_ending = true;
- }
- break;
- }
- case 'c':
- end_date = std::time(NULL);
- have_ending = true;
- break;
-
- case 'C': show_cleared = true; break;
case 's': show_children = true; break;
case 'S': show_empty = true; break;
case 'n': no_subtotals = true; break;
- // -i path-to-file-of-regexps
- case 'i':
- read_regexps(optarg, regexps);
- break;
-
- // -p "COMMODITY=PRICE"
- // -p path-to-price-database
- case 'p':
- if (access(optarg, R_OK) != -1) {
- std::ifstream pricedb(optarg);
-
- while (! pricedb.eof()) {
- char buf[80];
- pricedb.getline(buf, 79);
- if (*buf && ! std::isspace(*buf))
- record_price(buf, prices);
- }
- } else {
- record_price(optarg, prices);
- }
- break;
-
#ifdef HUQUQULLAH
case 'G': {
double gold = std::atof(optarg);
gold = 1 / gold;
char buf[256];
- std::sprintf(buf, "$=%f troy", gold);
- record_price(buf, prices);
+ std::sprintf(buf, DEFAULT_COMMODITY "=%f troy", gold);
+ main_ledger.record_price(buf);
break;
}
#endif
@@ -186,7 +107,9 @@ void report_balances(int argc, char **argv, std::ostream& out)
std::map<account *, totals *> balances;
- for (ledger_iterator i = ledger.begin(); i != ledger.end(); i++) {
+ for (entries_iterator i = main_ledger.entries.begin();
+ i != main_ledger.entries.end();
+ i++) {
for (std::list<transaction *>::iterator x = (*i)->xacts.begin();
x != (*i)->xacts.end();
x++) {
@@ -223,9 +146,9 @@ void report_balances(int argc, char **argv, std::ostream& out)
bool allocated = false;
for (int cycles = 0; cost && cycles < 10; cycles++) {
std::map<const std::string, amount *>::iterator pi
- = prices.find(cost->comm_symbol());
+ = main_ledger.prices.amounts.find(cost->comm_symbol());
- if (pi == prices.end()) {
+ if (pi == main_ledger.prices.amounts.end()) {
balance->credit(cost);
if (allocated)
delete cost;
@@ -247,7 +170,9 @@ void report_balances(int argc, char **argv, std::ostream& out)
totals total_balance;
- for (accounts_iterator i = accounts.begin(); i != accounts.end(); i++)
+ for (accounts_iterator i = main_ledger.accounts.begin();
+ i != main_ledger.accounts.end();
+ i++)
display_total(out, total_balance, (*i).second, balances, regexps);
// Print the total of all the balances shown
@@ -262,11 +187,6 @@ void report_balances(int argc, char **argv, std::ostream& out)
i != balances.end();
i++)
delete (*i).second;
-
- for (std::map<const std::string, amount *>::iterator i = prices.begin();
- i != prices.end();
- i++)
- delete (*i).second;
}
} // namespace ledger
diff --git a/gnucash.cc b/gnucash.cc
index 19b870b9..0166720c 100644
--- a/gnucash.cc
+++ b/gnucash.cc
@@ -98,13 +98,15 @@ static void endElement(void *userData, const char *name)
if (std::strcmp(name, "gnc:account") == 0) {
assert(curr_account);
if (! curr_account->parent)
- accounts.insert(accounts_entry(curr_account->name, curr_account));
+ main_ledger.accounts.insert(accounts_entry(curr_account->name,
+ curr_account));
accounts_by_id.insert(accounts_entry(curr_account_id, curr_account));
curr_account = NULL;
}
else if (std::strcmp(name, "gnc:commodity") == 0) {
assert(curr_comm);
- commodities.insert(commodities_entry(curr_comm->symbol, curr_comm));
+ main_ledger.commodities.insert(commodities_entry(curr_comm->symbol,
+ curr_comm));
curr_comm = NULL;
}
else if (std::strcmp(name, "gnc:transaction") == 0) {
@@ -115,7 +117,7 @@ static void endElement(void *userData, const char *name)
<< XML_GetCurrentLineNumber(current_parser) << std::endl;
curr_entry->print(std::cerr);
} else {
- ledger.push_back(curr_entry);
+ main_ledger.entries.push_back(curr_entry);
}
curr_entry = NULL;
}
@@ -146,9 +148,9 @@ static void dataHandler(void *userData, const char *s, int len)
if (curr_comm)
curr_comm->symbol = std::string(s, len);
else if (curr_account)
- curr_account->comm = commodities[std::string(s, len)];
+ curr_account->comm = main_ledger.commodities[std::string(s, len)];
else if (curr_entry)
- entry_comm = commodities[std::string(s, len)];
+ entry_comm = main_ledger.commodities[std::string(s, len)];
break;
case COMM_NAME:
diff --git a/ledger.cc b/ledger.cc
index d8b3143c..44a0830e 100644
--- a/ledger.cc
+++ b/ledger.cc
@@ -4,12 +4,12 @@
namespace ledger {
-commodities_t commodities;
-accounts_t accounts;
-ledger_t ledger;
-
bool use_warnings = false;
+state main_ledger;
+
+std::list<mask> regexps;
+
#ifdef HUQUQULLAH
bool compute_huquq;
std::list<mask> huquq_categories;
@@ -40,18 +40,8 @@ void entry::print(std::ostream& out) const
i++) {
out << " ";
- std::string acct_name;
- for (account * acct = (*i)->acct;
- acct;
- acct = acct->parent) {
- if (acct_name.empty())
- acct_name = acct->name;
- else
- acct_name = acct->name + ":" + acct_name;
- }
-
out.width(30);
- out << std::left << acct_name;
+ out << std::left << (*i)->acct->as_str();
if (! shortcut || i == xacts.begin()) {
out << " ";
@@ -78,40 +68,67 @@ bool entry::validate() const
if (balance) {
std::cerr << "Totals are:" << std::endl;
- balance.print(std::cerr);
+ balance.print(std::cerr, 20);
std::cerr << std::endl;
}
return ! balance; // must balance to 0.0
}
+bool entry::matches(const std::list<mask>& regexps) const
+{
+ if (regexps.empty() || (ledger::matches(regexps, code) ||
+ ledger::matches(regexps, desc))) {
+ return true;
+ }
+ else {
+ bool match = false;
+
+ for (std::list<transaction *>::const_iterator x = xacts.begin();
+ x != xacts.end();
+ x++) {
+ if (ledger::matches(regexps, (*x)->acct->name) ||
+ ledger::matches(regexps, (*x)->note)) {
+ match = true;
+ break;
+ }
+ }
+ return match;
+ }
+}
+
+totals::~totals()
+{
+ for (iterator i = amounts.begin(); i != amounts.end(); i++)
+ delete (*i).second;
+}
+
void totals::credit(const totals& other)
{
- for (const_iterator_t i = other.amounts.begin();
+ for (const_iterator i = other.amounts.begin();
i != other.amounts.end();
- i++) {
+ i++)
credit((*i).second);
- }
}
totals::operator bool() const
{
- for (const_iterator_t i = amounts.begin(); i != amounts.end(); i++)
+ for (const_iterator i = amounts.begin(); i != amounts.end(); i++)
if (*((*i).second))
return true;
return false;
}
-void totals::print(std::ostream& out) const
+void totals::print(std::ostream& out, int width) const
{
bool first = true;
- for (const_iterator_t i = amounts.begin(); i != amounts.end(); i++)
+ for (const_iterator i = amounts.begin(); i != amounts.end(); i++)
if (*((*i).second)) {
if (first)
first = false;
else
out << std::endl;
- out.width(20);
+ out.width(width);
out << std::right << *((*i).second);
}
}
@@ -123,7 +140,7 @@ amount * totals::value(const std::string& commodity)
amount * total = create_amount((commodity + " 0.00").c_str());
- for (iterator_t i = amounts.begin(); i != amounts.end(); i++)
+ for (iterator i = amounts.begin(); i != amounts.end(); i++)
*total += *((*i).second);
return total;
@@ -134,18 +151,7 @@ amount * totals::value(const std::string& commodity)
void print_ledger(int argc, char *argv[], std::ostream& out)
{
- std::list<mask> regexps;
-
- int c;
optind = 1;
- while (-1 != (c = getopt(argc, argv, "i:"))) {
- switch (char(c)) {
- // -i path-to-file-of-regexps
- case 'i':
- read_regexps(optarg, regexps);
- break;
- }
- }
// Compile the list of specified regular expressions, which can be
// specified on the command line, or using an include/exclude file
@@ -155,33 +161,14 @@ void print_ledger(int argc, char *argv[], std::ostream& out)
// Sort the list of entries by date, then print them in order.
- std::sort(ledger.begin(), ledger.end(), cmp_entry_date());
+ std::sort(main_ledger.entries.begin(), main_ledger.entries.end(),
+ cmp_entry_date());
- for (std::vector<entry *>::const_iterator i = ledger.begin();
- i != ledger.end();
- i++) {
- if (regexps.empty() ||
- (matches(regexps, (*i)->code) ||
- matches(regexps, (*i)->desc))) {
+ for (entries_iterator i = main_ledger.entries.begin();
+ i != main_ledger.entries.end();
+ i++)
+ if ((*i)->matches(regexps))
(*i)->print(out);
- }
- else {
- bool match = false;
-
- for (std::list<transaction *>::const_iterator x = (*i)->xacts.begin();
- x != (*i)->xacts.end();
- x++) {
- if (matches(regexps, (*x)->acct->name) ||
- matches(regexps, (*x)->note)) {
- match = true;
- break;
- }
- }
-
- if (match)
- (*i)->print(out);
- }
- }
}
void record_regexp(char * pattern, std::list<mask>& regexps)
@@ -245,4 +232,78 @@ bool matches(const std::list<mask>& regexps, const std::string& str)
return match;
}
+state::~state()
+{
+#if 0
+ for (commodities_iterator i = commodities.begin();
+ i != commodities.end();
+ i++)
+ delete (*i).second;
+
+ for (accounts_iterator i = accounts.begin();
+ i != accounts.end();
+ i++)
+ delete (*i).second;
+
+ for (entries_iterator i = entries.begin();
+ i != entries.end();
+ i++)
+ delete *i;
+#endif
+}
+
+void state::record_price(const char * setting)
+{
+ char buf[128];
+ std::strcpy(buf, setting);
+
+ assert(std::strlen(setting) < 128);
+
+ char * c = buf;
+ char * p = std::strchr(buf, '=');
+ if (! p) {
+ std::cerr << "Warning: Invalid price setting: " << setting << std::endl;
+ } else {
+ *p++ = '\0';
+ prices.amounts.insert(totals::pair(c, create_amount(p)));
+ }
+}
+
+account * state::find_account(const char * name, bool create)
+{
+ char * buf = new char[std::strlen(name) + 1];
+ std::strcpy(buf, name);
+
+ account * current = NULL;
+ for (char * tok = std::strtok(buf, ":");
+ tok;
+ tok = std::strtok(NULL, ":")) {
+ if (! current) {
+ accounts_iterator i = accounts.find(tok);
+ if (i == accounts.end()) {
+ if (! create)
+ return NULL;
+ current = new account(tok);
+ accounts.insert(accounts_entry(tok, current));
+ } else {
+ current = (*i).second;
+ }
+ } else {
+ account::iterator i = current->children.find(tok);
+ if (i == current->children.end()) {
+ if (! create)
+ return NULL;
+ current = new account(tok, current);
+ current->parent->children.insert(accounts_entry(tok, current));
+ } else {
+ current = (*i).second;
+ }
+ }
+ }
+
+ delete[] buf;
+
+ return current;
+}
+
} // namespace ledger
diff --git a/ledger.h b/ledger.h
index ec82f54a..0df8b870 100644
--- a/ledger.h
+++ b/ledger.h
@@ -1,5 +1,5 @@
#ifndef _LEDGER_H
-#define _LEDGER_H "$Revision: 1.7 $"
+#define _LEDGER_H "$Revision: 1.8 $"
//////////////////////////////////////////////////////////////////////
//
@@ -107,17 +107,6 @@ typedef std::map<const std::string, commodity *> commodities_t;
typedef commodities_t::iterator commodities_iterator;
typedef std::pair<const std::string, commodity *> commodities_entry;
-extern commodities_t commodities;
-
-inline commodity::commodity(const std::string& sym, bool pre, bool sep,
- bool thou, bool euro, int prec)
- : symbol(sym), prefix(pre), separate(sep),
- thousands(thou), european(euro), precision(prec) {
- std::pair<commodities_iterator, bool> result =
- commodities.insert(commodities_entry(sym, this));
- assert(result.second);
-}
-
class amount
{
@@ -155,6 +144,23 @@ operator<<(std::basic_ostream<char, Traits>& out, const amount& a) {
extern amount * create_amount(const char * value, const amount * price = NULL);
+
+struct mask
+{
+ bool exclude;
+ pcre * regexp;
+
+ mask(bool exc, pcre * re) : exclude(exc), regexp(re) {}
+};
+
+extern std::list<mask> regexps;
+
+extern void record_regexp(char * pattern, std::list<mask>& regexps);
+extern void read_regexps(const char * path, std::list<mask>& regexps);
+extern bool matches(const std::list<mask>& regexps,
+ const std::string& str);
+
+
struct account;
struct transaction
{
@@ -190,6 +196,7 @@ struct entry
}
}
+ bool matches(const std::list<mask>& regexps) const;
void print(std::ostream& out) const;
bool validate() const;
};
@@ -200,25 +207,24 @@ struct cmp_entry_date {
}
};
-typedef std::vector<entry *> ledger_t;
-typedef ledger_t::iterator ledger_iterator;
-
-extern ledger_t ledger;
+typedef std::vector<entry *> entries_t;
+typedef entries_t::iterator entries_iterator;
-class totals
+struct totals
{
- typedef std::map<const std::string, amount *> map_t;
- typedef map_t::iterator iterator_t;
- typedef map_t::const_iterator const_iterator_t;
- typedef std::pair<const std::string, amount *> pair_t;
+ typedef std::map<const std::string, amount *> map;
+ typedef map::iterator iterator;
+ typedef map::const_iterator const_iterator;
+ typedef std::pair<const std::string, amount *> pair;
- map_t amounts;
+ map amounts;
+
+ ~totals();
- public:
void credit(const amount * val) {
- std::pair<iterator_t, bool> result =
- amounts.insert(pair_t(val->comm_symbol(), val->copy()));
+ std::pair<iterator, bool> result =
+ amounts.insert(pair(val->comm_symbol(), val->copy()));
if (! result.second)
amounts[val->comm_symbol()]->credit(val);
}
@@ -226,7 +232,7 @@ class totals
operator bool() const;
- void print(std::ostream& out) const;
+ void print(std::ostream& out, int width) const;
// Returns an allocated entity
amount * value(const std::string& comm);
@@ -238,7 +244,7 @@ class totals
template<class Traits>
std::basic_ostream<char, Traits> &
operator<<(std::basic_ostream<char, Traits>& out, const totals& t) {
- t.print(out);
+ t.print(out, 20);
return out;
}
@@ -280,27 +286,38 @@ typedef std::map<const std::string, account *> accounts_t;
typedef accounts_t::iterator accounts_iterator;
typedef std::pair<const std::string, account *> accounts_entry;
-extern accounts_t accounts;
-
-struct mask
-{
- bool exclude;
- pcre * regexp;
-
- mask(bool exc, pcre * re) : exclude(exc), regexp(re) {}
-};
-
-extern void record_regexp(char * pattern, std::list<mask>& regexps);
-extern void read_regexps(const char * path, std::list<mask>& regexps);
-extern bool matches(const std::list<mask>& regexps,
- const std::string& str);
#ifdef HUQUQULLAH
+#define DEFAULT_COMMODITY "$"
+
extern bool compute_huquq;
extern std::list<mask> huquq_categories;
#endif
-extern bool use_warnings;
+struct state
+{
+ commodities_t commodities;
+ accounts_t accounts;
+ entries_t entries;
+ totals prices;
+
+ ~state();
+
+ void record_price(const char * setting);
+ account * find_account(const char * name, bool create = true);
+};
+
+extern state main_ledger;
+extern bool use_warnings;
+
+inline commodity::commodity(const std::string& sym, bool pre, bool sep,
+ bool thou, bool euro, int prec)
+ : symbol(sym), prefix(pre), separate(sep),
+ thousands(thou), european(euro), precision(prec) {
+ std::pair<commodities_iterator, bool> result =
+ main_ledger.commodities.insert(commodities_entry(sym, this));
+ assert(result.second);
+}
} // namespace ledger
diff --git a/ledger.texi b/ledger.texi
index a373f4b8..f716a448 100644
--- a/ledger.texi
+++ b/ledger.texi
@@ -1,5 +1,5 @@
\input texinfo @c -*-texinfo-*-
-@comment $Id: ledger.texi,v 1.2 2003/09/30 11:31:45 johnw Exp $
+@comment $Id: ledger.texi,v 1.3 2003/09/30 23:17:18 johnw Exp $
@comment %**start of header
@setfilename ledger.info
@@ -192,6 +192,101 @@ the balanced amount, if it is the same as the first line:
For this entry, @code{ledger} will figure out that $-23.00 must come
from ``Assets:Checking'' in order to balance the entry.
+@section Commodities and currencies
+
+@code{ledger} makes no assumptions about the commodities you use. It
+only requires that you specify which commodity. The commodity may be
+any non-numeric string that does not contain a period, comma, forward
+slash or at sign (@@). Here are some valid commodity specifiers:
+
+@example
+$20.00 ; twenty US dollars
+20 USD ; the same
+AAPL 40 ; 40 shares of Apple Computer stock
+DM 60 ; 60 Deutsch Mark
+£50 ; 50 British pounds
+@end example
+
+@code{ledger} will examine the first use of any commodity to determine
+how that commodity should be printed on reports. It pays attention to
+whether the name of commodity was separated from the amount, whether
+it came before or after, the precision used in specifying the amount,
+whether thousand marks were used, etc. This is done so that printing
+the commodity looks the same as the way you use it.
+
+An account may contain multiple commodities, in which case it will
+have separate totals for each. For example, if your brokage account
+contains both cash, gold, and several stock quantities, the balance
+might look like:
+
+@example
+ $200.00
+100.00 AU
+ AAPL 40
+ BORL 100
+ FEQTX 50 Assets:Brokerage
+@end example
+
+This balance report shows how much of each commodity is in your
+brokerage account.
+
+Sometimes, you will want to know the current street value of your
+balance, and not the commodity totals. For this to happen, you must
+specify what the current price is for each commodity. The price can
+be in any commodity, in which case the balance will be computed in
+terms of that commodity. The usual way to specify prices is with a
+file of price settings, which might look like this:
+
+@example
+AU=$357.00
+AAPL=$37
+BORL=$19
+FEQTX=$32
+@end example
+
+Specify the prices file using the @samp{-p} option:
+
+@example
+/home/johnw $ ledger -f ledger.dat -p prices.db balance brokerage
+@end example
+
+Now the balance for your brokerage account will be given in US
+dollars, since the prices database has specified conversion factors
+from each commodity into dollars:
+
+@example
+$40880.00 Assets:Brokerage
+@end example
+
+You can convert from any commodity to any other commodity. Let's say
+you had $5000 in your checking account, and for whatever reason you
+wanted to know many ounces of gold that would buy. If gold is
+currently $357 per ounce, then each dollar is worth 1/357 AU:
+
+@example
+/home/johnw $ ledger -f ledger.dat -p "\$=0.00280112 AU" balance checking
+@end example
+
+@example
+14.01 AU Assets:Checking
+@end example
+
+$5000 would buy 14 ounces of gold, which becomes the new display
+commodity since a conversion factor was provided.
+
+Commodities conversions can also be chained, up to a depth of 15.
+Here is a sample prices database that uses chaining:
+
+@example
+AAPL=$15
+$=0.00280112 AU
+AU=300 Euro
+Euro=DM 0.75
+@end example
+
+This is a roundabout way of reporting AAPL shares in their Deutsch
+Mark equivalent.
+
@chapter Using @code{ledger}
@chapter Computing Huqúqu'lláh
diff --git a/main.cc b/main.cc
index a732ae19..2b1b086a 100644
--- a/main.cc
+++ b/main.cc
@@ -7,7 +7,15 @@ namespace ledger {
extern bool parse_gnucash(std::istream& in);
extern void report_balances(int argc, char **argv, std::ostream& out);
+ extern void print_register(int argc, char **argv, std::ostream& out);
extern void print_ledger(int argc, char *argv[], std::ostream& out);
+
+ bool show_cleared;
+
+ std::time_t begin_date;
+ bool have_beginning;
+ std::time_t end_date;
+ bool have_ending;
}
using namespace ledger;
@@ -36,18 +44,72 @@ int main(int argc, char *argv[])
std::istream * file = NULL;
#ifdef HUQUQULLAH
- compute_huquq = true;
+ compute_huquq = true;
#endif
+ have_beginning = false;
+ have_ending = false;
+ show_cleared = false;
int c;
- while (-1 != (c = getopt(argc, argv, "+hHwf:"))) {
+ while (-1 != (c = getopt(argc, argv, "+b:e:cChHwf:i:p:"))) {
switch (char(c)) {
+ case 'b': {
+ struct tm * when = getdate(optarg);
+ if (! when) {
+ std::cerr << "Error: Bad begin date string: " << optarg
+ << std::endl;
+ } else {
+ begin_date = std::mktime(when);
+ have_beginning = true;
+ }
+ break;
+ }
+ case 'e': {
+ struct tm * when = getdate(optarg);
+ if (! when) {
+ std::cerr << "Error: Bad end date string: " << optarg
+ << std::endl;
+ } else {
+ end_date = std::mktime(when);
+ have_ending = true;
+ }
+ break;
+ }
+ case 'c':
+ end_date = std::time(NULL);
+ have_ending = true;
+ break;
+
+ case 'C': show_cleared = true; break;
+
case 'h': show_help(std::cout); break;
#ifdef HUQUQULLAH
case 'H': compute_huquq = false; break;
#endif
case 'w': use_warnings = true; break;
case 'f': file = new std::ifstream(optarg); break;
+
+ // -i path-to-file-of-regexps
+ case 'i':
+ read_regexps(optarg, regexps);
+ break;
+
+ // -p "COMMODITY=PRICE"
+ // -p path-to-price-database
+ case 'p':
+ if (access(optarg, R_OK) != -1) {
+ std::ifstream pricedb(optarg);
+
+ while (! pricedb.eof()) {
+ char buf[80];
+ pricedb.getline(buf, 79);
+ if (*buf && ! std::isspace(*buf))
+ main_ledger.record_price(buf);
+ }
+ } else {
+ main_ledger.record_price(optarg);
+ }
+ break;
}
}
@@ -69,7 +131,7 @@ int main(int argc, char *argv[])
// The -f option is required
- if (! file) {
+ if (! file || ! *file) {
std::cerr << "Please specify the ledger file using the -f option."
<< std::endl;
return 1;
@@ -78,7 +140,7 @@ int main(int argc, char *argv[])
// Global defaults
commodity * usd = new commodity("$", true, false, true, false, 2);
- commodities.insert(commodities_entry("USD", usd));
+ main_ledger.commodities.insert(commodities_entry("USD", usd));
#ifdef HUQUQULLAH
if (compute_huquq) {
@@ -86,9 +148,21 @@ int main(int argc, char *argv[])
new commodity("mithqal", false, true, true, false, 1);
read_regexps(".huquq", huquq_categories);
+
+ main_ledger.record_price("H=" DEFAULT_COMMODITY "0.19");
+ main_ledger.record_price("troy=8.5410148523 mithqal");
}
#endif
+ // Read the command word
+
+ const std::string command = argv[optind];
+
+#ifdef HUQUQ_CATEGORIES
+ if (command == "register")
+ compute_huquq = false;
+#endif
+
// Parse the ledger
char buf[32];
@@ -104,10 +178,10 @@ int main(int argc, char *argv[])
// Process the command
- const std::string command = argv[optind];
-
if (command == "balance")
report_balances(argc - optind, &argv[optind], std::cout);
+ else if (command == "register")
+ print_register(argc - optind, &argv[optind], std::cout);
else if (command == "print")
print_ledger(argc - optind, &argv[optind], std::cout);
}
diff --git a/parse.cc b/parse.cc
index 3a6430bb..07d7f6ce 100644
--- a/parse.cc
+++ b/parse.cc
@@ -40,44 +40,11 @@ static inline void finalize_entry(entry * curr)
<< "ending on line " << (linenum - 1) << std::endl;
curr->print(std::cerr);
} else {
- ledger.push_back(curr);
+ main_ledger.entries.push_back(curr);
}
}
}
-static account * find_account(const char * name)
-{
- char * buf = new char[std::strlen(name) + 1];
- std::strcpy(buf, name);
-
- account * current = NULL;
- for (char * tok = std::strtok(buf, ":");
- tok;
- tok = std::strtok(NULL, ":")) {
- if (! current) {
- accounts_iterator i = accounts.find(tok);
- if (i == accounts.end()) {
- current = new account(tok);
- accounts.insert(accounts_entry(tok, current));
- } else {
- current = (*i).second;
- }
- } else {
- account::iterator i = current->children.find(tok);
- if (i == current->children.end()) {
- current = new account(tok, current);
- current->parent->children.insert(accounts_entry(tok, current));
- } else {
- current = (*i).second;
- }
- }
- }
-
- delete[] buf;
-
- return current;
-}
-
//////////////////////////////////////////////////////////////////////
//
// Ledger parser
@@ -230,7 +197,7 @@ bool parse_ledger(std::istream& in)
}
#endif
- xact->acct = find_account(p);
+ xact->acct = main_ledger.find_account(p);
xact->acct->balance.credit(xact->cost);
curr->xacts.push_back(xact);
@@ -241,14 +208,14 @@ bool parse_ledger(std::istream& in)
amount * temp;
transaction * t = new transaction();
- t->acct = find_account("Huququ'llah");
+ t->acct = main_ledger.find_account("Huququ'llah");
temp = xact->cost->value();
t->cost = temp->value(huquq);
delete temp;
curr->xacts.push_back(t);
t = new transaction();
- t->acct = find_account("Expenses:Huququ'llah");
+ t->acct = main_ledger.find_account("Expenses:Huququ'llah");
temp = xact->cost->value();
t->cost = temp->value(huquq);
delete temp;
diff --git a/register.cc b/register.cc
new file mode 100644
index 00000000..86e42d53
--- /dev/null
+++ b/register.cc
@@ -0,0 +1,111 @@
+#include "ledger.h"
+
+#include <unistd.h>
+
+namespace ledger {
+
+extern bool show_cleared;
+
+extern std::time_t begin_date;
+extern bool have_beginning;
+extern std::time_t end_date;
+extern bool have_ending;
+
+//////////////////////////////////////////////////////////////////////
+//
+// Register printing code
+//
+
+void print_register(int argc, char **argv, std::ostream& out)
+{
+ optind = 1;
+#if 0
+ int c;
+ while (-1 != (c = getopt(argc, argv, ""))) {
+ switch (char(c)) {
+ }
+ }
+#endif
+
+ // Find out which account this register is to be printed for
+
+ account * acct = main_ledger.find_account(argv[optind++], false);
+ if (! acct) {
+ std::cerr << "Error: Unknown account name: " << argv[optind - 1]
+ << std::endl;
+ return;
+ }
+
+ // Compile the list of specified regular expressions, which can be
+ // specified on the command line, or using an include/exclude file
+
+ for (; optind < argc; optind++)
+ record_regexp(argv[optind], regexps);
+
+ // Walk through all of the ledger entries, printing their register
+ // formatted equivalent
+
+ totals balance;
+
+ for (entries_iterator i = main_ledger.entries.begin();
+ i != main_ledger.entries.end();
+ i++) {
+ bool applies = false;
+ for (std::list<transaction *>::iterator x = (*i)->xacts.begin();
+ x != (*i)->xacts.end();
+ x++) {
+ if ((*x)->acct == acct) {
+ applies = true;
+ break;
+ }
+ }
+
+ if (! applies || ! (*i)->matches(regexps))
+ continue;
+
+ for (std::list<transaction *>::iterator x = (*i)->xacts.begin();
+ x != (*i)->xacts.end();
+ x++) {
+ if ((*x)->acct == acct || ! show_cleared && (*i)->cleared)
+ continue;
+
+ char buf[32];
+ std::strftime(buf, 31, "%Y.%m.%d ", std::localtime(&(*i)->date));
+ out << buf;
+
+ if ((*i)->cleared)
+ out << "* ";
+ else
+ out << " ";
+
+ out.width(4);
+ if ((*i)->code.empty())
+ out << " ";
+ else
+ out << std::left << (*i)->code;
+ out << " ";
+
+ out.width(20);
+ if ((*i)->desc.empty())
+ out << " ";
+ else
+ out << std::left << (*i)->desc;
+ out << " ";
+
+ out.width(18);
+ out << std::left << (*x)->acct->as_str() << " ";
+
+ (*x)->cost->negate();
+
+ out.width(12);
+ out << std::right << (*x)->cost->as_str(true);
+
+ balance.credit((*x)->cost);
+ balance.print(out, 12);
+
+ out << std::endl;
+ }
+ }
+}
+
+} // namespace ledger
diff --git a/report b/report
new file mode 100755
index 00000000..c7c6187a
--- /dev/null
+++ b/report
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+binary=./ledger
+ledger=ledger.dat
+
+line="$binary -f $ledger"
+
+command=$1
+shift
+
+case "$command" in
+ balance) $line "$@" balance bank checking mastercard cash ;;
+ worth) $line "$@" balance assets liabilities ;;
+ profit) $line "$@" balance income expense ;;
+ spending) $line "$@" balance -n food movies gas tips \
+ health supplies -insurance -vacation ;;
+ huquq) $line "$@" balance ^huquq ;;
+ gold) $line "$@" balance -G $1 ^huquq ;;
+esac