diff options
-rw-r--r-- | Makefile | 13 | ||||
-rw-r--r-- | amount.cc | 9 | ||||
-rw-r--r-- | gnucash.cc | 5 | ||||
-rw-r--r-- | ledger.cc | 81 | ||||
-rw-r--r-- | ledger.h | 122 | ||||
-rw-r--r-- | main.cc | 341 | ||||
-rw-r--r-- | parse.cc | 11 | ||||
-rw-r--r-- | reports.cc | 63 |
8 files changed, 198 insertions, 447 deletions
@@ -1,14 +1,9 @@ -define GNUCASH -true -endef - -CODE = amount.cc ledger.cc parse.cc reports.cc - -OBJS = $(patsubst %.cc,%.o,$(CODE)) - +CODE = amount.cc ledger.cc parse.cc reports.cc +OBJS = $(patsubst %.cc,%.o,$(CODE)) CFLAGS = -Wall -ansi -pedantic #DFLAGS = -O3 -fomit-frame-pointer -mcpu=pentium -DFLAGS = -g -DDEBUG=1 +#DFLAGS = -g -DDEBUG=1 +DFLAGS = -O2 INCS = LIBS = -lgmpxx -lgmp -lpcre @@ -144,14 +144,10 @@ static void multiply(mpz_t out, const mpz_t l, const mpz_t r) amount * gmp_amount::copy() const { gmp_amount * new_amt = new gmp_amount(); -#if 0 - // Don't copy the price - new_amt->priced = priced; - mpz_set(new_amt->price, price); - new_amt->price_comm = price_comm; -#endif + mpz_set(new_amt->quantity, quantity); new_amt->quantity_comm = quantity_comm; + return new_amt; } @@ -242,6 +238,7 @@ amount * gmp_amount::street(bool get_quotes) const if (amt->commdty() == old->commdty()) break; } + return amt; } @@ -203,10 +203,13 @@ static void dataHandler(void *userData, const char *s, int len) delete curr_value; curr_value = NULL; } + xact->cost = create_amount(value.c_str(), curr_value); - if (curr_value) + if (curr_value) { delete curr_value; + curr_value = NULL; + } if (do_compute) xact->acct->balance.credit(xact->cost); @@ -4,9 +4,15 @@ namespace ledger { -bool use_warnings = false; +bool use_warnings = false; book * main_ledger; +commodity::~commodity() +{ + if (price) + delete price; +} + const std::string transaction::acct_as_str() const { char * begin = NULL; @@ -96,8 +102,11 @@ bool entry::validate(bool show_unaccounted) const for (std::list<transaction *>::const_iterator x = xacts.begin(); x != xacts.end(); x++) - if ((*x)->cost && (*x)->must_balance) - balance.credit((*x)->cost->value()); + if ((*x)->cost && (*x)->must_balance) { + amount * value = (*x)->cost->value(); + balance.credit(value); + delete value; + } if (show_unaccounted && ! balance.is_zero()) { std::cerr << "Unaccounted-for balances are:" << std::endl; @@ -135,6 +144,23 @@ totals::~totals() delete (*i).second; } +void totals::credit(const amount * val) +{ + iterator i = amounts.find(val->commdty()); + if (i != amounts.end()) + (*i).second->credit(val); +#ifndef DEBUG + else + amounts.insert(pair(val->commdty(), val->copy())); +#else + else { + std::pair<iterator, bool> result = + amounts.insert(pair(val->commdty(), val->copy())); + assert(result.second); + } +#endif +} + void totals::credit(const totals& other) { for (const_iterator i = other.amounts.begin(); @@ -169,6 +195,24 @@ void totals::print(std::ostream& out, int width) const } } +account::~account() +{ + for (accounts_map_iterator i = children.begin(); + i != children.end(); + i++) + delete (*i).second; +} + +const std::string account::as_str() const +{ + if (! parent) + return name; + else if (full_name.empty()) + full_name = parent->as_str() + ":" + name; + + return full_name; +} + // Print out the entire ledger that was read in, sorted by date. // This can be used to "wash" ugly ledger files. @@ -216,11 +260,19 @@ void read_regexps(const std::string& path, regexps_map& regexps) char buf[80]; file.getline(buf, 79); if (*buf && ! std::isspace(*buf)) - regexps.push_back(new mask(buf)); + regexps.push_back(mask(buf)); } } } +bool mask::match(const std::string& str) const +{ + static int ovec[30]; + int result = pcre_exec(regexp, NULL, str.c_str(), str.length(), + 0, 0, ovec, 30); + return result >= 0 && ! exclude; +} + bool matches(const regexps_map& regexps, const std::string& str, bool * by_exclusion) { @@ -235,15 +287,15 @@ bool matches(const regexps_map& regexps, const std::string& str, for (regexps_map_const_iterator r = regexps.begin(); r != regexps.end(); r++) { -// out << " Trying: " << (*r)->pattern << std::endl; +// out << " Trying: " << (*r).pattern << std::endl; static int ovec[30]; - int result = pcre_exec((*r)->regexp, NULL, str.c_str(), str.length(), + int result = pcre_exec((*r).regexp, NULL, str.c_str(), str.length(), 0, 0, ovec, 30); if (result >= 0) { // out << " Definite "; - match = ! (*r)->exclude; + match = ! (*r).exclude; // if (match) // out << "match"; // else @@ -255,7 +307,7 @@ bool matches(const regexps_map& regexps, const std::string& str, // out << " failure code = " << result << std::endl; - if ((*r)->exclude) { + if ((*r).exclude) { if (! match) { match = ! definite; // if (match) @@ -294,6 +346,19 @@ book::~book() i++) delete (*i).second; + for (virtual_map_iterator i = virtual_mapping.begin(); + i != virtual_mapping.end(); + i++) { + delete (*i).first; + + for (std::list<transaction *>::iterator j = (*i).second->begin(); + j != (*i).second->end(); + j++) { + delete *j; + } + delete (*i).second; + } + for (entries_list_iterator i = entries.begin(); i != entries.end(); i++) @@ -1,5 +1,5 @@ #ifndef _LEDGER_H -#define _LEDGER_H "$Revision: 1.21 $" +#define _LEDGER_H "$Revision: 1.22 $" ////////////////////////////////////////////////////////////////////// // @@ -8,6 +8,7 @@ // by John Wiegley <johnw@newartisans.com> // // Copyright (c) 2003 New Artisans, Inc. All Rights Reserved. +// #include <iostream> #include <string> @@ -26,25 +27,32 @@ namespace ledger { -struct amount; -struct commodity +class amount; +class commodity { + commodity(const commodity&); + + public: std::string name; std::string symbol; mutable amount * price; // the current price - bool prefix; - bool separate; - bool thousands; - bool european; + bool prefix; + bool separate; + bool thousands; + bool european; + + int precision; - int precision; + explicit commodity() : price(NULL), prefix(false), separate(true), + thousands(false), european(false) {} - commodity() : price(NULL), prefix(false), separate(true), - thousands(false), european(false) {} - commodity(const std::string& sym, bool pre = false, bool sep = true, - bool thou = true, bool euro = false, int prec = 2); + explicit commodity(const std::string& sym, bool pre = false, + bool sep = true, bool thou = true, + bool euro = false, int prec = 2); + + ~commodity(); }; typedef std::map<const std::string, commodity *> commodities_map; @@ -83,22 +91,30 @@ class amount extern amount * create_amount(const std::string& value, const amount * cost = NULL); -struct mask +class mask { + // jww (2003-10-08): need to correct this + //mask(const mask&); + + public: bool exclude; std::string pattern; pcre * regexp; - mask(const std::string& pattern); + explicit mask(const std::string& pattern); +#if 0 ~mask() { pcre_free(regexp); } +#endif + + bool match(const std::string& str) const; }; -typedef std::list<mask *> regexps_map; -typedef std::list<mask *>::iterator regexps_map_iterator; -typedef std::list<mask *>::const_iterator regexps_map_const_iterator; +typedef std::list<mask> regexps_map; +typedef std::list<mask>::iterator regexps_map_iterator; +typedef std::list<mask>::const_iterator regexps_map_const_iterator; void record_regexp(const std::string& pattern, regexps_map& regexps); void read_regexps(const std::string& path, regexps_map& regexps); @@ -106,9 +122,12 @@ bool matches(const regexps_map& regexps, const std::string& str, bool * by_exclusion = NULL); -struct account; -struct transaction +class account; +class transaction { + transaction(const transaction&); + + public: account * acct; amount * cost; @@ -118,7 +137,7 @@ struct transaction bool must_balance; bool specified; - transaction(account * _acct = NULL, amount * _cost = NULL) + explicit transaction(account * _acct = NULL, amount * _cost = NULL) : acct(_acct), cost(_cost), is_virtual(false), must_balance(true), specified(false) {} @@ -134,8 +153,11 @@ struct transaction }; -struct entry +class entry { + entry(const entry&); + + public: std::time_t date; std::string code; std::string desc; @@ -144,7 +166,7 @@ struct entry std::list<transaction *> xacts; - entry() : cleared(false) {} + explicit entry() : cleared(false) {} // If we're running as a command-line tool, it's cheaper to just // throw away the heap on exit, than spend time freeing things up @@ -175,8 +197,11 @@ typedef entries_list::iterator entries_list_iterator; typedef entries_list::const_iterator entries_list_const_iterator; -struct totals +class totals { + totals(const totals&); + + public: typedef std::map<commodity *, amount *> map; typedef map::iterator iterator; typedef map::const_iterator const_iterator; @@ -184,24 +209,15 @@ struct totals map amounts; + totals() {} ~totals(); - void credit(const amount * val) { - std::pair<iterator, bool> result = - amounts.insert(pair(val->commdty(), val->copy())); - if (! result.second) - amounts[val->commdty()]->credit(val); - } + void credit(const amount * val); void credit(const totals& other); bool is_zero() const; void print(std::ostream& out, int width) const; - - // Returns an allocated entity - amount * sum(commodity * comm) { - return amounts[comm]; - } }; @@ -209,8 +225,11 @@ typedef std::map<const std::string, account *> accounts_map; typedef accounts_map::iterator accounts_map_iterator; typedef std::pair<const std::string, account *> accounts_map_pair; -struct account +class account { + account(const account&); + + public: account * parent; std::string name; @@ -223,30 +242,23 @@ struct account mutable std::string full_name; - account() : parent(NULL), checked(0) {} + explicit account() : parent(NULL), checked(0) {} - account(const std::string& _name, struct account * _parent = NULL) + explicit account(const std::string& _name, + struct account * _parent = NULL) : parent(_parent), name(_name), checked(0) {} - const std::string as_str() const { - if (! parent) - return name; - else if (full_name.empty()) - full_name = parent->as_str() + ":" + name; + ~account(); - return full_name; - } + const std::string as_str() const; }; -struct book +class book { - commodities_map commodities; - accounts_map accounts; - accounts_map accounts_cache; // maps full names to accounts - entries_list entries; - int current_year; + book(const book&); + public: typedef std::map<regexps_map *, std::list<transaction *> *> virtual_map; @@ -255,8 +267,14 @@ struct book typedef virtual_map::const_iterator virtual_map_iterator; - virtual_map virtual_mapping; + commodities_map commodities; + accounts_map accounts; + accounts_map accounts_cache; // maps full names to accounts + virtual_map virtual_mapping; + entries_list entries; + int current_year; + book() {} ~book(); template<typename Compare> @@ -269,7 +287,7 @@ struct book }; extern book * main_ledger; -extern bool use_warnings; +extern bool use_warnings; inline commodity::commodity(const std::string& sym, bool pre, bool sep, bool thou, bool euro, int prec) diff --git a/main.cc b/main.cc deleted file mode 100644 index 8fca0150..00000000 --- a/main.cc +++ /dev/null @@ -1,341 +0,0 @@ -#include "ledger.h" - -#define LEDGER_VERSION "1.1" - -#include <fstream> - -namespace ledger { - extern book * parse_ledger(std::istream& in, regexps_map& regexps, - bool compute_balances); -#ifdef READ_GNUCASH - extern book * parse_gnucash(std::istream& in, bool compute_balances); -#endif - - extern bool parse_date(const char * date_str, std::time_t * result, - const int year = -1); - extern void parse_price_setting(const std::string& setting); - - extern void report_balances(std::ostream& out, regexps_map& regexps); - extern void print_register(const std::string& acct_name, std::ostream& out, - regexps_map& regexps); - extern void equity_ledger(std::ostream& out, regexps_map& regexps); - - bool show_cleared; - bool show_virtual; - bool get_quotes; - bool show_children; - bool show_empty; - bool show_subtotals; - bool full_names; - - std::time_t begin_date; - bool have_beginning; - std::time_t end_date; - bool have_ending; -} - -using namespace ledger; - -static void show_help(std::ostream& out) -{ - std::cerr - << "usage: ledger [options] COMMAND [options] [REGEXPS]" << std::endl - << std::endl - << "ledger options:" << std::endl - << " -C also show cleared transactions" << std::endl - << " -d DATE specify an implicit date range (e.g., -d april)" - << std::endl - << " -b DATE specify a beginning date" << std::endl - << " -e DATE specify an ending date" << std::endl - << " -c do not show future entries (same as -e TODAY)" << std::endl - << " -f FILE specify pathname of ledger data file" << std::endl - << " -h display this help text" << std::endl - << " -R do not factor any virtual transactions" << std::endl - << " -V FILE use virtual mappings listed in FILE" << std::endl - << " -i FILE read the list of inclusion regexps from FILE" << std::endl - << " -p FILE read the list of prices from FILE" << std::endl - << " -P download price quotes from the Internet" << std::endl - << " (works by running the command \"getquote SYMBOL\")" - << std::endl - << " -v display version information" << std::endl - << " -w print out warnings where applicable" << std::endl - << std::endl - << "commands:" << std::endl - << " balance show balance totals" << std::endl - << " register display a register for ACCOUNT" << std::endl - << " print print all ledger entries" << std::endl - << " equity generate equity ledger for all entries" << std::endl - << std::endl - << "`balance' options:" << std::endl - << " -F print each account's full name" << std::endl - << " -n do not generate totals for parent accounts" << std::endl - << " -s show sub-accounts in balance totals" << std::endl - << " -S show empty accounts in balance totals" << std::endl; -} - -////////////////////////////////////////////////////////////////////// -// -// Command-line parser and top-level logic. -// - -int main(int argc, char * argv[]) -{ - std::istream * file = NULL; - std::string prices; - regexps_map regexps; - int index; - - have_beginning = false; - have_ending = false; - show_cleared = false; - show_virtual = true; - show_children = false; - show_empty = false; - show_subtotals = true; - full_names = false; - - // Parse the command-line options - - int c; - while (-1 != (c = getopt(argc, argv, "+b:e:d:cChRV:wf:i:p:PvsSnF"))) { - switch (char(c)) { - case 'b': - case 'e': { - std::time_t when; - if (! parse_date(optarg, &when)) { - std::cerr << "Error: Bad date string: " << optarg << std::endl; - return 1; - } - - if (c == 'b') { - begin_date = when; - have_beginning = true; - } else { - end_date = when; - have_ending = true; - } - break; - } - -#if 0 - case 'd': { - if (! parse_date(optarg, &begin_date)) { - std::cerr << "Error: Bad date string: " << optarg << std::endl; - return 1; - } - have_beginning = true; - - struct std::tm when, then; - std::memset(&then, 0, sizeof(struct std::tm)); - - std::time_t now = std::time(NULL); - struct std::tm * now_tm = std::localtime(&now); - - for (const char ** f = formats; *f; f++) { - memset(&when, INT_MAX, sizeof(struct std::tm)); - if (strptime(optarg, *f, &when)) { - then.tm_hour = 0; - then.tm_min = 0; - then.tm_sec = 0; - - if (when.tm_year != -1) - then.tm_year = when.tm_year + 1; - else - then.tm_year = now_tm->tm_year; - - if (std::strcmp(*f, "%Y") == 0) { - then.tm_mon = 0; - then.tm_mday = 1; - } else { - if (when.tm_mon != -1) - then.tm_mon = when.tm_mon + 1; - else - then.tm_mon = now_tm->tm_mon; - - if (when.tm_mday != -1) - then.tm_mday = when.tm_mday + 1; - else - then.tm_mday = now_tm->tm_mday; - } - - end_date = std::mktime(&then); - have_ending = true; - break; - } - } - break; - } -#endif - - case 'c': - end_date = std::time(NULL); - have_ending = true; - break; - - case 'h': show_help(std::cout); break; - case 'f': file = new std::ifstream(optarg); break; - - case 'C': show_cleared = true; break; - case 'R': show_virtual = false; break; - case 'w': use_warnings = true; break; - case 's': show_children = true; break; - case 'S': show_empty = true; break; - case 'n': show_subtotals = false; break; - case 'F': full_names = 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': - prices = optarg; - break; - - case 'P': - get_quotes = true; - break; - - case 'v': - std::cout - << "Ledger Accouting Tool " LEDGER_VERSION << std::endl - << " Copyright (c) 2003 John Wiegley <johnw@newartisans.com>" - << std::endl << std::endl - << "This program is made available under the terms of the BSD" - << std::endl - << "Public License. See the LICENSE file included with the" - << std::endl - << "distribution for details and disclaimer." << std::endl; - return 0; - } - } - - if (optind == argc) { - show_help(std::cout); - return 1; - } - - index = optind; - - if (use_warnings && (have_beginning || have_ending)) { - std::cout << "Reporting"; - - if (have_beginning) { - char buf[32]; - std::strftime(buf, 31, "%Y.%m.%d", std::localtime(&begin_date)); - std::cout << " from " << buf; - } - - if (have_ending) { - char buf[32]; - std::strftime(buf, 31, "%Y.%m.%d", std::localtime(&end_date)); - std::cout << " until " << buf; - } - - std::cout << std::endl; - } - - // A ledger data file must be specified - - if (! file) { - const char * p = std::getenv("LEDGER"); - if (p) - file = new std::ifstream(p); - - if (! file || ! *file) { - std::cerr << ("Please specify ledger file using -f option " - "or LEDGER environment variable.") - << std::endl; - return 1; - } - } - - // Read the command word - - const std::string command = argv[index++]; - - int name_index = index; - if (command == "register") { - if (optind == argc) { - std::cerr << ("Error: Must specify an account name " - "after the 'register' command.") << std::endl; - return 1; - } - index++; - } - - // Compile the list of specified regular expressions, which can be - // specified after the command, or using the '-i FILE' option - - for (; index < argc; index++) - regexps.push_back(new mask(argv[index])); - - // Parse the ledger - -#ifdef READ_GNUCASH - char buf[32]; - file->get(buf, 31); - file->seekg(0); - - if (std::strncmp(buf, "<?xml version=\"1.0\"?>", 21) == 0) - main_ledger = parse_gnucash(*file, command == "equity"); - else -#endif - main_ledger = parse_ledger(*file, regexps, command == "equity"); - - delete file; - - if (! main_ledger) - std::exit(1); - - // Record any prices specified by the user - - if (! prices.empty()) { - if (access(prices.c_str(), R_OK) != -1) { - std::ifstream pricedb(prices.c_str()); - while (! pricedb.eof()) { - char buf[80]; - pricedb.getline(buf, 79); - if (*buf && ! std::isspace(*buf)) - parse_price_setting(buf); - } - } else { - parse_price_setting(prices); - } - } - - // Process the command - - if (command == "balance") { - report_balances(std::cout, regexps); - } - else if (command == "register") { - print_register(argv[name_index], std::cout, regexps); - } - else if (command == "print") { - main_ledger->sort(cmp_entry_date()); - main_ledger->print(std::cout, regexps, true); - } - else if (command == "equity") { - equity_ledger(std::cout, regexps); - } - -#ifdef DEBUG - // Ordinarily, deleting the main ledger just isn't necessary at - // this point. - - delete main_ledger; - - // Delete the known regexp maps. - - for (regexps_map_iterator r = regexps.begin(); - r != regexps.end(); - r++) { - delete *r; - } -#endif -} - -// main.cc ends here. @@ -242,8 +242,11 @@ entry * parse_entry(std::istream& in, book * ledger) for (std::list<transaction *>::iterator x = curr->xacts.begin(); x != curr->xacts.end(); x++) - if ((*x)->cost && ! (*x)->is_virtual) - balance.credit((*x)->cost->value()); + if ((*x)->cost && ! (*x)->is_virtual) { + amount * value = (*x)->cost->value(); + balance.credit(value); + delete value; + } // If one transaction is of a different commodity than the others, // and it has no per-unit price, determine its price by dividing @@ -422,7 +425,7 @@ void parse_automated_transactions(std::istream& in, book * ledger) if (! masks) masks = new regexps_map; - masks->push_back(new mask(p)); + masks->push_back(mask(p)); } std::list<transaction *> * xacts = NULL; @@ -494,7 +497,7 @@ book * parse_ledger(std::istream& in, regexps_map& regexps, linenum++; // Add the regexp to whatever masks currently exist - regexps.push_back(new mask(line)); + regexps.push_back(mask(line)); break; case '=': // automated transactions @@ -22,6 +22,7 @@ static bool show_cleared; static bool show_virtual; static bool get_quotes; static bool show_children; +static bool show_sorted; static bool show_empty; static bool show_subtotals; static bool full_names; @@ -117,8 +118,11 @@ void report_balances(std::ostream& out, regexps_map& regexps) } } - if (acct->checked == 1) - acct->balance.credit((*x)->cost->street(get_quotes)); + if (acct->checked == 1) { + amount * street = (*x)->cost->street(get_quotes); + acct->balance.credit(street); + delete street; + } } } } @@ -163,7 +167,17 @@ static std::string truncated(const std::string& str, int width) void print_register(const std::string& acct_name, std::ostream& out, regexps_map& regexps) { - account * acct = main_ledger->find_account(acct_name, false); + account * acct = NULL; + mask acct_regex(acct_name); + + for (accounts_map_iterator i = main_ledger->accounts.begin(); + i != main_ledger->accounts.end(); + i++) + if (acct_regex.match((*i).second->as_str())) { + acct = (*i).second; + break; + } + if (! acct) { std::cerr << "Error: Unknown account name: " << acct_name << std::endl; @@ -380,19 +394,20 @@ int main(int argc, char * argv[]) regexps_map regexps; int index; - have_beginning = false; - have_ending = false; - show_cleared = false; - show_virtual = true; - show_children = false; - show_empty = false; - show_subtotals = true; - full_names = false; + have_beginning = false; + have_ending = false; + show_cleared = false; + show_virtual = true; + show_children = false; + show_sorted = false; + show_empty = false; + show_subtotals = true; + full_names = false; // Parse the command-line options int c; - while (-1 != (c = getopt(argc, argv, "+b:e:d:cChRV:wf:i:p:PvsSnF"))) { + while (-1 != (c = getopt(argc, argv, "+b:e:d:cChRV:wf:i:p:PvsSEnF"))) { switch (char(c)) { case 'b': case 'e': { @@ -468,13 +483,14 @@ int main(int argc, char * argv[]) break; case 'h': show_help(std::cout); break; - case 'f': file = new std::ifstream(optarg); break; + case 'f': file = new std::ifstream(optarg); break; case 'C': show_cleared = true; break; case 'R': show_virtual = false; break; case 'w': use_warnings = true; break; case 's': show_children = true; break; - case 'S': show_empty = true; break; + case 'S': show_sorted = true; break; + case 'E': show_empty = true; break; case 'n': show_subtotals = false; break; case 'F': full_names = true; break; @@ -565,7 +581,7 @@ int main(int argc, char * argv[]) // specified after the command, or using the '-i FILE' option for (; index < argc; index++) - regexps.push_back(new mask(argv[index])); + regexps.push_back(mask(argv[index])); // Parse the ledger @@ -607,10 +623,13 @@ int main(int argc, char * argv[]) report_balances(std::cout, regexps); } else if (command == "register") { + if (show_sorted) + main_ledger->sort(cmp_entry_date()); print_register(argv[name_index], std::cout, regexps); } else if (command == "print") { - main_ledger->sort(cmp_entry_date()); + if (show_sorted) + main_ledger->sort(cmp_entry_date()); main_ledger->print(std::cout, regexps, true); } else if (command == "equity") { @@ -618,18 +637,10 @@ int main(int argc, char * argv[]) } #ifdef DEBUG - // Ordinarily, deleting the main ledger just isn't necessary at - // this point. + // Ordinarily, deleting the main ledger isn't necessary, since the + // process is about to give back its heap to the OS. delete main_ledger; - - // Delete the known regexp maps. - - for (regexps_map_iterator r = regexps.begin(); - r != regexps.end(); - r++) { - delete *r; - } #endif } |