diff options
-rw-r--r-- | Makefile | 5 | ||||
-rw-r--r-- | amount.cc | 4 | ||||
-rw-r--r-- | balance.cc | 118 | ||||
-rw-r--r-- | gnucash.cc | 12 | ||||
-rw-r--r-- | ledger.cc | 181 | ||||
-rw-r--r-- | ledger.h | 101 | ||||
-rw-r--r-- | ledger.texi | 97 | ||||
-rw-r--r-- | main.cc | 86 | ||||
-rw-r--r-- | parse.cc | 41 | ||||
-rw-r--r-- | register.cc | 111 | ||||
-rwxr-xr-x | report | 19 |
11 files changed, 521 insertions, 254 deletions
@@ -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 @@ -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 { @@ -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 @@ -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: @@ -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 @@ -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 @@ -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); } @@ -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 @@ -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 |