diff --git a/Makefile b/Makefile index 0cffe073..df54748c 100644 --- a/Makefile +++ b/Makefile @@ -1,22 +1,27 @@ -define GNUCASH -true -endef - -CODE = amount.cc ledger.cc parse.cc \ - balance.cc register.cc equity.cc main.cc -ifdef GNUCASH -CODE := $(CODE) gnucash.cc -endif +CODE = amount.cc \ + ledger.cc \ + parse.cc \ + balance.cc \ + register.cc \ + equity.cc \ + main.cc OBJS = $(patsubst %.cc,%.o,$(CODE)) -CFLAGS = -Wall -ansi -pedantic -DDEFAULT_COMMODITY="\"\$$\"" -DHUQUQULLAH=1 -#DFLAGS = -O3 -fomit-frame-pointer -DFLAGS = -g # -pg +CFLAGS = -Wall -ansi -pedantic -DDEFAULT_COMMODITY="\"\$$\"" +CFLAGS := $(CFLAGS) -DHUQUQULLAH=1 + +DFLAGS = -O3 -fomit-frame-pointer +#DFLAGS = -g -O2 # -pg + INCS = -I/usr/include/xmltok + LIBS = -lgmpxx -lgmp -lpcre + ifdef GNUCASH -LIBS := $(LIBS) -lxmlparse +CODE := $(CODE) gnucash.cc +CFLAGS := $(CFLAGS) -DREAD_GNUCASH=1 +LIBS := $(LIBS) -lxmlparse endif all: make.deps ledger @@ -28,7 +33,7 @@ ledger: $(OBJS) g++ $(CFLAGS) $(INCS) $(DFLAGS) -c -o $@ $< clean: - rm -f libledger.so ledger *.o + rm -f ledger *.o rebuild: clean deps all diff --git a/amount.cc b/amount.cc index ac2288ad..e0aca70b 100644 --- a/amount.cc +++ b/amount.cc @@ -474,9 +474,12 @@ static commodity * parse_amount(mpz_t out, const char * num, static char buf[256]; bool saw_commodity = false; + bool prefix = false; + bool separate = true; + bool thousands = true; + bool european = false; std::string symbol; - bool prefix, separate, thousands, european; int precision, result; if (ovector[base * 2] >= 0) { diff --git a/balance.cc b/balance.cc index d006f83e..fe8dbe16 100644 --- a/balance.cc +++ b/balance.cc @@ -4,17 +4,17 @@ namespace ledger { -extern bool show_cleared; +extern bool show_cleared; extern std::time_t begin_date; -extern bool have_beginning; +extern bool have_beginning; extern std::time_t end_date; -extern bool have_ending; +extern bool have_ending; -static bool show_children; -static bool show_empty; -static bool no_subtotals; -static bool full_names; +static bool show_children; +static bool show_empty; +static bool no_subtotals; +static bool full_names; static bool account_matches(const account * acct, const std::list& regexps, @@ -41,31 +41,25 @@ static bool account_matches(const account * acct, return match; } -static void display_total(std::ostream& out, totals& total_balance, +static void display_total(std::ostream& out, totals& balance, const account * acct, bool top_level, - const std::map& balances, const std::list& regexps) { bool displayed = false; - std::map::const_iterator b = - balances.find(const_cast(acct)); - if (b != balances.end()) { - totals * balance = (*b).second; - if (balance && (show_empty || *balance)) { - displayed = true; + if (acct->display && (show_empty || acct->balance)) { + displayed = true; - out << *balance; - if (top_level) - total_balance.credit(*balance); + out << acct->balance; + if (top_level) + balance.credit(acct->balance); - if (acct->parent && ! no_subtotals && ! full_names) { - for (const account * a = acct; a; a = a->parent) - out << " "; - out << acct->name << std::endl; - } else { - out << " " << *acct << std::endl; - } + if (acct->parent && ! no_subtotals && ! full_names) { + for (const account * a = acct; a; a = a->parent) + out << " "; + out << acct->name << std::endl; + } else { + out << " " << *acct << std::endl; } } @@ -74,8 +68,7 @@ static void display_total(std::ostream& out, totals& total_balance, for (account::const_iterator i = acct->children.begin(); i != acct->children.end(); i++) - display_total(out, total_balance, (*i).second, ! displayed, - balances, regexps); + display_total(out, balance, (*i).second, ! displayed, regexps); } ////////////////////////////////////////////////////////////////////// @@ -122,11 +115,14 @@ void report_balances(int argc, char **argv, std::ostream& out) // Walk through all of the ledger entries, computing the account // totals - std::map balances; - for (entries_iterator i = main_ledger.entries.begin(); i != main_ledger.entries.end(); i++) { + if ((have_beginning && difftime((*i)->date, begin_date) < 0) || + (have_ending && difftime((*i)->date, end_date) >= 0) || + (show_cleared && ! (*i)->cleared)) + continue; + for (std::list::iterator x = (*i)->xacts.begin(); x != (*i)->xacts.end(); x++) { @@ -140,27 +136,8 @@ void report_balances(int argc, char **argv, std::ostream& out) else if (! (true_match || show_children || ! acct->parent)) continue; - totals * balance = NULL; - - std::map::iterator t = balances.find(acct); - if (t == balances.end()) { - balance = new totals; - balances.insert(std::pair(acct, balance)); - } else { - balance = (*t).second; - } - - bool do_credit = true; - if (have_beginning && difftime((*i)->date, begin_date) < 0) - do_credit = false; - else if (have_ending && difftime((*i)->date, end_date) > 0) - do_credit = false; - else if (show_cleared && ! (*i)->cleared) - do_credit = false; - if (! do_credit) - continue; - - balance->credit((*x)->cost->street()); + acct->display = true; + acct->balance.credit((*x)->cost->street()); } } } @@ -168,25 +145,18 @@ void report_balances(int argc, char **argv, std::ostream& out) // Walk through all the top-level accounts, giving the balance // report for each, and then for each of their children. - totals total_balance; + totals balance; for (accounts_iterator i = main_ledger.accounts.begin(); i != main_ledger.accounts.end(); i++) - display_total(out, total_balance, (*i).second, true, balances, regexps); + display_total(out, balance, (*i).second, true, regexps); // Print the total of all the balances shown if (! no_subtotals) out << "--------------------" << std::endl - << total_balance << std::endl; - - // Free up temporary variables created on the heap - - for (std::map::iterator i = balances.begin(); - i != balances.end(); - i++) - delete (*i).second; + << balance << std::endl; } } // namespace ledger diff --git a/gnucash.cc b/gnucash.cc index 0166720c..7866f9c0 100644 --- a/gnucash.cc +++ b/gnucash.cc @@ -17,8 +17,8 @@ static commodity * curr_comm; static amount * curr_value; static std::string curr_quant; static XML_Parser current_parser; - -static accounts_t accounts_by_id; +static bool do_compute; +static accounts_t accounts_by_id; static enum { NO_ACTION, @@ -205,7 +205,9 @@ static void dataHandler(void *userData, const char *s, int len) std::string value = curr_quant + " " + (*i).second->comm->symbol; xact->cost = create_amount(value.c_str(), curr_value); - xact->acct->balance.credit(xact->cost); + + if (do_compute) + xact->acct->balance.credit(xact->cost); break; } @@ -224,13 +226,14 @@ static void dataHandler(void *userData, const char *s, int len) } } -bool parse_gnucash(std::istream& in) +bool parse_gnucash(std::istream& in, bool compute_balances) { char buf[BUFSIZ]; curr_account = NULL; curr_entry = NULL; curr_comm = NULL; + do_compute = compute_balances; action = NO_ACTION; diff --git a/ledger.cc b/ledger.cc index 548d51c5..a3c82fbb 100644 --- a/ledger.cc +++ b/ledger.cc @@ -96,12 +96,16 @@ bool entry::matches(const std::list& regexps) const } } +#ifdef DO_CLEANUP + totals::~totals() { for (iterator i = amounts.begin(); i != amounts.end(); i++) delete (*i).second; } +#endif // DO_CLEANUP + void totals::credit(const totals& other) { for (const_iterator i = other.amounts.begin(); @@ -236,9 +240,10 @@ bool matches(const std::list& regexps, const std::string& str, return match; } +#ifdef DO_CLEANUP + state::~state() { -#if 0 for (commodities_iterator i = commodities.begin(); i != commodities.end(); i++) @@ -253,9 +258,10 @@ state::~state() i != entries.end(); i++) delete *i; -#endif } +#endif // DO_CLEANUP + void state::record_price(const char * setting) { char buf[128]; @@ -275,6 +281,10 @@ void state::record_price(const char * setting) account * state::find_account(const char * name, bool create) { + accounts_iterator i = accounts_cache.find(name); + if (i != accounts_cache.end()) + return (*i).second; + char * buf = new char[std::strlen(name) + 1]; std::strcpy(buf, name); @@ -307,6 +317,9 @@ account * state::find_account(const char * name, bool create) delete[] buf; + if (current) + accounts_cache.insert(accounts_entry(name, current)); + return current; } diff --git a/ledger.h b/ledger.h index 5662162b..dc689536 100644 --- a/ledger.h +++ b/ledger.h @@ -1,5 +1,5 @@ #ifndef _LEDGER_H -#define _LEDGER_H "$Revision: 1.11 $" +#define _LEDGER_H "$Revision: 1.12 $" ////////////////////////////////////////////////////////////////////// // @@ -172,10 +172,12 @@ struct transaction transaction() : acct(NULL), cost(NULL) {} +#ifdef DO_CLEANUP ~transaction() { if (cost) delete cost; } +#endif }; struct entry @@ -189,6 +191,12 @@ struct entry std::list xacts; entry() : cleared(false) {} + +#ifdef DO_CLEANUP + // 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 + // like a good citizen. + ~entry() { for (std::list::iterator i = xacts.begin(); i != xacts.end(); @@ -196,6 +204,7 @@ struct entry delete *i; } } +#endif bool matches(const std::list& regexps) const; void print(std::ostream& out) const; @@ -221,7 +230,9 @@ struct totals map amounts; +#ifdef DO_CLEANUP ~totals(); +#endif void credit(const amount * val) { std::pair result = @@ -257,6 +268,7 @@ struct account std::string name; commodity * comm; // default commodity for this account totals balance; + bool display; typedef std::map map; typedef map::iterator iterator; @@ -266,7 +278,7 @@ struct account map children; account(const std::string& _name, struct account * _parent = NULL) - : parent(_parent), name(_name) {} + : parent(_parent), name(_name), display(false) {} const std::string as_str() const { if (! parent) @@ -297,10 +309,13 @@ struct state { commodities_t commodities; accounts_t accounts; + accounts_t accounts_cache; // maps full names to accounts entries_t entries; totals prices; +#ifdef DO_CLEANUP ~state(); +#endif void record_price(const char * setting); account * find_account(const char * name, bool create = true); diff --git a/main.cc b/main.cc index b2c2fd6a..78feb961 100644 --- a/main.cc +++ b/main.cc @@ -3,8 +3,10 @@ #include namespace ledger { - extern bool parse_ledger(std::istream& in); - extern bool parse_gnucash(std::istream& in); + extern bool parse_ledger(std::istream& in, bool compute_balances); +#ifdef READ_GNUCASH + extern bool parse_gnucash(std::istream& in, bool compute_balances); +#endif extern void report_balances(int argc, char **argv, std::ostream& out); extern void print_register(int argc, char **argv, std::ostream& out); @@ -188,16 +190,20 @@ int main(int argc, char *argv[]) // Parse the ledger +#ifdef READ_GNUCASH char buf[32]; file->get(buf, 31); file->seekg(0); if (std::strncmp(buf, "", 21) == 0) - parse_gnucash(*file); + parse_gnucash(*file, command == "equity"); else - parse_ledger(*file); +#endif + parse_ledger(*file, command == "equity"); +#ifdef DO_CLEANUP delete file; +#endif // Process the command diff --git a/parse.cc b/parse.cc index 8ff5207a..309b90a4 100644 --- a/parse.cc +++ b/parse.cc @@ -50,38 +50,33 @@ static inline void finalize_entry(entry * curr) // Ledger parser // -bool parse_ledger(std::istream& in) +bool parse_ledger(std::istream& in, bool compute_balances) { - static std::time_t now = std::time(NULL); - static struct std::tm * now_tm = std::localtime(&now); - static int current_year = now_tm->tm_year + 1900; + std::time_t now = std::time(NULL); + struct std::tm * now_tm = std::localtime(&now); + int current_year = now_tm->tm_year + 1900; - static char line[1024]; + char line[1024]; - static struct std::tm moment; + struct std::tm moment; memset(&moment, 0, sizeof(struct std::tm)); entry * curr = NULL; // Compile the regular expression used for parsing amounts - static pcre * entry_re = NULL; - if (! entry_re) { - const char *error; - int erroffset; - static const std::string regexp = - "^(([0-9]{4})[./])?([0-9]+)[./]([0-9]+)\\s+(\\*\\s+)?" - "(\\(([^)]+)\\)\\s+)?(.+)"; - entry_re = pcre_compile(regexp.c_str(), 0, &error, &erroffset, NULL); - } + const char *error; + int erroffset; + static const std::string regexp = + "^(([0-9]{4})[./])?([0-9]+)[./]([0-9]+)\\s+(\\*\\s+)?" + "(\\(([^)]+)\\)\\s+)?(.+)"; + pcre * entry_re = pcre_compile(regexp.c_str(), 0, + &error, &erroffset, NULL); while (! in.eof()) { in.getline(line, 1023); linenum++; - if (in.eof()) { - break; - } - else if (line[0] == '\n') { + if (line[0] == '\n') { continue; } else if (std::isdigit(line[0])) { @@ -96,28 +91,28 @@ bool parse_ledger(std::istream& in) continue; } + // If we haven't finished with the last entry yet, do so now + if (curr) finalize_entry(curr); + curr = new entry; // Parse the date - int mday, mon, year = current_year; - + int year = current_year; if (ovector[1 * 2] >= 0) { pcre_copy_substring(line, ovector, matched, 2, buf, 255); year = std::atoi(buf); } - if (ovector[3 * 2] >= 0) { - pcre_copy_substring(line, ovector, matched, 3, buf, 255); - mon = std::atoi(buf); - } + assert(ovector[3 * 2] >= 0); + pcre_copy_substring(line, ovector, matched, 3, buf, 255); + int mon = std::atoi(buf); - if (ovector[4 * 2] >= 0) { - pcre_copy_substring(line, ovector, matched, 4, buf, 255); - mday = std::atoi(buf); - } + assert(ovector[4 * 2] >= 0); + pcre_copy_substring(line, ovector, matched, 4, buf, 255); + int mday = std::atoi(buf); moment.tm_mday = mday; moment.tm_mon = mon - 1; @@ -125,6 +120,8 @@ bool parse_ledger(std::istream& in) curr->date = std::mktime(&moment); + // Parse the remaining entry details + if (ovector[5 * 2] >= 0) curr->cleared = true; @@ -164,6 +161,7 @@ bool parse_ledger(std::istream& in) cost_str++; xact->note = cost_str; } + xact->cost = curr->xacts.front()->cost->copy(); xact->cost->negate(); } @@ -198,7 +196,8 @@ bool parse_ledger(std::istream& in) #endif xact->acct = main_ledger.find_account(p); - xact->acct->balance.credit(xact->cost); + if (compute_balances) + xact->acct->balance.credit(xact->cost); curr->xacts.push_back(xact); @@ -215,9 +214,11 @@ bool parse_ledger(std::istream& in) temp = xact->cost->value(); t->cost = temp->value(huquq); delete temp; - t->acct->balance.credit(t->cost); curr->xacts.push_back(t); + if (compute_balances) + t->acct->balance.credit(t->cost); + // Balance the above transaction by recording the inverse in // Expenses:Huququ'llah. t = new transaction(); @@ -226,8 +227,10 @@ bool parse_ledger(std::istream& in) t->cost = temp->value(huquq); delete temp; t->cost->negate(); - t->acct->balance.credit(t->cost); curr->xacts.push_back(t); + + if (compute_balances) + t->acct->balance.credit(t->cost); } #endif }