diff options
Diffstat (limited to 'parse.cc')
-rw-r--r-- | parse.cc | 407 |
1 files changed, 282 insertions, 125 deletions
@@ -1,12 +1,13 @@ #include "ledger.h" +#include <fstream> #include <cstring> #include <ctime> #include <cctype> 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<transaction *>::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<transaction *>::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<transaction *>::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<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; - 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<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; + } + } - // Balance the above transaction by recording the inverse in - // Expenses:Huququ'llah. + 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); + } + } - 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<transaction *>::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<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; + + 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<state::virtual_map_iterator, bool> 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<mask>; + + masks->push_back(mask(line)); + } + } + + if (xacts) { + std::pair<state::virtual_map_iterator, bool> result = + main_ledger.virtual_mapping.insert + (state::virtual_map_pair(masks, xacts)); + assert(result.second); + } +} + } // namespace ledger |