summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2003-10-04 01:54:30 +0000
committerJohn Wiegley <johnw@newartisans.com>2003-10-04 01:54:30 +0000
commit3ef7bfdb32d770812e9805474ea9956568385efe (patch)
tree9bd7673055d3a5ff03caf0d4f9ba63a8c2a45cc7
parent3cfae2794784c1629dd98c0b600b2731e27a3f57 (diff)
downloadfork-ledger-3ef7bfdb32d770812e9805474ea9956568385efe.tar.gz
fork-ledger-3ef7bfdb32d770812e9805474ea9956568385efe.tar.bz2
fork-ledger-3ef7bfdb32d770812e9805474ea9956568385efe.zip
Added support for virtual accounts.
-rw-r--r--Makefile11
-rw-r--r--amount.cc13
-rw-r--r--ledger.cc83
-rw-r--r--ledger.h66
-rw-r--r--ledger.texi18
-rw-r--r--main.cc102
-rw-r--r--parse.cc407
-rw-r--r--register.cc4
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<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 << " ";
-
- 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<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;
}
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<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
-
- 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<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);
};
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 <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
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();