diff options
-rw-r--r-- | Makefile | 10 | ||||
-rw-r--r-- | amount.cc | 14 | ||||
-rw-r--r-- | balance.cc | 55 | ||||
-rw-r--r-- | equity.cc | 21 | ||||
-rw-r--r-- | gnucash.cc | 59 | ||||
-rw-r--r-- | ledger.cc | 80 | ||||
-rw-r--r-- | ledger.h | 71 | ||||
-rw-r--r-- | main.cc | 126 | ||||
-rw-r--r-- | parse.cc | 566 | ||||
-rw-r--r-- | register.cc | 28 |
10 files changed, 492 insertions, 538 deletions
@@ -1,3 +1,7 @@ +define GNUCASH +true +endef + CODE = amount.cc \ ledger.cc \ parse.cc \ @@ -8,9 +12,9 @@ CODE = amount.cc \ OBJS = $(patsubst %.cc,%.o,$(CODE)) -CFLAGS = -Wall -ansi -pedantic # -DDEBUG=1 -DFLAGS = -O3 -fomit-frame-pointer -mcpu=pentium -#DFLAGS = -g +CFLAGS = -Wall -ansi -pedantic +#DFLAGS = -O3 -fomit-frame-pointer -mcpu=pentium +DFLAGS = -g -DDEBUG=1 INCS = LIBS = -lgmpxx -lgmp -lpcre @@ -208,8 +208,8 @@ amount * gmp_amount::street() const for (int cycles = 0; cycles < 10; cycles++) { totals::iterator pi = - main_ledger.prices.amounts.find(amt->comm_symbol()); - if (pi == main_ledger.prices.amounts.end()) { + main_ledger->prices.amounts.find(amt->comm_symbol()); + if (pi == main_ledger->prices.amounts.end()) { using namespace std; if (! get_quotes) @@ -231,7 +231,7 @@ amount * gmp_amount::street() const char * p = strchr(buf, '\n'); if (p) *p = '\0'; - main_ledger.record_price((amt->comm_symbol() + "=" + buf).c_str()); + main_ledger->record_price((amt->comm_symbol() + "=" + buf).c_str()); continue; } break; @@ -347,10 +347,9 @@ static std::string amount_to_str(const commodity * comm, const mpz_t val, else if (! comm->thousands) s << quotient; else { - // jww (2003-09-29): use a smarter starting value - bool printed = false; + // jww (2003-09-29): use a smarter starting value for `powers' for (int powers = 27; powers >= 0; powers -= 3) { mpz_ui_pow_ui(divisor, 10, powers); mpz_tdiv_q(temp, quotient, divisor); @@ -554,8 +553,9 @@ static commodity * parse_amount(mpz_t out, const char * num, commodity * comm = NULL; if (saw_commodity) { - commodities_iterator item = main_ledger.commodities.find(symbol.c_str()); - if (item == main_ledger.commodities.end()) { + commodities_map_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 { @@ -5,17 +5,17 @@ namespace ledger { extern bool show_cleared; +extern bool show_virtual; +extern bool show_children; +extern bool show_empty; +extern bool show_subtotals; +extern bool full_names; extern std::time_t begin_date; extern bool have_beginning; extern std::time_t end_date; extern bool have_ending; -static bool show_children; -static bool show_empty; -static bool no_subtotals; -static bool full_names; - static void display_total(std::ostream& out, totals& balance, account * acct, bool top_level) { @@ -26,7 +26,7 @@ static void display_total(std::ostream& out, totals& balance, displayed = true; acct->balance.print(out, 20); - if (! no_subtotals && top_level) + if (show_subtotals && top_level) balance.credit(acct->balance); if (acct->parent && ! full_names && ! top_level) { @@ -40,7 +40,7 @@ static void display_total(std::ostream& out, totals& balance, // Display balances for all child accounts - for (accounts_iterator i = acct->children.begin(); + for (accounts_map_iterator i = acct->children.begin(); i != acct->children.end(); i++) display_total(out, balance, (*i).second, ! displayed); @@ -51,37 +51,13 @@ static void display_total(std::ostream& out, totals& balance, // Balance reporting code // -void report_balances(int argc, char ** argv, regexps_t& regexps, - std::ostream& out) +void report_balances(std::ostream& out, regexps_map& regexps) { - show_children = false; - show_empty = false; - no_subtotals = false; - full_names = false; - - optind = 1; - - int c; - while (-1 != (c = getopt(argc, argv, "sSnF"))) { - switch (char(c)) { - case 's': show_children = true; break; - case 'S': show_empty = true; break; - case 'n': no_subtotals = true; break; - case 'F': full_names = true; break; - } - } - - // 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, computing the account // totals - for (entries_iterator i = main_ledger.entries.begin(); - i != main_ledger.entries.end(); + for (entries_list_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) || @@ -91,9 +67,12 @@ void report_balances(int argc, char ** argv, regexps_t& regexps, for (std::list<transaction *>::iterator x = (*i)->xacts.begin(); x != (*i)->xacts.end(); x++) { + if (! show_virtual && (*x)->is_virtual) + continue; + for (account * acct = (*x)->acct; acct; - acct = no_subtotals ? NULL : acct->parent) { + acct = show_subtotals ? acct->parent : NULL) { if (acct->checked == 0) { if (regexps.empty()) { if (! (show_children || ! acct->parent)) @@ -131,14 +110,14 @@ void report_balances(int argc, char ** argv, regexps_t& regexps, totals balance; - for (accounts_iterator i = main_ledger.accounts.begin(); - i != main_ledger.accounts.end(); + for (accounts_map_iterator i = main_ledger->accounts.begin(); + i != main_ledger->accounts.end(); i++) display_total(out, balance, (*i).second, true); // Print the total of all the balances shown - if (! no_subtotals && ! balance.is_zero()) { + if (show_subtotals && ! balance.is_zero()) { out << "--------------------" << std::endl; balance.print(out, 20); out << std::endl; @@ -2,7 +2,7 @@ namespace ledger { -static void equity_entry(account * acct, regexps_t& regexps, +static void equity_entry(account * acct, regexps_map& regexps, std::ostream& out) { if (! acct->balance.is_zero() && @@ -27,7 +27,7 @@ static void equity_entry(account * acct, regexps_t& regexps, opening.xacts.push_back(xact); xact = new transaction(); - xact->acct = main_ledger.find_account("Equity:Opening Balances"); + xact->acct = main_ledger->find_account("Equity:Opening Balances"); xact->cost = (*i).second->street(); xact->cost->negate(); opening.xacts.push_back(xact); @@ -38,7 +38,7 @@ static void equity_entry(account * acct, regexps_t& regexps, // Display balances for all child accounts - for (accounts_iterator i = acct->children.begin(); + for (accounts_map_iterator i = acct->children.begin(); i != acct->children.end(); i++) equity_entry((*i).second, regexps, out); @@ -51,23 +51,14 @@ static void equity_entry(account * acct, regexps_t& regexps, // balances. // -void equity_ledger(int argc, char ** argv, regexps_t& regexps, - std::ostream& out) +void equity_ledger(std::ostream& out, regexps_map& regexps) { - optind = 1; - - // 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); - // The account have their current totals already generated as a // result of parsing. We just have to output those values. // totals - for (accounts_iterator i = main_ledger.accounts.begin(); - i != main_ledger.accounts.end(); + for (accounts_map_iterator i = main_ledger->accounts.begin(); + i != main_ledger->accounts.end(); i++) equity_entry((*i).second, regexps, out); } @@ -9,16 +9,15 @@ extern "C" { namespace ledger { -static account * curr_account; -static std::string curr_account_id; -static entry * curr_entry; -static commodity * entry_comm; -static commodity * curr_comm; -static amount * curr_value; -static std::string curr_quant; -static XML_Parser current_parser; -static bool do_compute; -static accounts_t accounts_by_id; +static account * curr_account; +static std::string curr_account_id; +static entry * curr_entry; +static commodity * entry_comm; +static commodity * curr_comm; +static amount * curr_value; +static std::string curr_quant; +static XML_Parser current_parser; +static accounts_map accounts_by_id; static enum { NO_ACTION, @@ -98,21 +97,21 @@ static void endElement(void *userData, const char *name) if (std::strcmp(name, "gnc:account") == 0) { assert(curr_account); if (! curr_account->parent) - main_ledger.accounts.insert(accounts_entry(curr_account->name, - curr_account)); - accounts_by_id.insert(accounts_entry(curr_account_id, curr_account)); + main_ledger->accounts.insert(accounts_map_pair(curr_account->name, + curr_account)); + accounts_by_id.insert(accounts_map_pair(curr_account_id, curr_account)); curr_account = NULL; } else if (std::strcmp(name, "gnc:commodity") == 0) { assert(curr_comm); - main_ledger.commodities.insert(commodities_entry(curr_comm->symbol, - curr_comm)); + main_ledger->commodities.insert(commodities_map_pair(curr_comm->symbol, + curr_comm)); curr_comm = NULL; } else if (std::strcmp(name, "gnc:transaction") == 0) { assert(curr_entry); assert(curr_entry->validate()); - main_ledger.entries.push_back(curr_entry); + main_ledger->entries.push_back(curr_entry); curr_entry = NULL; } action = NO_ACTION; @@ -130,11 +129,11 @@ static void dataHandler(void *userData, const char *s, int len) break; case ACCOUNT_PARENT: { - accounts_iterator i = accounts_by_id.find(std::string(s, len)); + accounts_map_iterator i = accounts_by_id.find(std::string(s, len)); assert(i != accounts_by_id.end()); curr_account->parent = (*i).second; - (*i).second->children.insert(accounts_entry(curr_account->name, - curr_account)); + (*i).second->children.insert(accounts_map_pair(curr_account->name, + curr_account)); break; } @@ -142,9 +141,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 = main_ledger.commodities[std::string(s, len)]; + curr_account->comm = main_ledger->commodities[std::string(s, len)]; else if (curr_entry) - entry_comm = main_ledger.commodities[std::string(s, len)]; + entry_comm = main_ledger->commodities[std::string(s, len)]; break; case COMM_NAME: @@ -186,7 +185,7 @@ static void dataHandler(void *userData, const char *s, int len) break; case XACT_ACCOUNT: { - accounts_iterator i = accounts_by_id.find(std::string(s, len)); + accounts_map_iterator i = accounts_by_id.find(std::string(s, len)); if (i == accounts_by_id.end()) { std::cerr << "Could not find account " << std::string(s, len) << std::endl; @@ -208,7 +207,7 @@ static void dataHandler(void *userData, const char *s, int len) if (curr_value) delete curr_value; - if (do_compute) + if (main_ledger->compute_balances) xact->acct->balance.credit(xact->cost); break; } @@ -228,12 +227,16 @@ static void dataHandler(void *userData, const char *s, int len) } } -bool parse_gnucash(std::istream& in, bool compute_balances) +state * parse_gnucash(std::istream& in, bool compute_balances) { char buf[BUFSIZ]; + state * ledger = new state; + + main_ledger = ledger; + ledger->compute_balances = compute_balances; + action = NO_ACTION; - do_compute = compute_balances; curr_account = NULL; curr_entry = NULL; curr_value = NULL; @@ -243,7 +246,7 @@ bool parse_gnucash(std::istream& in, bool compute_balances) // GnuCash uses the USD commodity without defining it, which really // means to use $. commodity * usd = new commodity("$", true, false, true, false, 2); - main_ledger.commodities.insert(commodities_entry("USD", usd)); + main_ledger->commodities.insert(commodities_map_pair("USD", usd)); XML_Parser parser = XML_ParserCreate(NULL); current_parser = parser; @@ -258,7 +261,7 @@ bool parse_gnucash(std::istream& in, bool compute_balances) std::cerr << XML_ErrorString(XML_GetErrorCode(parser)) << " at line " << XML_GetCurrentLineNumber(parser) << std::endl; - return false; + return NULL; } } XML_ParserFree(parser); @@ -267,7 +270,7 @@ bool parse_gnucash(std::istream& in, bool compute_balances) curr_account_id.clear(); curr_quant.clear(); - return true; + return ledger; } } // namespace ledger @@ -4,10 +4,8 @@ namespace ledger { -bool use_warnings = false; -state main_ledger; - -std::list<mask> regexps; +bool use_warnings = false; +state * main_ledger; const std::string transaction::acct_as_str() const { @@ -85,7 +83,7 @@ void entry::print(std::ostream& out, bool shortcut) const // jww (2003-10-03): If we are shortcutting, don't print the // "per-unit price" of a commodity, if it is not necessary. - (*x)->print(out, shortcut && x != xacts.begin()); + (*x)->print(out, shortcut && x == xacts.begin()); } out << std::endl; @@ -98,8 +96,7 @@ 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 && - (! (*x)->is_virtual || main_ledger.compute_virtual)) + if ((*x)->cost && (*x)->must_balance) balance.credit((*x)->cost->value()); if (show_unaccounted && ! balance.is_zero()) { @@ -132,16 +129,12 @@ bool entry::matches(const std::list<mask>& 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(); @@ -179,36 +172,14 @@ void totals::print(std::ostream& out, int width) const // Print out the entire ledger that was read in, sorted by date. // This can be used to "wash" ugly ledger files. -void print_ledger(int argc, char ** argv, regexps_t& regexps, - std::ostream& out) +void state::print(std::ostream& out, regexps_map& regexps, + bool shortcut) const { - bool use_shortcuts = true; - - optind = 1; - - int c; - while (-1 != (c = getopt(argc, argv, "n"))) { - switch (char(c)) { - case 'n': use_shortcuts = false; break; - } - } - - // 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); - - // Sort the list of entries by date, then print them in order. - - std::sort(main_ledger.entries.begin(), main_ledger.entries.end(), - cmp_entry_date()); - - for (entries_iterator i = main_ledger.entries.begin(); - i != main_ledger.entries.end(); + for (entries_list_const_iterator i = entries.begin(); + i != entries.end(); i++) if ((*i)->matches(regexps)) - (*i)->print(out, use_shortcuts); + (*i)->print(out, shortcut); } mask::mask(const std::string& pat) : exclude(false) @@ -236,12 +207,7 @@ mask::mask(const std::string& pat) : exclude(false) << std::endl; } -void record_regexp(const std::string& pattern, regexps_t& regexps) -{ - regexps.push_back(mask(pattern)); -} - -void read_regexps(const std::string& path, regexps_t& regexps) +void read_regexps(const std::string& path, regexps_map& regexps) { if (access(path.c_str(), R_OK) != -1) { std::ifstream file(path.c_str()); @@ -250,12 +216,12 @@ void read_regexps(const std::string& path, regexps_t& regexps) char buf[80]; file.getline(buf, 79); if (*buf && ! std::isspace(*buf)) - record_regexp(buf, regexps); + regexps.push_back(mask(buf)); } } } -bool matches(const regexps_t& regexps, const std::string& str, +bool matches(const regexps_map& regexps, const std::string& str, bool * by_exclusion) { assert(! regexps.empty()); @@ -285,28 +251,24 @@ bool matches(const regexps_t& regexps, const std::string& str, return match; } -#ifdef DO_CLEANUP - state::~state() { - for (commodities_iterator i = commodities.begin(); + for (commodities_map_iterator i = commodities.begin(); i != commodities.end(); i++) delete (*i).second; - for (accounts_iterator i = accounts.begin(); + for (accounts_map_iterator i = accounts.begin(); i != accounts.end(); i++) delete (*i).second; - for (entries_iterator i = entries.begin(); + for (entries_list_iterator i = entries.begin(); i != entries.end(); i++) delete *i; } -#endif // DO_CLEANUP - void state::record_price(const std::string& setting) { char buf[128]; @@ -326,7 +288,7 @@ void state::record_price(const std::string& setting) account * state::find_account(const std::string& name, bool create) { - accounts_iterator i = accounts_cache.find(name); + accounts_map_iterator i = accounts_cache.find(name); if (i != accounts_cache.end()) return (*i).second; @@ -338,26 +300,26 @@ account * state::find_account(const std::string& name, bool create) tok; tok = std::strtok(NULL, ":")) { if (! current) { - accounts_iterator i = accounts.find(tok); + accounts_map_iterator i = accounts.find(tok); if (i == accounts.end()) { if (! create) { delete[] buf; return NULL; } current = new account(tok); - accounts.insert(accounts_entry(tok, current)); + accounts.insert(accounts_map_pair(tok, current)); } else { current = (*i).second; } } else { - accounts_iterator i = current->children.find(tok); + accounts_map_iterator i = current->children.find(tok); if (i == current->children.end()) { if (! create) { delete[] buf; return NULL; } current = new account(tok, current); - current->parent->children.insert(accounts_entry(tok, current)); + current->parent->children.insert(accounts_map_pair(tok, current)); } else { current = (*i).second; } @@ -367,7 +329,7 @@ account * state::find_account(const std::string& name, bool create) delete[] buf; if (current) - accounts_cache.insert(accounts_entry(name, current)); + accounts_cache.insert(accounts_map_pair(name, current)); return current; } @@ -1,5 +1,5 @@ #ifndef _LEDGER_H -#define _LEDGER_H "$Revision: 1.18 $" +#define _LEDGER_H "$Revision: 1.19 $" ////////////////////////////////////////////////////////////////////// // @@ -44,9 +44,9 @@ struct commodity bool thou = true, bool euro = false, int prec = 2); }; -typedef std::map<const std::string, commodity *> commodities_t; -typedef commodities_t::iterator commodities_iterator; -typedef std::pair<const std::string, commodity *> commodities_entry; +typedef std::map<const std::string, commodity *> commodities_map; +typedef commodities_map::iterator commodities_map_iterator; +typedef std::pair<const std::string, commodity *> commodities_map_pair; class amount @@ -90,11 +90,11 @@ struct mask mask(const std::string& pattern); }; -typedef std::list<mask> regexps_t; +typedef std::list<mask> regexps_map; -void record_regexp(const std::string& pattern, regexps_t& regexps); -void read_regexps(const std::string& path, regexps_t& regexps); -bool matches(const regexps_t& regexps, const std::string& str, +void record_regexp(const std::string& pattern, regexps_map& regexps); +void read_regexps(const std::string& path, regexps_map& regexps); +bool matches(const regexps_map& regexps, const std::string& str, bool * by_exclusion = NULL); @@ -114,12 +114,10 @@ struct transaction : acct(_acct), cost(_cost), is_virtual(false), must_balance(true), specified(false) {} -#ifdef DO_CLEANUP ~transaction() { if (cost) delete cost; } -#endif const std::string acct_as_str() const; @@ -140,7 +138,6 @@ struct entry 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. @@ -152,7 +149,6 @@ struct entry delete *i; } } -#endif bool matches(const std::list<mask>& regexps) const; bool validate(bool show_unaccounted = false) const; @@ -166,22 +162,21 @@ struct cmp_entry_date { } }; -typedef std::vector<entry *> entries_t; -typedef entries_t::iterator entries_iterator; +typedef std::vector<entry *> entries_list; +typedef entries_list::iterator entries_list_iterator; +typedef entries_list::const_iterator entries_list_const_iterator; struct totals { - typedef std::map<const std::string, amount *> map; - typedef map::iterator iterator; - typedef map::const_iterator const_iterator; + 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 amounts; -#ifdef DO_CLEANUP ~totals(); -#endif void credit(const amount * val) { std::pair<iterator, bool> result = @@ -202,9 +197,9 @@ struct totals }; -typedef std::map<const std::string, account *> accounts_t; -typedef accounts_t::iterator accounts_iterator; -typedef std::pair<const std::string, account *> accounts_entry; +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 { @@ -216,7 +211,7 @@ struct account #endif totals balance; // optional, parse-time computed balance int checked; // 'balance' uses this for speed's sake - accounts_t children; + accounts_map children; mutable std::string full_name; @@ -238,11 +233,12 @@ struct account struct state { - commodities_t commodities; - accounts_t accounts; - accounts_t accounts_cache; // maps full names to accounts - entries_t entries; + commodities_map commodities; + accounts_map accounts; + accounts_map accounts_cache; // maps full names to accounts + entries_list entries; totals prices; + int current_year; typedef std::map<std::list<mask> *, std::list<transaction *> *> virtual_map; @@ -252,30 +248,31 @@ struct state typedef virtual_map::const_iterator virtual_map_iterator; - std::string mapping_file; + bool compute_balances; virtual_map virtual_mapping; - bool compute_virtual; - - state() : mapping_file(".mapping"), compute_virtual(true) {} -#ifdef DO_CLEANUP ~state(); -#endif void record_price(const std::string& setting); + template<typename Compare> + void sort(Compare comp) { + std::sort(entries.begin(), entries.end(), comp); + } + void print(std::ostream& out, regexps_map& regexps, bool shortcut) const; + account * find_account(const std::string& name, bool create = true); }; -extern state main_ledger; -extern bool use_warnings; +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)); + std::pair<commodities_map_iterator, bool> result = + main_ledger->commodities.insert(commodities_map_pair(sym, this)); assert(result.second); } @@ -1,27 +1,31 @@ #include "ledger.h" +#define LEDGER_VERSION "1.1" + #include <fstream> namespace ledger { - extern bool parse_ledger(std::istream& in, bool compute_balances); - extern void parse_virtual_mappings(const std::string& path); - extern bool parse_date(const std::string& date_str, std::time_t * result, - const int year = -1); + extern state * parse_ledger(std::istream& in, regexps_map& regexps, + bool compute_balances); #ifdef READ_GNUCASH - extern bool parse_gnucash(std::istream& in, bool compute_balances); + extern state * parse_gnucash(std::istream& in, bool compute_balances); #endif - extern void report_balances(int argc, char ** argv, regexps_t& regexps, - std::ostream& out); - extern void print_register(int argc, char ** argv, regexps_t& regexps, - std::ostream& out); - extern void print_ledger(int argc, char ** argv, regexps_t& regexps, - std::ostream& out); - extern void equity_ledger(int argc, char ** argv, regexps_t& regexps, - std::ostream& out); + extern bool parse_date(const char * date_str, std::time_t * result, + const int year = -1); + + 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; @@ -76,20 +80,22 @@ static void show_help(std::ostream& out) int main(int argc, char * argv[]) { std::istream * file = NULL; - regexps_t regexps; - have_beginning = false; - have_ending = false; - show_cleared = false; + regexps_map regexps; - const char * p = std::getenv("MAPPINGS"); - if (p) - main_ledger.mapping_file = p; + 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:Pv"))) { + while (-1 != (c = getopt(argc, argv, "+b:e:d:cChRV:wf:i:p:PvsSnF"))) { switch (char(c)) { case 'b': case 'e': { @@ -164,12 +170,16 @@ int main(int argc, char * argv[]) have_ending = true; break; - case 'C': show_cleared = true; break; case 'h': show_help(std::cout); break; - case 'R': main_ledger.compute_virtual = false; break; - case 'V': main_ledger.mapping_file = optarg; break; - case 'w': use_warnings = true; 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 'n': show_subtotals = false; break; + case 'F': full_names = true; break; // -i path-to-file-of-regexps case 'i': @@ -186,10 +196,10 @@ int main(int argc, char * argv[]) char buf[80]; pricedb.getline(buf, 79); if (*buf && ! std::isspace(*buf)) - main_ledger.record_price(buf); + main_ledger->record_price(buf); } } else { - main_ledger.record_price(optarg); + main_ledger->record_price(optarg); } break; @@ -199,7 +209,7 @@ int main(int argc, char * argv[]) case 'v': std::cout - << "Ledger Accouting Tool 1.0" << std::endl + << "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" @@ -242,7 +252,8 @@ int main(int argc, char * argv[]) file = new std::ifstream(p); if (! file || ! *file) { - std::cerr << "Please specify ledger file using -f option or LEDGER environment variable." + std::cerr << ("Please specify ledger file using -f option " + "or LEDGER environment variable.") << std::endl; return 1; } @@ -250,13 +261,17 @@ int main(int argc, char * argv[]) // Read the command word - const std::string command = argv[optind]; + const std::string command = argv[optind++]; - // Parse any virtual mappings being used - - if (main_ledger.compute_virtual && - access(main_ledger.mapping_file.c_str(), R_OK) >= 0) - parse_virtual_mappings(main_ledger.mapping_file); + int optind_begin = optind; + if (command == "register") { + if (optind == argc) { + std::cerr << ("Error: Must specify an account name " + "after the 'register' command.") << std::endl; + return 1; + } + optind++; + } // Parse the ledger @@ -266,23 +281,42 @@ int main(int argc, char * argv[]) file->seekg(0); if (std::strncmp(buf, "<?xml version=\"1.0\"?>", 21) == 0) - parse_gnucash(*file, command == "equity"); + main_ledger = parse_gnucash(*file, command == "equity"); else #endif - parse_ledger(*file, command == "equity"); + main_ledger = parse_ledger(*file, regexps, command == "equity"); delete file; + if (! main_ledger) + std::exit(1); + + // Compile the list of specified regular expressions, which can be + // specified after the command, or using the '-i FILE' option + + for (; optind < argc; optind++) + regexps.push_back(mask(argv[optind])); + // Process the command - if (command == "balance") - report_balances(argc - optind, &argv[optind], regexps, std::cout); - else if (command == "register") - print_register(argc - optind, &argv[optind], regexps, std::cout); - else if (command == "print") - print_ledger(argc - optind, &argv[optind], regexps, std::cout); - else if (command == "equity") - equity_ledger(argc - optind, &argv[optind], regexps, std::cout); + if (command == "balance") { + report_balances(std::cout, regexps); + } + else if (command == "register") { + print_register(argv[optind_begin], 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); + } + +#if 0 + // Deleting the main ledger just isn't necessary at this point. + delete main_ledger; +#endif } // main.cc ends here. @@ -7,28 +7,34 @@ namespace ledger { +static inline char * skip_ws(char * ptr) +{ + while (std::isspace(*ptr)) + ptr++; + return ptr; +} + static inline char * next_element(char * buf, bool variable = false) { char * p; - // Convert any tabs to spaces, for simplicity's sake - for (p = buf; *p; p++) - if (*p == '\t') - *p = ' '; + if (variable) { + // Convert any tabs to spaces, for simplicity's sake + for (p = buf; *p; p++) + if (*p == '\t') + *p = ' '; - if (variable) p = std::strstr(buf, " "); - else + } else { p = std::strchr(buf, ' '); + } if (! p) return NULL; *p++ = '\0'; - while (std::isspace(*p)) - p++; - return p; + return skip_ws(p); } static const char *formats[] = { @@ -44,17 +50,17 @@ static const char *formats[] = { NULL }; -bool parse_date(const std::string& date_str, std::time_t * result, +bool parse_date(const char * date_str, std::time_t * result, const int year = -1) { struct std::tm when; - std::time_t now = std::time(NULL); + 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(date_str.c_str(), *f, &when)) { + if (strptime(date_str, *f, &when)) { when.tm_hour = 0; when.tm_min = 0; when.tm_sec = 0; @@ -78,14 +84,130 @@ bool parse_date(const std::string& date_str, std::time_t * result, return false; } -static int linenum = 0; +#define MAX_LINE 1024 + +static int linenum; + +transaction * parse_transaction(std::istream& in, state * ledger) +{ + transaction * xact = new transaction(); + + static char line[MAX_LINE + 1]; + in.getline(line, MAX_LINE); + linenum++; + + char * p = line; + p = skip_ws(p); + + // The call to `next_element' will skip past the account name, + // and return a pointer to the beginning of the amount. Once + // we know where the amount is, we can strip off any + // transaction note, and parse it. + + char * cost_str = next_element(p, true); + char * note_str; + + // 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 || *cost_str == ';') { + if (cost_str && *cost_str) { + while (*cost_str == ';' || std::isspace(*cost_str)) + cost_str++; + xact->note = cost_str; + } + + xact->cost = NULL; + } + else { + note_str = std::strchr(cost_str, ';'); + if (note_str) { + *note_str++ = '\0'; + xact->note = skip_ws(note_str); + } + + for (char * t = cost_str + (std::strlen(cost_str) - 1); + std::isspace(*t); + t--) + *t = '\0'; + + xact->cost = create_amount(cost_str); + } + + if (*p == '[' || *p == '(') { + xact->is_virtual = true; + xact->specified = true; + xact->must_balance = *p == '['; + p++; + + char * e = p + (std::strlen(p) - 1); + assert(*e == ')' || *e == ']'); + *e = '\0'; + } + + xact->acct = ledger->find_account(p); -static void finalize_entry(entry * curr, bool compute_balances) + if (ledger->compute_balances && xact->cost) + xact->acct->balance.credit(xact->cost); + + return xact; +} + +entry * parse_entry(std::istream& in, state * ledger) { - assert(curr); - assert(! curr->xacts.empty()); + entry * curr = new entry; + + static char line[MAX_LINE + 1]; + in.getline(line, MAX_LINE); + linenum++; + + // Parse the date + + char * next = next_element(line); + if (! parse_date(line, &curr->date, ledger->current_year)) { + std::cerr << "Error, line " << linenum + << ": Failed to parse date: " << line << std::endl; + return NULL; + } - // Scan through and compute the total balance for the entry. + // Parse the optional cleared flag: * + + if (*next == '*') { + curr->cleared = true; + next = skip_ws(++next); + } + + // Parse the optional code: (TEXT) + + if (*next == '(') { + if (char * p = std::strchr(next++, ')')) { + *p++ = '\0'; + curr->code = next; + next = skip_ws(p); + } + } + + // Parse the description text + + curr->desc = next; + + // Parse all of the transactions associated with this entry + + while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) + if (transaction * xact = parse_transaction(in, ledger)) + curr->xacts.push_back(xact); + + // If there were no transactions, throw away the entry + + if (curr->xacts.empty()) { + delete curr; + return NULL; + } + + // Scan through and compute the total balance for the entry. This + // is used for auto-calculating the value of entries with no cost, + // and the per-unit price of unpriced commodities. totals balance; @@ -139,10 +261,10 @@ static void finalize_entry(entry * curr, bool compute_balances) if (! empty_allowed || balance.amounts.empty() || balance.amounts.size() != 1) { - std::cerr << "Error, line " << (linenum - 1) + std::cerr << "Error, line " << linenum << ": Transaction entry is lacking an amount." << std::endl; - return; + return NULL; } empty_allowed = false; @@ -154,7 +276,7 @@ static void finalize_entry(entry * curr, bool compute_balances) (*x)->cost = (*i).second->value(); (*x)->cost->negate(); - if (compute_balances) + if (ledger->compute_balances) (*x)->acct->balance.credit((*x)->cost); } @@ -162,47 +284,56 @@ static void finalize_entry(entry * curr, bool compute_balances) // transactions and create new virtual transactions for all that // apply. - if (main_ledger.compute_virtual) { - for (state::virtual_map_iterator m = main_ledger.virtual_mapping.begin(); - m != main_ledger.virtual_mapping.end(); - m++) { - std::list<transaction *> xacts; - - for (std::list<transaction *>::iterator x = curr->xacts.begin(); - x != curr->xacts.end(); - x++) { - if ((*x)->is_virtual || - ! ledger::matches(*((*m).first), (*x)->acct->as_str())) - continue; - - for (std::list<transaction *>::iterator i = (*m).second->begin(); - i != (*m).second->end(); - i++) { - transaction * t; + for (state::virtual_map_iterator m = ledger->virtual_mapping.begin(); + m != ledger->virtual_mapping.end(); + m++) { + std::list<transaction *> xacts; - assert((*i)->is_virtual); - assert((*i)->cost); + for (std::list<transaction *>::iterator x = curr->xacts.begin(); + x != curr->xacts.end(); + x++) { + if ((*x)->is_virtual || + ! ledger::matches(*((*m).first), (*x)->acct->as_str())) + continue; - if ((*i)->cost->comm()) { - t = new transaction((*i)->acct, (*i)->cost); - } else { - amount * temp = (*x)->cost->value(); - t = new transaction((*i)->acct, temp->value((*i)->cost)); - delete temp; - } + for (std::list<transaction *>::iterator i = (*m).second->begin(); + i != (*m).second->end(); + i++) { + transaction * t; + + if ((*i)->cost->comm()) { + t = new transaction((*i)->acct, (*i)->cost); + } else { + amount * temp = (*x)->cost->value(); + t = new transaction((*i)->acct, temp->value((*i)->cost)); + delete temp; + } + + t->is_virtual = (*i)->is_virtual; + t->must_balance = (*i)->must_balance; - t->is_virtual = true; - t->must_balance = (*i)->must_balance; + // If there is already a virtual transaction for the + // account under consideration, and it's `must_balance' + // flag matches, then simply add this amount to that + // transaction. - // If there is already a virtual transaction for the - // account under consideration, and it's `must_balance' - // flag matches, then simply add this amount to that - // transaction. + bool added = false; - bool added = false; + for (std::list<transaction *>::iterator y = xacts.begin(); + y != xacts.end(); + y++) { + if ((*y)->is_virtual && (*y)->acct == t->acct && + (*y)->must_balance == t->must_balance) { + (*y)->cost->credit(t->cost); + delete t; + added = true; + break; + } + } - for (std::list<transaction *>::iterator y = xacts.begin(); - y != xacts.end(); + if (! added) + for (std::list<transaction *>::iterator y = curr->xacts.begin(); + y != curr->xacts.end(); y++) { if ((*y)->is_virtual && (*y)->acct == t->acct && (*y)->must_balance == t->must_balance) { @@ -213,42 +344,28 @@ static void finalize_entry(entry * curr, bool compute_balances) } } - if (! added) - for (std::list<transaction *>::iterator y = curr->xacts.begin(); - y != curr->xacts.end(); - y++) { - if ((*y)->is_virtual && (*y)->acct == t->acct && - (*y)->must_balance == t->must_balance) { - (*y)->cost->credit(t->cost); - delete t; - added = true; - break; - } - } - - if (! added) - xacts.push_back(t); - } + if (! added) + xacts.push_back(t); } + } - // Add to the current entry any virtual transactions which were - // created. We have to do this afterward, otherwise the - // iteration above is screwed up if we try adding new - // transactions during the traversal. + // Add to the current entry any virtual transactions which were + // created. We have to do this afterward, otherwise the + // iteration above is screwed up if we try adding new + // transactions during the traversal. - for (std::list<transaction *>::iterator x = xacts.begin(); - x != xacts.end(); - x++) { - curr->xacts.push_back(*x); + for (std::list<transaction *>::iterator x = xacts.begin(); + x != xacts.end(); + x++) { + curr->xacts.push_back(*x); - if (compute_balances) - (*x)->acct->balance.credit((*x)->cost); - } + if (ledger->compute_balances) + (*x)->acct->balance.credit((*x)->cost); } } // Compute the balances again, just to make sure it all comes out - // right (i.e., to zero for every commodity). + // right (i.e., zero for every commodity). if (! curr->validate()) { std::cerr << "Error, line " << (linenum - 1) @@ -256,230 +373,113 @@ static void finalize_entry(entry * curr, bool compute_balances) << std::endl; curr->print(std::cerr); curr->validate(true); - return; + delete curr; + return NULL; } - - // If it's OK, add it to the general ledger's list of entries. - - main_ledger.entries.push_back(curr); + return curr; } -////////////////////////////////////////////////////////////////////// -// -// Ledger parser -// - -bool parse_ledger(std::istream& in, bool compute_balances) +void parse_automated_transactions(std::istream& in, state * ledger) { - char line[1024]; - int current_year = -1; - entry * curr = NULL; + static char line[MAX_LINE + 1]; - while (! in.eof()) { - in.getline(line, 1023); - linenum++; - - if (line[0] == '\n') { - continue; - } - else if (std::isdigit(line[0])) { - if (curr && ! curr->xacts.empty()) - finalize_entry(curr, compute_balances); - curr = new entry; - - char * next = next_element(line); - if (! parse_date(line, &curr->date, current_year)) { - std::cerr << "Error, line " << linenum - << ": Failed to parse date: " << line << std::endl; - continue; - } - - if (*next == '*') { - curr->cleared = true; - - next++; - while (std::isspace(*next)) - next++; - } - - if (*next == '(') { - char * p = std::strchr(next, ')'); - if (p) { - *p++ = '\0'; - curr->code = next; - next = p; - - next++; - while (std::isspace(*next)) - next++; - } - } - - curr->desc = next; - } - else if (curr && std::isspace(line[0])) { - transaction * xact = new transaction(); - - char * p = line; - while (std::isspace(*p)) - p++; - - // The call to `next_element' will skip past the account name, - // and return a pointer to the beginning of the amount. Once - // we know where the amount is, we can strip off any - // transaction note, and parse it. - - char * cost_str = next_element(p, true); - char * note_str; - - // 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 || *cost_str == ';') { - if (cost_str && *cost_str) { - while (*cost_str == ';' || std::isspace(*cost_str)) - cost_str++; - xact->note = cost_str; - } + std::list<mask> * masks = NULL; - xact->cost = NULL; - } - else { - note_str = std::strchr(cost_str, ';'); - if (note_str) { - *note_str++ = '\0'; - while (std::isspace(*note_str)) - note_str++; - xact->note = note_str; - } + while (! in.eof() && in.peek() == '=') { + in.getline(line, MAX_LINE); + linenum++; - for (char * t = cost_str + (std::strlen(cost_str) - 1); - std::isspace(*t); - t--) - *t = '\0'; + char * p = line + 1; + p = skip_ws(p); - xact->cost = create_amount(cost_str); - } + if (! masks) + masks = new std::list<mask>; + masks->push_back(mask(p)); + } - if (*p == '[' || *p == '(') { - xact->is_virtual = true; - xact->specified = true; - xact->must_balance = *p == '['; - p++; + std::list<transaction *> * xacts = NULL; - char * e = p + (std::strlen(p) - 1); - assert(*e == ')' || *e == ']'); - *e = '\0'; - } + while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) { + if (transaction * xact = parse_transaction(in, ledger)) { + if (! xacts) + xacts = new std::list<transaction *>; - if (xact->is_virtual && ! main_ledger.compute_virtual) { - delete xact; + if (! xact->cost) { + std::cerr << "Error, line " << (linenum - 1) + << ": All automated transactions must have a value." + << std::endl; } else { - xact->acct = main_ledger.find_account(p); - if (compute_balances && xact->cost) - xact->acct->balance.credit(xact->cost); - - curr->xacts.push_back(xact); + xacts->push_back(xact); } } - else if (line[0] == 'Y') { - current_year = std::atoi(line + 2); - } } - if (curr && ! curr->xacts.empty()) - finalize_entry(curr, compute_balances); - - return true; + if (masks && xacts) + ledger->virtual_mapping.insert(state::virtual_map_pair(masks, xacts)); + else if (masks) + delete masks; + else if (xacts) + delete xacts; } -void parse_virtual_mappings(const std::string& path) -{ - main_ledger.mapping_file = path; - - std::ifstream maps(main_ledger.mapping_file.c_str()); - - char line[1024]; - int linenum = 0; - - std::list<mask> * masks = NULL; - std::list<transaction *> * xacts = NULL; - - while (! maps.eof()) { - maps.getline(line, 1023); - linenum++; - - // The format of each entry is: - // - // REGEXP1 - // REGEXP2... - // ACCOUNT AMOUNT - // ACCOUNT AMOUNT... - // - // If AMOUNT has a commodity, that exact amount is always - // transacted whenever a REGEXP is matched. If it has no - // commodity, then it is taken as the multiplier, the result of - // which is transacted instead. - // - // If one of REGEXP is the word "{BEGIN}", then those - // transactions will be entered before parsing has begin. - - if (std::isspace(line[0])) { - if (! xacts) - xacts = new std::list<transaction *>; - - char * p = line; - while (std::isspace(*p)) - p++; - - char * cost_str = next_element(p, true); - account * acct = main_ledger.find_account(p); - transaction * xact = new transaction(acct, create_amount(cost_str)); - - xact->is_virtual = true; - xact->must_balance = false; - - assert(masks); - assert(! masks->empty()); - if (masks->size() == 1 && - masks->front().pattern == "{BEGIN}") { - entry * opening = new entry; +////////////////////////////////////////////////////////////////////// +// +// Ledger parser +// - opening->date = std::time(NULL); - opening->cleared = true; - opening->desc = "Opening Balance"; +state * parse_ledger(std::istream& in, regexps_map& regexps, + bool compute_balances) +{ + static char line[MAX_LINE + 1]; + char c; - opening->xacts.push_back(xact); - main_ledger.entries.push_back(opening); - } else { - xacts->push_back(xact); - } - } - else if (line[0] != '\0') { - if (xacts) { - std::pair<state::virtual_map_iterator, bool> result = - main_ledger.virtual_mapping.insert - (state::virtual_map_pair(masks, xacts)); - assert(result.second); - - masks = NULL; - xacts = NULL; - } + state * ledger = new state; + main_ledger = ledger; - if (! masks) - masks = new std::list<mask>; + ledger->compute_balances = compute_balances; - masks->push_back(mask(line)); + linenum = 0; + while (! in.eof()) { + switch (in.peek()) { + case -1: // end of file + return ledger; + + case '\n': + linenum++; + case '\r': // skip blank lines + in.get(c); + break; + + case 'Y': // set the current year + in >> c; + in >> ledger->current_year; + break; + + case ';': // a comment line + in.getline(line, MAX_LINE); + linenum++; + break; + + case '-': + case '+': // permanent regexps + in.getline(line, MAX_LINE); + linenum++; + + // Add the regexp to whatever masks currently exist + regexps.push_back(mask(line)); + break; + + case '=': // automated transactions + parse_automated_transactions(in, ledger); + break; + + default: + if (entry * ent = parse_entry(in, ledger)) + ledger->entries.push_back(ent); + break; } } - - if (xacts) { - std::pair<state::virtual_map_iterator, bool> result = - main_ledger.virtual_mapping.insert - (state::virtual_map_pair(masks, xacts)); - assert(result.second); - } + return ledger; } } // namespace ledger diff --git a/register.cc b/register.cc index 41f1fc48..38579ce0 100644 --- a/register.cc +++ b/register.cc @@ -28,39 +28,23 @@ static std::string truncated(const std::string& str, int width) // Register printing code // -void print_register(int argc, char ** argv, regexps_t& regexps, - std::ostream& out) +void print_register(const std::string& acct_name, std::ostream& out, + regexps_map& regexps) { - optind = 1; - - // Find out which account this register is to be printed for - - if (optind == argc) { - std::cerr << ("Error: Must specify an account name " - "after the 'register' command.") << std::endl; - return; - } - - account * acct = main_ledger.find_account(argv[optind++], false); + account * acct = main_ledger->find_account(acct_name, false); if (! acct) { - std::cerr << "Error: Unknown account name: " << argv[optind - 1] + std::cerr << "Error: Unknown account name: " << acct_name << 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(); + for (entries_list_iterator i = main_ledger->entries.begin(); + i != main_ledger->entries.end(); i++) { if (! (*i)->matches(regexps)) continue; |