328 lines
7.9 KiB
C++
328 lines
7.9 KiB
C++
#include "ledger.h"
|
|
|
|
#include <cstring>
|
|
#include <ctime>
|
|
#include <cctype>
|
|
|
|
namespace ledger {
|
|
|
|
static 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)
|
|
p = std::strstr(buf, " ");
|
|
else
|
|
p = std::strchr(buf, ' ');
|
|
|
|
if (! p)
|
|
return NULL;
|
|
|
|
*p++ = '\0';
|
|
while (std::isspace(*p))
|
|
p++;
|
|
|
|
return p;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// Scan through and compute the total balance for the entry.
|
|
|
|
totals balance;
|
|
|
|
for (std::list<transaction *>::iterator x = curr->xacts.begin();
|
|
x != curr->xacts.end();
|
|
x++)
|
|
if ((*x)->cost)
|
|
balance.credit((*x)->cost->value());
|
|
|
|
// If one transaction is of a different commodity than the others,
|
|
// and it has no per-unit price, determine its price by dividing
|
|
// the unit count into the value of the balance.
|
|
//
|
|
// NOTE: We don't do this for prefix-style or joined-symbol
|
|
// commodities. Also, do it for the last eligible commodity first,
|
|
// if it meets the criteria.
|
|
|
|
if (! balance.amounts.empty() && balance.amounts.size() == 2) {
|
|
for (std::list<transaction *>::iterator x = curr->xacts.begin();
|
|
x != curr->xacts.end();
|
|
x++) {
|
|
if (! (*x)->cost->has_price() &&
|
|
! (*x)->cost->comm()->prefix &&
|
|
(*x)->cost->comm()->separate) {
|
|
for (totals::iterator i = balance.amounts.begin();
|
|
i != balance.amounts.end();
|
|
i++) {
|
|
if ((*i).second->comm() != (*x)->cost->comm()) {
|
|
(*x)->cost->set_value((*i).second);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Walk through each of the transactions, fixing up any that we
|
|
// can, and performing any on-the-fly calculations.
|
|
|
|
bool empty_allowed = true;
|
|
|
|
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 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.
|
|
|
|
totals::iterator i = balance.amounts.begin();
|
|
(*x)->cost = (*i).second->value();
|
|
(*x)->cost->negate();
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
#ifdef HUQUQULLAH
|
|
if (! main_ledger.compute_huquq ||
|
|
! ((*x)->exempt_or_necessary ||
|
|
(*x)->acct->exempt_or_necessary))
|
|
continue;
|
|
|
|
// Reflect 19% of the exempt or necessary transaction in the
|
|
// Huququ'llah account.
|
|
|
|
amount * divisor = create_amount("0.19");
|
|
amount * temp = (*x)->cost->value();
|
|
|
|
transaction * t = new transaction(main_ledger.huquq_account,
|
|
temp->value(divisor));
|
|
curr->xacts.push_back(t);
|
|
|
|
if (compute_balances)
|
|
t->acct->balance.credit(t->cost);
|
|
|
|
// Balance the above transaction by recording the inverse in
|
|
// Expenses:Huququ'llah.
|
|
|
|
t = new transaction(main_ledger.huquq_expenses_account,
|
|
temp->value(divisor));
|
|
t->cost->negate();
|
|
curr->xacts.push_back(t);
|
|
|
|
if (compute_balances)
|
|
t->acct->balance.credit(t->cost);
|
|
|
|
delete temp;
|
|
delete divisor;
|
|
#endif
|
|
}
|
|
|
|
// Compute the balances again, just to make sure it all comes out
|
|
// right (i.e., to zero for every commodity).
|
|
|
|
if (! curr->validate()) {
|
|
std::cerr << "Error, line " << (linenum - 1)
|
|
<< ": Failed to balance the following transaction:"
|
|
<< std::endl;
|
|
curr->print(std::cerr);
|
|
curr->validate(true);
|
|
return;
|
|
}
|
|
|
|
// If it's OK, add it to the general ledger's list of entries.
|
|
|
|
main_ledger.entries.push_back(curr);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Ledger parser
|
|
//
|
|
|
|
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;
|
|
|
|
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++;
|
|
|
|
if (line[0] == '\n') {
|
|
continue;
|
|
}
|
|
else if (std::isdigit(line[0])) {
|
|
static char buf[256];
|
|
int ovector[60];
|
|
|
|
int matched = pcre_exec(entry_re, NULL, line, std::strlen(line),
|
|
0, 0, ovector, 60);
|
|
if (! matched) {
|
|
std::cerr << "Error, line " << linenum
|
|
<< ": Failed to parse: " << 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
|
|
|
|
int year = current_year;
|
|
if (ovector[1 * 2] >= 0) {
|
|
pcre_copy_substring(line, ovector, matched, 2, buf, 255);
|
|
year = std::atoi(buf);
|
|
}
|
|
|
|
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 (ovector[6 * 2] >= 0) {
|
|
pcre_copy_substring(line, ovector, matched, 7, buf, 255);
|
|
curr->code = buf;
|
|
}
|
|
|
|
if (ovector[8 * 2] >= 0) {
|
|
pcre_copy_substring(line, ovector, matched, 8, buf, 255);
|
|
curr->desc = buf;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
for (char * t = cost_str + (std::strlen(cost_str) - 1);
|
|
std::isspace(*t);
|
|
t--)
|
|
*t = '\0';
|
|
|
|
xact->cost = create_amount(cost_str);
|
|
}
|
|
|
|
#ifdef HUQUQULLAH
|
|
if (*p == '!') {
|
|
xact->exempt_or_necessary = true;
|
|
p++;
|
|
}
|
|
#endif
|
|
|
|
xact->acct = main_ledger.find_account(p);
|
|
if (compute_balances && xact->cost)
|
|
xact->acct->balance.credit(xact->cost);
|
|
|
|
curr->xacts.push_back(xact);
|
|
}
|
|
else if (line[0] == 'Y') {
|
|
current_year = std::atoi(line + 2);
|
|
}
|
|
}
|
|
|
|
if (curr)
|
|
finalize_entry(curr, compute_balances);
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace ledger
|