From 3ef7bfdb32d770812e9805474ea9956568385efe Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 4 Oct 2003 01:54:30 +0000 Subject: Added support for virtual accounts. --- Makefile | 11 +- amount.cc | 13 +- ledger.cc | 83 +++++++++---- ledger.h | 66 +++++----- ledger.texi | 18 ++- main.cc | 102 ++++----------- parse.cc | 407 +++++++++++++++++++++++++++++++++++++++++------------------- register.cc | 4 +- 8 files changed, 420 insertions(+), 284 deletions(-) diff --git a/Makefile b/Makefile index a64800ef..ac592732 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,6 @@ define GNUCASH true endef -define HUQUQ -true -endef CODE = amount.cc \ ledger.cc \ @@ -15,15 +12,11 @@ CODE = amount.cc \ OBJS = $(patsubst %.cc,%.o,$(CODE)) -CFLAGS = -Wall -ansi -pedantic -DFLAGS = -g -O2 # -O3 -fomit-frame-pointer -mcpu=pentium +CFLAGS = -Wall -ansi -pedantic -DDEBUG=1 +DFLAGS = -g # -O2 # -O3 -fomit-frame-pointer -mcpu=pentium INCS = LIBS = -lgmpxx -lgmp -lpcre -ifdef HUQUQ -CFLAGS := $(CFLAGS) -DHUQUQULLAH=1 -endif - ifdef GNUCASH CODE := $(CODE) gnucash.cc CFLAGS := $(CFLAGS) -DREAD_GNUCASH=1 diff --git a/amount.cc b/amount.cc index 8d1752a0..8d67bd3b 100644 --- a/amount.cc +++ b/amount.cc @@ -425,12 +425,17 @@ const std::string gmp_amount::as_str(bool full_prec) const { std::ostringstream s; - assert(quantity_comm); - s << amount_to_str(quantity_comm, quantity, full_prec); + if (quantity_comm) + s << amount_to_str(quantity_comm, quantity, full_prec); + else + s << quantity; if (priced) { - assert(price_comm); - s << " @ " << amount_to_str(price_comm, price, full_prec); + s << " @ "; + if (price_comm) + s << amount_to_str(price_comm, price, full_prec); + else + s << price; } return s.str(); } diff --git a/ledger.cc b/ledger.cc index 4f750202..0d591532 100644 --- a/ledger.cc +++ b/ledger.cc @@ -4,12 +4,56 @@ namespace ledger { -bool use_warnings = false; - +bool use_warnings = false; state main_ledger; std::list regexps; +const std::string transaction::acct_as_str() const +{ + char * begin = NULL; + char * end = NULL; + + if (is_virtual) { + if (must_balance) { + begin = "["; + end = "]"; + } else { + begin = "("; + end = ")"; + } + } + + if (begin) + return std::string(begin) + acct->as_str() + end; + else + return acct->as_str(); +} + +void transaction::print(std::ostream& out, bool display_quantity, + bool display_price) const +{ + out.width(30); + out << std::left << acct_as_str(); + + if (cost && display_quantity) { + out << " "; + out.width(10); + + std::string value = cost->as_str(true); + if (! display_price) { + int index = value.find('@'); + value = std::string(value, index - 1); + } + out << std::right << value; + } + + if (! note.empty()) + out << " ; " << note; + + out << std::endl; +} + void entry::print(std::ostream& out, bool shortcut) const { char buf[32]; @@ -33,28 +77,17 @@ void entry::print(std::ostream& out, bool shortcut) const for (std::list::const_iterator x = xacts.begin(); x != xacts.end(); x++) { -#ifdef HUQUQULLAH - if ((*x)->exempt_or_necessary || - (! shortcut && (*x)->acct->exempt_or_necessary)) - out << " !"; - else -#endif - out << " "; - - out.width(30); - out << std::left << (*x)->acct->as_str(); + if ((*x)->is_virtual && ! (*x)->specified) + continue; - if ((*x)->cost && (! shortcut || x == xacts.begin())) { - out << " "; - out.width(10); - out << std::right << (*x)->cost->as_str(true); - } + out << " "; - if (! (*x)->note.empty()) - out << " ; " << (*x)->note; + // jww (2003-10-03): If we are shortcutting, don't print the + // "per-unit price" of a commodity, if it is not necessary. - out << std::endl; + (*x)->print(out, shortcut && x != xacts.begin()); } + out << std::endl; } @@ -65,7 +98,8 @@ bool entry::validate(bool show_unaccounted) const for (std::list::const_iterator x = xacts.begin(); x != xacts.end(); x++) - if ((*x)->cost) + if ((*x)->cost && (*x)->must_balance && + (! (*x)->is_virtual || main_ledger.compute_virtual)) balance.credit((*x)->cost->value()); if (show_unaccounted && ! balance.is_zero()) { @@ -332,14 +366,9 @@ account * state::find_account(const std::string& name, bool create) delete[] buf; - if (current) { + if (current) accounts_cache.insert(accounts_entry(name, current)); -#ifdef HUQUQULLAH - if (matches(main_ledger.huquq_categories, name)) - current->exempt_or_necessary = true; -#endif - } return current; } diff --git a/ledger.h b/ledger.h index 900ce8e8..e8aa9943 100644 --- a/ledger.h +++ b/ledger.h @@ -1,5 +1,5 @@ #ifndef _LEDGER_H -#define _LEDGER_H "$Revision: 1.17 $" +#define _LEDGER_H "$Revision: 1.18 $" ////////////////////////////////////////////////////////////////////// // @@ -105,16 +105,14 @@ struct transaction amount * cost; std::string note; -#ifdef HUQUQULLAH - bool exempt_or_necessary; -#endif + + bool is_virtual; + bool must_balance; + bool specified; transaction(account * _acct = NULL, amount * _cost = NULL) - : acct(_acct), cost(_cost) { -#ifdef HUQUQULLAH - exempt_or_necessary = false; -#endif - } + : acct(_acct), cost(_cost), + is_virtual(false), must_balance(true), specified(false) {} #ifdef DO_CLEANUP ~transaction() { @@ -122,6 +120,11 @@ struct transaction delete cost; } #endif + + const std::string acct_as_str() const; + + void print(std::ostream& out, bool display_quantity = true, + bool display_price = true) const; }; @@ -152,8 +155,9 @@ struct entry #endif bool matches(const std::list& regexps) const; - void print(std::ostream& out, bool shortcut = true) const; bool validate(bool show_unaccounted = false) const; + + void print(std::ostream& out, bool shortcut = true) const; }; struct cmp_entry_date { @@ -211,27 +215,15 @@ struct account commodity * comm; // default commodity for this account #endif totals balance; // optional, parse-time computed balance + int checked; // 'balance' uses this for speed's sake + accounts_t children; mutable std::string full_name; - int checked; // 'balance' uses this for speed's sake -#ifdef HUQUQULLAH - bool exempt_or_necessary; -#endif - - accounts_t children; + account() : parent(NULL), checked(0) {} - account() : parent(NULL), checked(0) { -#ifdef HUQUQULLAH - exempt_or_necessary = false; -#endif - } account(const std::string& _name, struct account * _parent = NULL) - : parent(_parent), name(_name), checked(0) { -#ifdef HUQUQULLAH - exempt_or_necessary = false; -#endif - } + : parent(_parent), name(_name), checked(0) {} const std::string as_str() const { if (! parent) @@ -252,22 +244,26 @@ struct state entries_t entries; totals prices; -#ifdef HUQUQULLAH - bool compute_huquq; - std::list huquq_categories; - amount * huquq; - commodity * huquq_commodity; - account * huquq_account; - account * huquq_expenses_account; + typedef std::map *, + std::list *> virtual_map; - state() : compute_huquq(false) {} -#endif + typedef std::pair *, + std::list *> virtual_map_pair; + + typedef virtual_map::const_iterator virtual_map_iterator; + + std::string mapping_file; + 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); + account * find_account(const std::string& name, bool create = true); }; diff --git a/ledger.texi b/ledger.texi index aba24264..0a358cb4 100644 --- a/ledger.texi +++ b/ledger.texi @@ -1,5 +1,5 @@ \input texinfo @c -*-texinfo-*- -@comment $Id: ledger.texi,v 1.8 2003/10/02 05:04:38 johnw Exp $ +@comment $Id: ledger.texi,v 1.9 2003/10/04 01:54:31 johnw Exp $ @comment %**start of header @setfilename ledger.info @@ -524,7 +524,21 @@ Then again, why would anyone use a Gnome-centric, 35 megabyte behemoth to edit their data, and a 65 kilobyte executable to query it@dots{} @node Computing Huqúqu'lláh, , Keeping a ledger, Top -@chapter Computing Huqúqu'lláh +@chapter Using Virtual Accounts + +One special feature of the @code{ledger} is the management of virtual +accounts. A virtual account is when you, in your mind, see money as +moving to certain places, when in reality that money has not moved at +all. There are several scenarios where this type of thinking comes in +very handy, and each of them will be discussed in detail. + +@section Saving for a Special Occasion + +@section Keeping a Budget + +@section Tracking Allocated Funds + +@section Computing Bahá'í Huqúqu'lláh As a Bahá'í, I need to compute Huqúqu'lláh on some of my assets. The exact details of this matter are rather complex, so if you have any diff --git a/main.cc b/main.cc index ca2f4201..7845451c 100644 --- a/main.cc +++ b/main.cc @@ -4,6 +4,9 @@ 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); #ifdef READ_GNUCASH extern bool parse_gnucash(std::istream& in, bool compute_balances); #endif @@ -42,9 +45,8 @@ static void show_help(std::ostream& out) << " -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 -#ifdef HUQUQULLAH - << " -H do not auto-compute Huququ'llah" << std::endl -#endif + << " -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 @@ -66,52 +68,6 @@ static void show_help(std::ostream& out) << " -S show empty accounts in balance totals" << std::endl; } -static const char *formats[] = { - "%Y/%m/%d", - "%m/%d", - "%Y.%m.%d", - "%m.%d", - "%a", - "%A", - "%b", - "%B", - "%Y", - NULL -}; - -static bool parse_date(const std::string& date_str, std::time_t * result) -{ - struct std::tm when; - - 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)) { - when.tm_hour = 0; - when.tm_min = 0; - when.tm_sec = 0; - - if (when.tm_year == -1) - when.tm_year = now_tm->tm_year; - - if (std::strcmp(*f, "%Y") == 0) { - when.tm_mon = 0; - when.tm_mday = 1; - } else { - if (when.tm_mon == -1) - when.tm_mon = now_tm->tm_mon; - if (when.tm_mday == -1) - when.tm_mday = now_tm->tm_mday; - } - *result = std::mktime(&when); - return true; - } - } - return false; -} - ////////////////////////////////////////////////////////////////////// // // Command-line parser and top-level logic. @@ -120,20 +76,20 @@ static bool parse_date(const std::string& date_str, std::time_t * result) int main(int argc, char * argv[]) { std::istream * file = NULL; + regexps_t regexps; - regexps_t regexps; - -#ifdef HUQUQULLAH - bool compute_huquq = true; -#endif have_beginning = false; have_ending = false; show_cleared = false; + const char * p = std::getenv("MAPPINGS"); + if (p) + main_ledger.mapping_file = p; + // Parse the command-line options int c; - while (-1 != (c = getopt(argc, argv, "+b:e:d:cChHwf:i:p:Pv"))) { + while (-1 != (c = getopt(argc, argv, "+b:e:d:cChRV:wf:i:p:Pv"))) { switch (char(c)) { case 'b': case 'e': { @@ -153,6 +109,7 @@ int main(int argc, char * argv[]) break; } +#if 0 case 'd': { if (! parse_date(optarg, &begin_date)) { std::cerr << "Error: Bad date string: " << optarg << std::endl; @@ -200,6 +157,7 @@ int main(int argc, char * argv[]) } break; } +#endif case 'c': end_date = std::time(NULL); @@ -208,9 +166,8 @@ int main(int argc, char * argv[]) case 'C': show_cleared = true; break; case 'h': show_help(std::cout); break; -#ifdef HUQUQULLAH - case 'H': compute_huquq = false; break; -#endif + 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; @@ -285,7 +242,7 @@ int main(int argc, char * argv[]) file = new std::ifstream(p); if (! file || ! *file) { - std::cerr << "Please specify the ledger file using the -f option." + std::cerr << "Please specify ledger file using -f option or LEDGER environment variable." << std::endl; return 1; } @@ -295,6 +252,12 @@ int main(int argc, char * argv[]) 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); + // Parse the ledger #ifdef READ_GNUCASH @@ -306,30 +269,9 @@ int main(int argc, char * argv[]) parse_gnucash(*file, command == "equity"); else #endif - { -#ifdef HUQUQULLAH - if (command == "register" && argv[optind + 1] && - std::string(argv[optind + 1]) != "Huququ'llah" && - std::string(argv[optind + 1]) != "Expenses:Huququ'llah") - compute_huquq = false; - - if (compute_huquq) { - main_ledger.compute_huquq = true; - - read_regexps(".huquq", main_ledger.huquq_categories); - - main_ledger.huquq_account = main_ledger.find_account("Huququ'llah"); - main_ledger.huquq_expenses_account = - main_ledger.find_account("Expenses:Huququ'llah"); - } -#endif - parse_ledger(*file, command == "equity"); - } -#ifdef DO_CLEANUP delete file; -#endif // Process the command diff --git a/parse.cc b/parse.cc index f88eccc1..19ab1219 100644 --- a/parse.cc +++ b/parse.cc @@ -1,12 +1,13 @@ #include "ledger.h" +#include #include #include #include namespace ledger { -static char * next_element(char * buf, bool variable = false) +static inline char * next_element(char * buf, bool variable = false) { char * p; @@ -30,19 +31,59 @@ static char * next_element(char * buf, bool variable = false) return p; } +static const char *formats[] = { + "%Y/%m/%d", + "%m/%d", + "%Y.%m.%d", + "%m.%d", + "%a", + "%A", + "%b", + "%B", + "%Y", + NULL +}; + +bool parse_date(const std::string& date_str, std::time_t * result, + const int year = -1) +{ + struct std::tm when; + + 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)) { + when.tm_hour = 0; + when.tm_min = 0; + when.tm_sec = 0; + + if (when.tm_year == -1) + when.tm_year = year == -1 ? now_tm->tm_year : year - 1900; + + if (std::strcmp(*f, "%Y") == 0) { + when.tm_mon = 0; + when.tm_mday = 1; + } else { + if (when.tm_mon == -1) + when.tm_mon = now_tm->tm_mon; + if (when.tm_mday == -1) + when.tm_mday = now_tm->tm_mday; + } + *result = std::mktime(&when); + return true; + } + } + return false; +} + static int linenum = 0; static void finalize_entry(entry * curr, bool compute_balances) { assert(curr); - - // If there were no transactions, it's definitely an error! - - if (curr->xacts.empty()) { - std::cerr << "Error, line " << (linenum - 1) - << ": Entry has no transactions!" << std::endl; - return; - } + assert(! curr->xacts.empty()); // Scan through and compute the total balance for the entry. @@ -51,7 +92,7 @@ static void finalize_entry(entry * curr, bool compute_balances) for (std::list::iterator x = curr->xacts.begin(); x != curr->xacts.end(); x++) - if ((*x)->cost) + if ((*x)->cost && ! (*x)->is_virtual) balance.credit((*x)->cost->value()); // If one transaction is of a different commodity than the others, @@ -66,6 +107,9 @@ static void finalize_entry(entry * curr, bool compute_balances) for (std::list::iterator x = curr->xacts.begin(); x != curr->xacts.end(); x++) { + if ((*x)->is_virtual) + continue; + if (! (*x)->cost->has_price() && ! (*x)->cost->comm()->prefix && (*x)->cost->comm()->separate) { @@ -90,62 +134,117 @@ static void finalize_entry(entry * curr, bool compute_balances) for (std::list::iterator x = curr->xacts.begin(); x != curr->xacts.end(); x++) { - if (! (*x)->cost) { - if (empty_allowed && ! balance.amounts.empty() && - balance.amounts.size() == 1) { - empty_allowed = false; + if ((*x)->is_virtual || (*x)->cost) + continue; - // If one transaction gives no value at all -- and all the - // rest are of the same commodity -- then its value is the - // inverse of the computed value of the others. + if (! empty_allowed || balance.amounts.empty() || + balance.amounts.size() != 1) { + std::cerr << "Error, line " << (linenum - 1) + << ": Transaction entry is lacking an amount." + << std::endl; + return; + } + empty_allowed = false; - totals::iterator i = balance.amounts.begin(); - (*x)->cost = (*i).second->value(); - (*x)->cost->negate(); + // If one transaction gives no value at all -- and all the + // rest are of the same commodity -- then its value is the + // inverse of the computed value of the others. - if (compute_balances) - (*x)->acct->balance.credit((*x)->cost); - } else { - std::cerr << "Error, line " << (linenum - 1) - << ": Transaction entry is lacking an amount." - << std::endl; - return; - } - } + totals::iterator i = balance.amounts.begin(); + (*x)->cost = (*i).second->value(); + (*x)->cost->negate(); -#ifdef HUQUQULLAH - if (! main_ledger.compute_huquq || - ! ((*x)->exempt_or_necessary || - (*x)->acct->exempt_or_necessary)) - continue; + if (compute_balances) + (*x)->acct->balance.credit((*x)->cost); + } - // Reflect 19% of the exempt or necessary transaction in the - // Huququ'llah account. + // If virtual accounts are being supported, walk through the + // 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 xacts; + + for (std::list::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::iterator i = (*m).second->begin(); + i != (*m).second->end(); + i++) { + transaction * t; - amount * divisor = create_amount("0.19"); - amount * temp = (*x)->cost->value(); + assert((*i)->is_virtual); + assert((*i)->cost); - transaction * t = new transaction(main_ledger.huquq_account, - temp->value(divisor)); - curr->xacts.push_back(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; + } - if (compute_balances) - t->acct->balance.credit(t->cost); + 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. + + bool added = false; + + for (std::list::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; + } + } - // Balance the above transaction by recording the inverse in - // Expenses:Huququ'llah. + if (! added) + for (std::list::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); + } + } - t = new transaction(main_ledger.huquq_expenses_account, - temp->value(divisor)); - t->cost->negate(); - curr->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. - if (compute_balances) - t->acct->balance.credit(t->cost); + for (std::list::iterator x = xacts.begin(); + x != xacts.end(); + x++) { + curr->xacts.push_back(*x); - delete temp; - delete divisor; -#endif + if (compute_balances) + (*x)->acct->balance.credit((*x)->cost); + } + } } // Compute the balances again, just to make sure it all comes out @@ -172,25 +271,10 @@ static void finalize_entry(entry * curr, bool compute_balances) bool parse_ledger(std::istream& in, bool compute_balances) { - std::time_t now = std::time(NULL); - struct std::tm * now_tm = std::localtime(&now); - int current_year = now_tm->tm_year + 1900; - - char line[1024]; - - struct std::tm moment; - + char line[1024]; + int current_year = -1; entry * curr = NULL; - // Compile the regular expression used for parsing amounts - 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++; @@ -199,62 +283,39 @@ bool parse_ledger(std::istream& in, bool compute_balances) continue; } else if (std::isdigit(line[0])) { - static char buf[256]; - int ovector[60]; + if (curr && ! curr->xacts.empty()) + finalize_entry(curr, compute_balances); + curr = new entry; - int matched = pcre_exec(entry_re, NULL, line, std::strlen(line), - 0, 0, ovector, 60); - if (! matched) { + char * next = next_element(line); + if (! parse_date(line, &curr->date, current_year)) { std::cerr << "Error, line " << linenum - << ": Failed to parse: " << line << std::endl; + << ": Failed to parse date: " << line << std::endl; continue; } - // If we haven't finished with the last entry yet, do so now - - if (curr) - finalize_entry(curr, compute_balances); - - curr = new entry; - - // Parse the date + if (*next == '*') { + curr->cleared = true; - int year = current_year; - if (ovector[1 * 2] >= 0) { - pcre_copy_substring(line, ovector, matched, 2, buf, 255); - year = std::atoi(buf); + next++; + while (std::isspace(*next)) + next++; } - assert(ovector[3 * 2] >= 0); - pcre_copy_substring(line, ovector, matched, 3, buf, 255); - int mon = std::atoi(buf); - - assert(ovector[4 * 2] >= 0); - pcre_copy_substring(line, ovector, matched, 4, buf, 255); - int mday = std::atoi(buf); - - memset(&moment, 0, sizeof(struct std::tm)); - - moment.tm_mday = mday; - moment.tm_mon = mon - 1; - moment.tm_year = year - 1900; - - curr->date = std::mktime(&moment); - - // Parse the remaining entry details - - if (ovector[5 * 2] >= 0) - curr->cleared = true; + if (*next == '(') { + char * p = std::strchr(next, ')'); + if (p) { + *p++ = '\0'; + curr->code = next; + next = p; - if (ovector[6 * 2] >= 0) { - pcre_copy_substring(line, ovector, matched, 7, buf, 255); - curr->code = buf; + next++; + while (std::isspace(*next)) + next++; + } } - if (ovector[8 * 2] >= 0) { - pcre_copy_substring(line, ovector, matched, 8, buf, 255); - curr->desc = buf; - } + curr->desc = next; } else if (curr && std::isspace(line[0])) { transaction * xact = new transaction(); @@ -301,28 +362,124 @@ bool parse_ledger(std::istream& in, bool compute_balances) xact->cost = create_amount(cost_str); } -#ifdef HUQUQULLAH - if (*p == '!') { - xact->exempt_or_necessary = true; + 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'; } -#endif - xact->acct = main_ledger.find_account(p); - if (compute_balances && xact->cost) - xact->acct->balance.credit(xact->cost); + if (xact->is_virtual && ! main_ledger.compute_virtual) { + delete xact; + } else { + xact->acct = main_ledger.find_account(p); + if (compute_balances && xact->cost) + xact->acct->balance.credit(xact->cost); - curr->xacts.push_back(xact); + curr->xacts.push_back(xact); + } } else if (line[0] == 'Y') { current_year = std::atoi(line + 2); } } - if (curr) + if (curr && ! curr->xacts.empty()) finalize_entry(curr, compute_balances); return true; } +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 * masks = NULL; + std::list * 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; + + 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; + + opening->date = std::time(NULL); + opening->cleared = true; + opening->desc = "Opening Balance"; + + 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 result = + main_ledger.virtual_mapping.insert + (state::virtual_map_pair(masks, xacts)); + assert(result.second); + + masks = NULL; + xacts = NULL; + } + + if (! masks) + masks = new std::list; + + masks->push_back(mask(line)); + } + } + + if (xacts) { + std::pair result = + main_ledger.virtual_mapping.insert + (state::virtual_map_pair(masks, xacts)); + assert(result.second); + } +} + } // namespace ledger diff --git a/register.cc b/register.cc index bc7f6b06..41f1fc48 100644 --- a/register.cc +++ b/register.cc @@ -117,7 +117,7 @@ void print_register(int argc, char ** argv, regexps_t& regexps, } out.width(22); - out << std::left << truncated(xact->acct->as_str(), 22) << " "; + out << std::left << truncated(xact->acct_as_str(), 22) << " "; out.width(12); out << std::right << street->as_str(true); @@ -139,7 +139,7 @@ void print_register(int argc, char ** argv, regexps_t& regexps, out << " "; out.width(22); - out << std::left << truncated((*y)->acct->as_str(), 22) << " "; + out << std::left << truncated((*y)->acct_as_str(), 22) << " "; out.width(12); street = (*y)->cost->street(); -- cgit v1.2.3