diff options
-rw-r--r-- | Makefile | 23 | ||||
-rw-r--r-- | amount.cc | 30 | ||||
-rw-r--r-- | balance.cc | 165 | ||||
-rw-r--r-- | gnucash.cc | 7 | ||||
-rw-r--r-- | ledger.h | 5 | ||||
-rw-r--r-- | main.cc | 51 | ||||
-rw-r--r-- | parse.cc | 16 |
7 files changed, 188 insertions, 109 deletions
@@ -1,7 +1,4 @@ -CODE = amount.cc ledger.cc parse.cc gnucash.cc balance.cc -ifndef LIBRARY -CODE := $(CODE) main.cc -endif +CODE = amount.cc ledger.cc parse.cc gnucash.cc balance.cc main.cc OBJS = $(patsubst %.cc,%.o,$(CODE)) @@ -10,27 +7,11 @@ DFLAGS = -g INCS = -I/usr/include/xmltok LIBS = -lgmpxx -lgmp -lpcre -lxmlparse -ifdef LIBRARY - -CFLAGS := $(CFLAGS) -fpic - -all: make.deps libledger.so ledger - -libledger.so: $(OBJS) - g++ $(CFLAGS) $(INCS) $(DFLAGS) -shared -fpic -o $@ $(OBJS) $(LIBS) - -ledger: main.cc - g++ $(INCS) $(DFLAGS) -o $@ main.cc -L. -lledger - -else # LIBRARY - all: make.deps ledger ledger: $(OBJS) g++ $(CFLAGS) $(INCS) $(DFLAGS) -o $@ $(OBJS) $(LIBS) -endif # LIBRARY - %.o: %.cc g++ $(CFLAGS) $(INCS) $(DFLAGS) -c -o $@ $< @@ -42,6 +23,6 @@ rebuild: clean deps all deps: make.deps make.deps: Makefile - cc -M $(INCS) $(CODE) main.cc > $@ + cc -M $(INCS) $(CODE) > $@ include make.deps @@ -7,6 +7,8 @@ namespace ledger { +#define MAX_PRECISION 10 // must be 2 or higher + ////////////////////////////////////////////////////////////////////// // // The `amount' structure. Every transaction has an associated amount, @@ -16,8 +18,6 @@ namespace ledger { // default commodity is the US dollar, with a price of 1.00. // -#define MAX_PRECISION 10 // must be 2 or higher - class gmp_amount : public amount { bool priced; @@ -42,13 +42,16 @@ class gmp_amount : public amount mpz_clear(quantity); } + virtual commodity * comm() const { + return quantity_comm; + } virtual const std::string& comm_symbol() const { assert(quantity_comm); return quantity_comm->symbol; } virtual amount * copy() const; - virtual amount * value() const; + virtual amount * value(amount *) const; virtual operator bool() const; @@ -184,13 +187,21 @@ amount * gmp_amount::copy() const return new_amt; } -amount * gmp_amount::value() const +amount * gmp_amount::value(amount * pr) const { - if (! priced) { + if (pr) { + gmp_amount * p = dynamic_cast<gmp_amount *>(pr); + assert(p); + gmp_amount * new_amt = new gmp_amount(); + multiply(new_amt->quantity, quantity, p->quantity); + new_amt->quantity_comm = p->quantity_comm; + return new_amt; + } + else if (! priced) { return copy(); - } else { + } + else { gmp_amount * new_amt = new gmp_amount(); - new_amt->priced = false; multiply(new_amt->quantity, quantity, price); new_amt->quantity_comm = price_comm; return new_amt; @@ -455,8 +466,7 @@ static commodity * parse_amount(mpz_t out, const char * num, commodity * comm = NULL; if (! saw_commodity) { - std::cerr << "Error: No commodity specified: " << value_str - << std::endl; + std::cerr << "Error: No commodity specified: " << value_str << std::endl; std::exit(1); } else { commodities_iterator item = commodities.find(symbol.c_str()); @@ -492,7 +502,7 @@ amount& gmp_amount::operator=(const char * num) const char *error; int erroffset; static const std::string amount_re = - "(([^-0-9/.,]+)(\\s*))?([-0-9/.,]+)((\\s*)([^-0-9/.,@]+))?"; + "(([^-0-9/., ]+)(\\s*))?([-0-9/.,]+)((\\s*)([^-0-9/., @]+))?"; const std::string regexp = "^" + amount_re + "(\\s*@\\s*" + amount_re + ")?$"; re = pcre_compile(regexp.c_str(), 0, &error, &erroffset, NULL); @@ -1,14 +1,11 @@ #include "ledger.h" +#include <fstream> +#include <unistd.h> #include <pcre.h> // Perl regular expression library namespace ledger { -////////////////////////////////////////////////////////////////////// -// -// Balance report. -// - static bool show_current = false; static bool show_cleared = false; static bool show_children = false; @@ -24,10 +21,14 @@ struct mask }; static inline bool matches(const std::list<mask>& regexps, - const std::string& str) { - // If the first pattern is an exclude, then we assume that all - // patterns match if they don't match the exclude. + const std::string& str) +{ + // If the first pattern is an exclude, we assume all patterns match + // if they don't match the exclude. If the first pattern is an + // include, then only accounts matching the include will match. + bool match = (*regexps.begin()).exclude; + for (std::list<mask>::const_iterator r = regexps.begin(); r != regexps.end(); r++) { @@ -36,6 +37,7 @@ static inline bool matches(const std::list<mask>& regexps, 0, 0, ovec, 3) >= 0) match = ! (*r).exclude; } + return match; } @@ -82,48 +84,113 @@ static void display_total(std::ostream& out, totals& total_balance, } } + // Display balances for all child accounts + for (account::const_iterator i = acct->children.begin(); i != acct->children.end(); i++) 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)); + } +} + +static void record_regexp(char * pattern, std::list<mask>& regexps) +{ + bool exclude = false; + + char * pat = pattern; + if (*pat == '-') { + exclude = true; + pat++; + while (std::isspace(*pat)) + pat++; + } + else if (*pat == '+') { + pat++; + while (std::isspace(*pat)) + pat++; + } + + const char *error; + int erroffset; + pcre * re = pcre_compile(pat, PCRE_CASELESS, &error, &erroffset, NULL); + if (! re) + std::cerr << "Warning: Failed to compile regexp: " << pattern + << std::endl; + else + regexps.push_back(mask(exclude, re)); +} + +////////////////////////////////////////////////////////////////////// +// +// Balance reporting code +// + void report_balances(int argc, char **argv, std::ostream& out) { + std::map<const std::string, amount *> prices; + std::list<mask> regexps; + int c; optind = 1; - while (-1 != (c = getopt(argc, argv, "cCsSn"))) { + while (-1 != (c = getopt(argc, argv, "cCsSni:p:"))) { switch (char(c)) { case 'c': show_current = 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': + if (access(optarg, R_OK) != -1) { + std::ifstream include(optarg); + + while (! include.eof()) { + char buf[80]; + include.getline(buf, 79); + if (*buf && ! std::isspace(*buf)) + record_regexp(buf, 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; } } // Compile the list of specified regular expressions, which can be - // specified on the command line, or using an include/exclude file. + // specified on the command line, or using an include/exclude file - std::list<mask> regexps; - - for (; optind < argc; optind++) { - bool exclude = false; - char * pat = argv[optind]; - if (*pat == '-') { - exclude = true; - pat++; - } - - const char *error; - int erroffset; - pcre * re = pcre_compile(pat, PCRE_CASELESS, &error, &erroffset, NULL); - if (! re) - std::cerr << "Warning: Failed to compile regexp: " << argv[optind] - << std::endl; - else - regexps.push_back(mask(exclude, re)); - } + for (; optind < argc; optind++) + record_regexp(argv[optind], regexps); // Walk through all of the ledger entries, computing the account // totals @@ -165,34 +232,30 @@ void report_balances(int argc, char **argv, std::ostream& out) do_credit = true; } - if (do_credit) + if (! do_credit) + continue; + + std::map<const std::string, amount *>::iterator pi + = prices.find((*x)->cost->comm_symbol()); + + if (pi == prices.end()) { balance->credit((*x)->cost); + } else { + amount * value = (*x)->cost->value((*pi).second); + balance->credit(value); + delete value; + } } } } -#if 0 - // Print out the balance report header - - std::string which = "Future"; - if (show_current) - which = "Current"; - else if (show_cleared) - which = "Cleared"; - - out.width(20); - out << std::right << which << std::endl - << "--------------------" << std::endl; -#endif - - // Walk through all the top-level accounts, given the balance + // Walk through all the top-level accounts, giving the balance // report for each, and then for each of their children. totals total_balance; for (accounts_iterator i = accounts.begin(); i != accounts.end(); i++) - if (! (*i).second->parent) - display_total(out, total_balance, (*i).second, balances, regexps); + display_total(out, total_balance, (*i).second, balances, regexps); // Print the total of all the balances shown @@ -204,9 +267,13 @@ void report_balances(int argc, char **argv, std::ostream& out) for (std::map<account *, totals *>::iterator i = balances.begin(); i != balances.end(); - i++) { + i++) + delete (*i).second; + + for (std::map<const std::string, amount *>::iterator i = prices.begin(); + i != prices.end(); + i++) delete (*i).second; - } } } // namespace ledger @@ -18,9 +18,9 @@ static amount * curr_value; static std::string curr_quant; static XML_Parser current_parser; -accounts_t accounts_by_id; +static accounts_t accounts_by_id; -enum { +static enum { NO_ACTION, ACCOUNT_NAME, ACCOUNT_ID, @@ -97,7 +97,8 @@ static void endElement(void *userData, const char *name) { if (std::strcmp(name, "gnc:account") == 0) { assert(curr_account); - accounts.insert(accounts_entry(curr_account->name, curr_account)); + if (! curr_account->parent) + accounts.insert(accounts_entry(curr_account->name, curr_account)); accounts_by_id.insert(accounts_entry(curr_account_id, curr_account)); curr_account = NULL; } @@ -1,5 +1,5 @@ #ifndef _LEDGER_H -#define _LEDGER_H "$Revision: 1.4 $" +#define _LEDGER_H "$Revision: 1.5 $" ////////////////////////////////////////////////////////////////////// // @@ -122,9 +122,10 @@ class amount public: virtual ~amount() {} + virtual commodity * comm() const = 0; virtual const std::string& comm_symbol() const = 0; virtual amount * copy() const = 0; - virtual amount * value() const = 0; + virtual amount * value(amount * pr = NULL) const = 0; // Test if non-zero @@ -4,11 +4,6 @@ #include <pcre.h> // Perl regular expression library -////////////////////////////////////////////////////////////////////// -// -// Command-line parser and top-level logic. -// - namespace ledger { extern bool parse_ledger(std::istream& in); extern bool parse_gnucash(std::istream& in); @@ -31,6 +26,11 @@ void show_help(std::ostream& out) << " print print all ledger entries" << std::endl; } +////////////////////////////////////////////////////////////////////// +// +// Command-line parser and top-level logic. +// + int main(int argc, char *argv[]) { // Global defaults @@ -40,38 +40,53 @@ int main(int argc, char *argv[]) // Parse the command-line options + std::istream * file = NULL; + int c; - while (-1 != (c = getopt(argc, argv, "+hw"))) { + while (-1 != (c = getopt(argc, argv, "+hwf:"))) { switch (char(c)) { case 'h': show_help(std::cout); break; case 'w': use_warnings = true; break; + case 'f': file = new std::ifstream(optarg); break; } } if (optind == argc) { - std::cerr << "usage: ledger [options] DATA_FILE COMMAND [ARGS]" + std::cerr << "usage: ledger [options] COMMAND [options] [ARGS]" << std::endl + << std::endl + << "ledger options:" << std::endl + << " -f FILE specify pathname of ledger data file" << std::endl << std::endl - << "options:" << std::endl - << " -s show sub-accounts in balance totals" << std::endl - << " -S show empty accounts in balance totals" << std::endl << "commands:" << std::endl << " balance show balance totals" << std::endl - << " print print all ledger entries" << std::endl; + << " print print all ledger entries" << std::endl + << std::endl + << "`balance' command options:" << std::endl + << " -s show sub-accounts in balance totals" << std::endl + << " -S show empty accounts in balance totals" << std::endl; return 1; } - // Parse the ledger + // The -f option is required + + if (! file) { + std::cerr << "Please specify the ledger file using the -f option." + << std::endl; + return 1; + } - std::ifstream file(argv[optind++]); + // Parse the ledger char buf[32]; - file.get(buf, 31); - file.seekg(0); + file->get(buf, 31); + file->seekg(0); if (std::strncmp(buf, "<?xml version=\"1.0\"?>", 21) == 0) - parse_gnucash(file); + parse_gnucash(*file); else - parse_ledger(file); + parse_ledger(*file); + + delete file; // Process the command @@ -82,3 +97,5 @@ int main(int argc, char *argv[]) else if (command == "print") print_ledger(argc - optind, &argv[optind], std::cout); } + +// main.cc ends here. @@ -8,12 +8,7 @@ namespace ledger { -////////////////////////////////////////////////////////////////////// -// -// Ledger parser -// - -char * next_element(char * buf, bool variable = false) +static char * next_element(char * buf, bool variable = false) { char * p; @@ -34,7 +29,8 @@ char * next_element(char * buf, bool variable = false) static int linenum = 0; -inline void finalize_entry(entry * curr) { +static inline void finalize_entry(entry * curr) +{ if (curr) { if (! curr->validate()) { std::cerr << "Failed to balance the following transaction, " @@ -46,6 +42,11 @@ inline void finalize_entry(entry * curr) { } } +////////////////////////////////////////////////////////////////////// +// +// Ledger parser +// + bool parse_ledger(std::istream& in) { static std::time_t now = std::time(NULL); @@ -150,6 +151,7 @@ bool parse_ledger(std::istream& in) // If there is no amount given, it is intended as an implicit // amount; we must use the opposite of the value of the // preceding transaction. + if (! cost_str || *cost_str == ';') { if (cost_str) { while (*cost_str == ';' || std::isspace(*cost_str)) |