Added support for virtual accounts.

This commit is contained in:
John Wiegley 2003-10-04 01:54:30 +00:00
parent 3cfae27947
commit 3ef7bfdb32
8 changed files with 426 additions and 290 deletions

View file

@ -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

View file

@ -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();
}

View file

@ -4,12 +4,56 @@
namespace ledger {
bool use_warnings = false;
bool use_warnings = false;
state main_ledger;
std::list<mask> 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<transaction *>::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 << " ";
if ((*x)->is_virtual && ! (*x)->specified)
continue;
out.width(30);
out << std::left << (*x)->acct->as_str();
out << " ";
if ((*x)->cost && (! shortcut || x == xacts.begin())) {
out << " ";
out.width(10);
out << std::right << (*x)->cost->as_str(true);
}
// jww (2003-10-03): If we are shortcutting, don't print the
// "per-unit price" of a commodity, if it is not necessary.
if (! (*x)->note.empty())
out << " ; " << (*x)->note;
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<transaction *>::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;
}

View file

@ -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<mask>& 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
account() : parent(NULL), checked(0) {}
accounts_t children;
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<mask> huquq_categories;
amount * huquq;
commodity * huquq_commodity;
account * huquq_account;
account * huquq_expenses_account;
typedef std::map<std::list<mask> *,
std::list<transaction *> *> virtual_map;
state() : compute_huquq(false) {}
#endif
typedef std::pair<std::list<mask> *,
std::list<transaction *> *> 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);
};

View file

@ -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

102
main.cc
View file

@ -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

419
parse.cc
View file

@ -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.
totals::iterator i = balance.amounts.begin();
(*x)->cost = (*i).second->value();
(*x)->cost->negate();
if (compute_balances)
(*x)->acct->balance.credit((*x)->cost);
}
// 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;
assert((*i)->is_virtual);
assert((*i)->cost);
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 = 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;
}
}
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);
}
}
// 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);
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
@ -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
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)
if (*next == '*') {
curr->cleared = true;
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;
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();
@ -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

View file

@ -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();