summaryrefslogtreecommitdiff
path: root/parse.cc
diff options
context:
space:
mode:
Diffstat (limited to 'parse.cc')
-rw-r--r--parse.cc407
1 files changed, 282 insertions, 125 deletions
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 <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