summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2003-10-04 07:48:21 +0000
committerJohn Wiegley <johnw@newartisans.com>2003-10-04 07:48:21 +0000
commit2c109226140a99a07af079c70e54fb396bb3484e (patch)
tree9d8b23ebd636bb3e8e350fa9509bd931cb60eb94
parentcb9de0d695c89e03a926451f7ccf2f634e7be436 (diff)
downloadfork-ledger-2c109226140a99a07af079c70e54fb396bb3484e.tar.gz
fork-ledger-2c109226140a99a07af079c70e54fb396bb3484e.tar.bz2
fork-ledger-2c109226140a99a07af079c70e54fb396bb3484e.zip
Moved definition of virtual accounts into the ledger data file
itself. They are now called "automated transactions". Some rearchitecting.
-rw-r--r--Makefile10
-rw-r--r--amount.cc14
-rw-r--r--balance.cc55
-rw-r--r--equity.cc21
-rw-r--r--gnucash.cc59
-rw-r--r--ledger.cc80
-rw-r--r--ledger.h71
-rw-r--r--main.cc126
-rw-r--r--parse.cc566
-rw-r--r--register.cc28
10 files changed, 492 insertions, 538 deletions
diff --git a/Makefile b/Makefile
index eb4371f1..d5636a92 100644
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,7 @@
+define GNUCASH
+true
+endef
+
CODE = amount.cc \
ledger.cc \
parse.cc \
@@ -8,9 +12,9 @@ CODE = amount.cc \
OBJS = $(patsubst %.cc,%.o,$(CODE))
-CFLAGS = -Wall -ansi -pedantic # -DDEBUG=1
-DFLAGS = -O3 -fomit-frame-pointer -mcpu=pentium
-#DFLAGS = -g
+CFLAGS = -Wall -ansi -pedantic
+#DFLAGS = -O3 -fomit-frame-pointer -mcpu=pentium
+DFLAGS = -g -DDEBUG=1
INCS =
LIBS = -lgmpxx -lgmp -lpcre
diff --git a/amount.cc b/amount.cc
index 8d67bd3b..89c9d220 100644
--- a/amount.cc
+++ b/amount.cc
@@ -208,8 +208,8 @@ amount * gmp_amount::street() const
for (int cycles = 0; cycles < 10; cycles++) {
totals::iterator pi =
- main_ledger.prices.amounts.find(amt->comm_symbol());
- if (pi == main_ledger.prices.amounts.end()) {
+ main_ledger->prices.amounts.find(amt->comm_symbol());
+ if (pi == main_ledger->prices.amounts.end()) {
using namespace std;
if (! get_quotes)
@@ -231,7 +231,7 @@ amount * gmp_amount::street() const
char * p = strchr(buf, '\n');
if (p) *p = '\0';
- main_ledger.record_price((amt->comm_symbol() + "=" + buf).c_str());
+ main_ledger->record_price((amt->comm_symbol() + "=" + buf).c_str());
continue;
}
break;
@@ -347,10 +347,9 @@ static std::string amount_to_str(const commodity * comm, const mpz_t val,
else if (! comm->thousands)
s << quotient;
else {
- // jww (2003-09-29): use a smarter starting value
-
bool printed = false;
+ // jww (2003-09-29): use a smarter starting value for `powers'
for (int powers = 27; powers >= 0; powers -= 3) {
mpz_ui_pow_ui(divisor, 10, powers);
mpz_tdiv_q(temp, quotient, divisor);
@@ -554,8 +553,9 @@ static commodity * parse_amount(mpz_t out, const char * num,
commodity * comm = NULL;
if (saw_commodity) {
- commodities_iterator item = main_ledger.commodities.find(symbol.c_str());
- if (item == main_ledger.commodities.end()) {
+ commodities_map_iterator item =
+ main_ledger->commodities.find(symbol.c_str());
+ if (item == main_ledger->commodities.end()) {
comm = new commodity(symbol, prefix, separate,
thousands, european, precision);
} else {
diff --git a/balance.cc b/balance.cc
index 7301fa96..03063d21 100644
--- a/balance.cc
+++ b/balance.cc
@@ -5,17 +5,17 @@
namespace ledger {
extern bool show_cleared;
+extern bool show_virtual;
+extern bool show_children;
+extern bool show_empty;
+extern bool show_subtotals;
+extern bool full_names;
extern std::time_t begin_date;
extern bool have_beginning;
extern std::time_t end_date;
extern bool have_ending;
-static bool show_children;
-static bool show_empty;
-static bool no_subtotals;
-static bool full_names;
-
static void display_total(std::ostream& out, totals& balance,
account * acct, bool top_level)
{
@@ -26,7 +26,7 @@ static void display_total(std::ostream& out, totals& balance,
displayed = true;
acct->balance.print(out, 20);
- if (! no_subtotals && top_level)
+ if (show_subtotals && top_level)
balance.credit(acct->balance);
if (acct->parent && ! full_names && ! top_level) {
@@ -40,7 +40,7 @@ static void display_total(std::ostream& out, totals& balance,
// Display balances for all child accounts
- for (accounts_iterator i = acct->children.begin();
+ for (accounts_map_iterator i = acct->children.begin();
i != acct->children.end();
i++)
display_total(out, balance, (*i).second, ! displayed);
@@ -51,37 +51,13 @@ static void display_total(std::ostream& out, totals& balance,
// Balance reporting code
//
-void report_balances(int argc, char ** argv, regexps_t& regexps,
- std::ostream& out)
+void report_balances(std::ostream& out, regexps_map& regexps)
{
- show_children = false;
- show_empty = false;
- no_subtotals = false;
- full_names = false;
-
- optind = 1;
-
- int c;
- while (-1 != (c = getopt(argc, argv, "sSnF"))) {
- switch (char(c)) {
- case 's': show_children = true; break;
- case 'S': show_empty = true; break;
- case 'n': no_subtotals = true; break;
- case 'F': full_names = true; break;
- }
- }
-
- // Compile the list of specified regular expressions, which can be
- // specified on the command line, or using an include/exclude file
-
- for (; optind < argc; optind++)
- record_regexp(argv[optind], regexps);
-
// Walk through all of the ledger entries, computing the account
// totals
- for (entries_iterator i = main_ledger.entries.begin();
- i != main_ledger.entries.end();
+ for (entries_list_iterator i = main_ledger->entries.begin();
+ i != main_ledger->entries.end();
i++) {
if ((have_beginning && difftime((*i)->date, begin_date) < 0) ||
(have_ending && difftime((*i)->date, end_date) >= 0) ||
@@ -91,9 +67,12 @@ void report_balances(int argc, char ** argv, regexps_t& regexps,
for (std::list<transaction *>::iterator x = (*i)->xacts.begin();
x != (*i)->xacts.end();
x++) {
+ if (! show_virtual && (*x)->is_virtual)
+ continue;
+
for (account * acct = (*x)->acct;
acct;
- acct = no_subtotals ? NULL : acct->parent) {
+ acct = show_subtotals ? acct->parent : NULL) {
if (acct->checked == 0) {
if (regexps.empty()) {
if (! (show_children || ! acct->parent))
@@ -131,14 +110,14 @@ void report_balances(int argc, char ** argv, regexps_t& regexps,
totals balance;
- for (accounts_iterator i = main_ledger.accounts.begin();
- i != main_ledger.accounts.end();
+ for (accounts_map_iterator i = main_ledger->accounts.begin();
+ i != main_ledger->accounts.end();
i++)
display_total(out, balance, (*i).second, true);
// Print the total of all the balances shown
- if (! no_subtotals && ! balance.is_zero()) {
+ if (show_subtotals && ! balance.is_zero()) {
out << "--------------------" << std::endl;
balance.print(out, 20);
out << std::endl;
diff --git a/equity.cc b/equity.cc
index 1a4500b5..accbfd7c 100644
--- a/equity.cc
+++ b/equity.cc
@@ -2,7 +2,7 @@
namespace ledger {
-static void equity_entry(account * acct, regexps_t& regexps,
+static void equity_entry(account * acct, regexps_map& regexps,
std::ostream& out)
{
if (! acct->balance.is_zero() &&
@@ -27,7 +27,7 @@ static void equity_entry(account * acct, regexps_t& regexps,
opening.xacts.push_back(xact);
xact = new transaction();
- xact->acct = main_ledger.find_account("Equity:Opening Balances");
+ xact->acct = main_ledger->find_account("Equity:Opening Balances");
xact->cost = (*i).second->street();
xact->cost->negate();
opening.xacts.push_back(xact);
@@ -38,7 +38,7 @@ static void equity_entry(account * acct, regexps_t& regexps,
// Display balances for all child accounts
- for (accounts_iterator i = acct->children.begin();
+ for (accounts_map_iterator i = acct->children.begin();
i != acct->children.end();
i++)
equity_entry((*i).second, regexps, out);
@@ -51,23 +51,14 @@ static void equity_entry(account * acct, regexps_t& regexps,
// balances.
//
-void equity_ledger(int argc, char ** argv, regexps_t& regexps,
- std::ostream& out)
+void equity_ledger(std::ostream& out, regexps_map& regexps)
{
- optind = 1;
-
- // Compile the list of specified regular expressions, which can be
- // specified on the command line, or using an include/exclude file
-
- for (; optind < argc; optind++)
- record_regexp(argv[optind], regexps);
-
// The account have their current totals already generated as a
// result of parsing. We just have to output those values.
// totals
- for (accounts_iterator i = main_ledger.accounts.begin();
- i != main_ledger.accounts.end();
+ for (accounts_map_iterator i = main_ledger->accounts.begin();
+ i != main_ledger->accounts.end();
i++)
equity_entry((*i).second, regexps, out);
}
diff --git a/gnucash.cc b/gnucash.cc
index a056b352..234c869b 100644
--- a/gnucash.cc
+++ b/gnucash.cc
@@ -9,16 +9,15 @@ extern "C" {
namespace ledger {
-static account * curr_account;
-static std::string curr_account_id;
-static entry * curr_entry;
-static commodity * entry_comm;
-static commodity * curr_comm;
-static amount * curr_value;
-static std::string curr_quant;
-static XML_Parser current_parser;
-static bool do_compute;
-static accounts_t accounts_by_id;
+static account * curr_account;
+static std::string curr_account_id;
+static entry * curr_entry;
+static commodity * entry_comm;
+static commodity * curr_comm;
+static amount * curr_value;
+static std::string curr_quant;
+static XML_Parser current_parser;
+static accounts_map accounts_by_id;
static enum {
NO_ACTION,
@@ -98,21 +97,21 @@ static void endElement(void *userData, const char *name)
if (std::strcmp(name, "gnc:account") == 0) {
assert(curr_account);
if (! curr_account->parent)
- main_ledger.accounts.insert(accounts_entry(curr_account->name,
- curr_account));
- accounts_by_id.insert(accounts_entry(curr_account_id, curr_account));
+ main_ledger->accounts.insert(accounts_map_pair(curr_account->name,
+ curr_account));
+ accounts_by_id.insert(accounts_map_pair(curr_account_id, curr_account));
curr_account = NULL;
}
else if (std::strcmp(name, "gnc:commodity") == 0) {
assert(curr_comm);
- main_ledger.commodities.insert(commodities_entry(curr_comm->symbol,
- curr_comm));
+ main_ledger->commodities.insert(commodities_map_pair(curr_comm->symbol,
+ curr_comm));
curr_comm = NULL;
}
else if (std::strcmp(name, "gnc:transaction") == 0) {
assert(curr_entry);
assert(curr_entry->validate());
- main_ledger.entries.push_back(curr_entry);
+ main_ledger->entries.push_back(curr_entry);
curr_entry = NULL;
}
action = NO_ACTION;
@@ -130,11 +129,11 @@ static void dataHandler(void *userData, const char *s, int len)
break;
case ACCOUNT_PARENT: {
- accounts_iterator i = accounts_by_id.find(std::string(s, len));
+ accounts_map_iterator i = accounts_by_id.find(std::string(s, len));
assert(i != accounts_by_id.end());
curr_account->parent = (*i).second;
- (*i).second->children.insert(accounts_entry(curr_account->name,
- curr_account));
+ (*i).second->children.insert(accounts_map_pair(curr_account->name,
+ curr_account));
break;
}
@@ -142,9 +141,9 @@ static void dataHandler(void *userData, const char *s, int len)
if (curr_comm)
curr_comm->symbol = std::string(s, len);
else if (curr_account)
- curr_account->comm = main_ledger.commodities[std::string(s, len)];
+ curr_account->comm = main_ledger->commodities[std::string(s, len)];
else if (curr_entry)
- entry_comm = main_ledger.commodities[std::string(s, len)];
+ entry_comm = main_ledger->commodities[std::string(s, len)];
break;
case COMM_NAME:
@@ -186,7 +185,7 @@ static void dataHandler(void *userData, const char *s, int len)
break;
case XACT_ACCOUNT: {
- accounts_iterator i = accounts_by_id.find(std::string(s, len));
+ accounts_map_iterator i = accounts_by_id.find(std::string(s, len));
if (i == accounts_by_id.end()) {
std::cerr << "Could not find account " << std::string(s, len)
<< std::endl;
@@ -208,7 +207,7 @@ static void dataHandler(void *userData, const char *s, int len)
if (curr_value)
delete curr_value;
- if (do_compute)
+ if (main_ledger->compute_balances)
xact->acct->balance.credit(xact->cost);
break;
}
@@ -228,12 +227,16 @@ static void dataHandler(void *userData, const char *s, int len)
}
}
-bool parse_gnucash(std::istream& in, bool compute_balances)
+state * parse_gnucash(std::istream& in, bool compute_balances)
{
char buf[BUFSIZ];
+ state * ledger = new state;
+
+ main_ledger = ledger;
+ ledger->compute_balances = compute_balances;
+
action = NO_ACTION;
- do_compute = compute_balances;
curr_account = NULL;
curr_entry = NULL;
curr_value = NULL;
@@ -243,7 +246,7 @@ bool parse_gnucash(std::istream& in, bool compute_balances)
// GnuCash uses the USD commodity without defining it, which really
// means to use $.
commodity * usd = new commodity("$", true, false, true, false, 2);
- main_ledger.commodities.insert(commodities_entry("USD", usd));
+ main_ledger->commodities.insert(commodities_map_pair("USD", usd));
XML_Parser parser = XML_ParserCreate(NULL);
current_parser = parser;
@@ -258,7 +261,7 @@ bool parse_gnucash(std::istream& in, bool compute_balances)
std::cerr << XML_ErrorString(XML_GetErrorCode(parser))
<< " at line " << XML_GetCurrentLineNumber(parser)
<< std::endl;
- return false;
+ return NULL;
}
}
XML_ParserFree(parser);
@@ -267,7 +270,7 @@ bool parse_gnucash(std::istream& in, bool compute_balances)
curr_account_id.clear();
curr_quant.clear();
- return true;
+ return ledger;
}
} // namespace ledger
diff --git a/ledger.cc b/ledger.cc
index 0d591532..7f546617 100644
--- a/ledger.cc
+++ b/ledger.cc
@@ -4,10 +4,8 @@
namespace ledger {
-bool use_warnings = false;
-state main_ledger;
-
-std::list<mask> regexps;
+bool use_warnings = false;
+state * main_ledger;
const std::string transaction::acct_as_str() const
{
@@ -85,7 +83,7 @@ void entry::print(std::ostream& out, bool shortcut) const
// jww (2003-10-03): If we are shortcutting, don't print the
// "per-unit price" of a commodity, if it is not necessary.
- (*x)->print(out, shortcut && x != xacts.begin());
+ (*x)->print(out, shortcut && x == xacts.begin());
}
out << std::endl;
@@ -98,8 +96,7 @@ bool entry::validate(bool show_unaccounted) const
for (std::list<transaction *>::const_iterator x = xacts.begin();
x != xacts.end();
x++)
- if ((*x)->cost && (*x)->must_balance &&
- (! (*x)->is_virtual || main_ledger.compute_virtual))
+ if ((*x)->cost && (*x)->must_balance)
balance.credit((*x)->cost->value());
if (show_unaccounted && ! balance.is_zero()) {
@@ -132,16 +129,12 @@ bool entry::matches(const std::list<mask>& regexps) const
}
}
-#ifdef DO_CLEANUP
-
totals::~totals()
{
for (iterator i = amounts.begin(); i != amounts.end(); i++)
delete (*i).second;
}
-#endif // DO_CLEANUP
-
void totals::credit(const totals& other)
{
for (const_iterator i = other.amounts.begin();
@@ -179,36 +172,14 @@ void totals::print(std::ostream& out, int width) const
// Print out the entire ledger that was read in, sorted by date.
// This can be used to "wash" ugly ledger files.
-void print_ledger(int argc, char ** argv, regexps_t& regexps,
- std::ostream& out)
+void state::print(std::ostream& out, regexps_map& regexps,
+ bool shortcut) const
{
- bool use_shortcuts = true;
-
- optind = 1;
-
- int c;
- while (-1 != (c = getopt(argc, argv, "n"))) {
- switch (char(c)) {
- case 'n': use_shortcuts = false; break;
- }
- }
-
- // Compile the list of specified regular expressions, which can be
- // specified on the command line, or using an include/exclude file
-
- for (; optind < argc; optind++)
- record_regexp(argv[optind], regexps);
-
- // Sort the list of entries by date, then print them in order.
-
- std::sort(main_ledger.entries.begin(), main_ledger.entries.end(),
- cmp_entry_date());
-
- for (entries_iterator i = main_ledger.entries.begin();
- i != main_ledger.entries.end();
+ for (entries_list_const_iterator i = entries.begin();
+ i != entries.end();
i++)
if ((*i)->matches(regexps))
- (*i)->print(out, use_shortcuts);
+ (*i)->print(out, shortcut);
}
mask::mask(const std::string& pat) : exclude(false)
@@ -236,12 +207,7 @@ mask::mask(const std::string& pat) : exclude(false)
<< std::endl;
}
-void record_regexp(const std::string& pattern, regexps_t& regexps)
-{
- regexps.push_back(mask(pattern));
-}
-
-void read_regexps(const std::string& path, regexps_t& regexps)
+void read_regexps(const std::string& path, regexps_map& regexps)
{
if (access(path.c_str(), R_OK) != -1) {
std::ifstream file(path.c_str());
@@ -250,12 +216,12 @@ void read_regexps(const std::string& path, regexps_t& regexps)
char buf[80];
file.getline(buf, 79);
if (*buf && ! std::isspace(*buf))
- record_regexp(buf, regexps);
+ regexps.push_back(mask(buf));
}
}
}
-bool matches(const regexps_t& regexps, const std::string& str,
+bool matches(const regexps_map& regexps, const std::string& str,
bool * by_exclusion)
{
assert(! regexps.empty());
@@ -285,28 +251,24 @@ bool matches(const regexps_t& regexps, const std::string& str,
return match;
}
-#ifdef DO_CLEANUP
-
state::~state()
{
- for (commodities_iterator i = commodities.begin();
+ for (commodities_map_iterator i = commodities.begin();
i != commodities.end();
i++)
delete (*i).second;
- for (accounts_iterator i = accounts.begin();
+ for (accounts_map_iterator i = accounts.begin();
i != accounts.end();
i++)
delete (*i).second;
- for (entries_iterator i = entries.begin();
+ for (entries_list_iterator i = entries.begin();
i != entries.end();
i++)
delete *i;
}
-#endif // DO_CLEANUP
-
void state::record_price(const std::string& setting)
{
char buf[128];
@@ -326,7 +288,7 @@ void state::record_price(const std::string& setting)
account * state::find_account(const std::string& name, bool create)
{
- accounts_iterator i = accounts_cache.find(name);
+ accounts_map_iterator i = accounts_cache.find(name);
if (i != accounts_cache.end())
return (*i).second;
@@ -338,26 +300,26 @@ account * state::find_account(const std::string& name, bool create)
tok;
tok = std::strtok(NULL, ":")) {
if (! current) {
- accounts_iterator i = accounts.find(tok);
+ accounts_map_iterator i = accounts.find(tok);
if (i == accounts.end()) {
if (! create) {
delete[] buf;
return NULL;
}
current = new account(tok);
- accounts.insert(accounts_entry(tok, current));
+ accounts.insert(accounts_map_pair(tok, current));
} else {
current = (*i).second;
}
} else {
- accounts_iterator i = current->children.find(tok);
+ accounts_map_iterator i = current->children.find(tok);
if (i == current->children.end()) {
if (! create) {
delete[] buf;
return NULL;
}
current = new account(tok, current);
- current->parent->children.insert(accounts_entry(tok, current));
+ current->parent->children.insert(accounts_map_pair(tok, current));
} else {
current = (*i).second;
}
@@ -367,7 +329,7 @@ account * state::find_account(const std::string& name, bool create)
delete[] buf;
if (current)
- accounts_cache.insert(accounts_entry(name, current));
+ accounts_cache.insert(accounts_map_pair(name, current));
return current;
}
diff --git a/ledger.h b/ledger.h
index e8aa9943..4b21b54f 100644
--- a/ledger.h
+++ b/ledger.h
@@ -1,5 +1,5 @@
#ifndef _LEDGER_H
-#define _LEDGER_H "$Revision: 1.18 $"
+#define _LEDGER_H "$Revision: 1.19 $"
//////////////////////////////////////////////////////////////////////
//
@@ -44,9 +44,9 @@ struct commodity
bool thou = true, bool euro = false, int prec = 2);
};
-typedef std::map<const std::string, commodity *> commodities_t;
-typedef commodities_t::iterator commodities_iterator;
-typedef std::pair<const std::string, commodity *> commodities_entry;
+typedef std::map<const std::string, commodity *> commodities_map;
+typedef commodities_map::iterator commodities_map_iterator;
+typedef std::pair<const std::string, commodity *> commodities_map_pair;
class amount
@@ -90,11 +90,11 @@ struct mask
mask(const std::string& pattern);
};
-typedef std::list<mask> regexps_t;
+typedef std::list<mask> regexps_map;
-void record_regexp(const std::string& pattern, regexps_t& regexps);
-void read_regexps(const std::string& path, regexps_t& regexps);
-bool matches(const regexps_t& regexps, const std::string& str,
+void record_regexp(const std::string& pattern, regexps_map& regexps);
+void read_regexps(const std::string& path, regexps_map& regexps);
+bool matches(const regexps_map& regexps, const std::string& str,
bool * by_exclusion = NULL);
@@ -114,12 +114,10 @@ struct transaction
: acct(_acct), cost(_cost),
is_virtual(false), must_balance(true), specified(false) {}
-#ifdef DO_CLEANUP
~transaction() {
if (cost)
delete cost;
}
-#endif
const std::string acct_as_str() const;
@@ -140,7 +138,6 @@ struct entry
entry() : cleared(false) {}
-#ifdef DO_CLEANUP
// If we're running as a command-line tool, it's cheaper to just
// throw away the heap on exit, than spend time freeing things up
// like a good citizen.
@@ -152,7 +149,6 @@ struct entry
delete *i;
}
}
-#endif
bool matches(const std::list<mask>& regexps) const;
bool validate(bool show_unaccounted = false) const;
@@ -166,22 +162,21 @@ struct cmp_entry_date {
}
};
-typedef std::vector<entry *> entries_t;
-typedef entries_t::iterator entries_iterator;
+typedef std::vector<entry *> entries_list;
+typedef entries_list::iterator entries_list_iterator;
+typedef entries_list::const_iterator entries_list_const_iterator;
struct totals
{
- typedef std::map<const std::string, amount *> map;
- typedef map::iterator iterator;
- typedef map::const_iterator const_iterator;
+ typedef std::map<const std::string, amount *> map;
+ typedef map::iterator iterator;
+ typedef map::const_iterator const_iterator;
typedef std::pair<const std::string, amount *> pair;
map amounts;
-#ifdef DO_CLEANUP
~totals();
-#endif
void credit(const amount * val) {
std::pair<iterator, bool> result =
@@ -202,9 +197,9 @@ struct totals
};
-typedef std::map<const std::string, account *> accounts_t;
-typedef accounts_t::iterator accounts_iterator;
-typedef std::pair<const std::string, account *> accounts_entry;
+typedef std::map<const std::string, account *> accounts_map;
+typedef accounts_map::iterator accounts_map_iterator;
+typedef std::pair<const std::string, account *> accounts_map_pair;
struct account
{
@@ -216,7 +211,7 @@ struct account
#endif
totals balance; // optional, parse-time computed balance
int checked; // 'balance' uses this for speed's sake
- accounts_t children;
+ accounts_map children;
mutable std::string full_name;
@@ -238,11 +233,12 @@ struct account
struct state
{
- commodities_t commodities;
- accounts_t accounts;
- accounts_t accounts_cache; // maps full names to accounts
- entries_t entries;
+ commodities_map commodities;
+ accounts_map accounts;
+ accounts_map accounts_cache; // maps full names to accounts
+ entries_list entries;
totals prices;
+ int current_year;
typedef std::map<std::list<mask> *,
std::list<transaction *> *> virtual_map;
@@ -252,30 +248,31 @@ struct state
typedef virtual_map::const_iterator virtual_map_iterator;
- std::string mapping_file;
+ bool compute_balances;
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);
+ template<typename Compare>
+ void sort(Compare comp) {
+ std::sort(entries.begin(), entries.end(), comp);
+ }
+ void print(std::ostream& out, regexps_map& regexps, bool shortcut) const;
+
account * find_account(const std::string& name, bool create = true);
};
-extern state main_ledger;
-extern bool use_warnings;
+extern state * main_ledger;
+extern bool use_warnings;
inline commodity::commodity(const std::string& sym, bool pre, bool sep,
bool thou, bool euro, int prec)
: symbol(sym), prefix(pre), separate(sep),
thousands(thou), european(euro), precision(prec) {
- std::pair<commodities_iterator, bool> result =
- main_ledger.commodities.insert(commodities_entry(sym, this));
+ std::pair<commodities_map_iterator, bool> result =
+ main_ledger->commodities.insert(commodities_map_pair(sym, this));
assert(result.second);
}
diff --git a/main.cc b/main.cc
index 7845451c..4258b72e 100644
--- a/main.cc
+++ b/main.cc
@@ -1,27 +1,31 @@
#include "ledger.h"
+#define LEDGER_VERSION "1.1"
+
#include <fstream>
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);
+ extern state * parse_ledger(std::istream& in, regexps_map& regexps,
+ bool compute_balances);
#ifdef READ_GNUCASH
- extern bool parse_gnucash(std::istream& in, bool compute_balances);
+ extern state * parse_gnucash(std::istream& in, bool compute_balances);
#endif
- extern void report_balances(int argc, char ** argv, regexps_t& regexps,
- std::ostream& out);
- extern void print_register(int argc, char ** argv, regexps_t& regexps,
- std::ostream& out);
- extern void print_ledger(int argc, char ** argv, regexps_t& regexps,
- std::ostream& out);
- extern void equity_ledger(int argc, char ** argv, regexps_t& regexps,
- std::ostream& out);
+ extern bool parse_date(const char * date_str, std::time_t * result,
+ const int year = -1);
+
+ extern void report_balances(std::ostream& out, regexps_map& regexps);
+ extern void print_register(const std::string& acct_name, std::ostream& out,
+ regexps_map& regexps);
+ extern void equity_ledger(std::ostream& out, regexps_map& regexps);
bool show_cleared;
+ bool show_virtual;
bool get_quotes;
+ bool show_children;
+ bool show_empty;
+ bool show_subtotals;
+ bool full_names;
std::time_t begin_date;
bool have_beginning;
@@ -76,20 +80,22 @@ static void show_help(std::ostream& out)
int main(int argc, char * argv[])
{
std::istream * file = NULL;
- regexps_t regexps;
- have_beginning = false;
- have_ending = false;
- show_cleared = false;
+ regexps_map regexps;
- const char * p = std::getenv("MAPPINGS");
- if (p)
- main_ledger.mapping_file = p;
+ have_beginning = false;
+ have_ending = false;
+ show_cleared = false;
+ show_virtual = true;
+ show_children = false;
+ show_empty = false;
+ show_subtotals = true;
+ full_names = false;
// Parse the command-line options
int c;
- while (-1 != (c = getopt(argc, argv, "+b:e:d:cChRV:wf:i:p:Pv"))) {
+ while (-1 != (c = getopt(argc, argv, "+b:e:d:cChRV:wf:i:p:PvsSnF"))) {
switch (char(c)) {
case 'b':
case 'e': {
@@ -164,12 +170,16 @@ int main(int argc, char * argv[])
have_ending = true;
break;
- case 'C': show_cleared = true; break;
case 'h': show_help(std::cout); break;
- 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;
+ case 'f': file = new std::ifstream(optarg); break;
+
+ case 'C': show_cleared = true; break;
+ case 'R': show_virtual = false; break;
+ case 'w': use_warnings = true; break;
+ case 's': show_children = true; break;
+ case 'S': show_empty = true; break;
+ case 'n': show_subtotals = false; break;
+ case 'F': full_names = true; break;
// -i path-to-file-of-regexps
case 'i':
@@ -186,10 +196,10 @@ int main(int argc, char * argv[])
char buf[80];
pricedb.getline(buf, 79);
if (*buf && ! std::isspace(*buf))
- main_ledger.record_price(buf);
+ main_ledger->record_price(buf);
}
} else {
- main_ledger.record_price(optarg);
+ main_ledger->record_price(optarg);
}
break;
@@ -199,7 +209,7 @@ int main(int argc, char * argv[])
case 'v':
std::cout
- << "Ledger Accouting Tool 1.0" << std::endl
+ << "Ledger Accouting Tool " LEDGER_VERSION << std::endl
<< " Copyright (c) 2003 John Wiegley <johnw@newartisans.com>"
<< std::endl << std::endl
<< "This program is made available under the terms of the BSD"
@@ -242,7 +252,8 @@ int main(int argc, char * argv[])
file = new std::ifstream(p);
if (! file || ! *file) {
- std::cerr << "Please specify ledger file using -f option or LEDGER environment variable."
+ std::cerr << ("Please specify ledger file using -f option "
+ "or LEDGER environment variable.")
<< std::endl;
return 1;
}
@@ -250,13 +261,17 @@ int main(int argc, char * argv[])
// Read the command word
- const std::string command = argv[optind];
+ 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);
+ int optind_begin = optind;
+ if (command == "register") {
+ if (optind == argc) {
+ std::cerr << ("Error: Must specify an account name "
+ "after the 'register' command.") << std::endl;
+ return 1;
+ }
+ optind++;
+ }
// Parse the ledger
@@ -266,23 +281,42 @@ int main(int argc, char * argv[])
file->seekg(0);
if (std::strncmp(buf, "<?xml version=\"1.0\"?>", 21) == 0)
- parse_gnucash(*file, command == "equity");
+ main_ledger = parse_gnucash(*file, command == "equity");
else
#endif
- parse_ledger(*file, command == "equity");
+ main_ledger = parse_ledger(*file, regexps, command == "equity");
delete file;
+ if (! main_ledger)
+ std::exit(1);
+
+ // Compile the list of specified regular expressions, which can be
+ // specified after the command, or using the '-i FILE' option
+
+ for (; optind < argc; optind++)
+ regexps.push_back(mask(argv[optind]));
+
// Process the command
- if (command == "balance")
- report_balances(argc - optind, &argv[optind], regexps, std::cout);
- else if (command == "register")
- print_register(argc - optind, &argv[optind], regexps, std::cout);
- else if (command == "print")
- print_ledger(argc - optind, &argv[optind], regexps, std::cout);
- else if (command == "equity")
- equity_ledger(argc - optind, &argv[optind], regexps, std::cout);
+ if (command == "balance") {
+ report_balances(std::cout, regexps);
+ }
+ else if (command == "register") {
+ print_register(argv[optind_begin], std::cout, regexps);
+ }
+ else if (command == "print") {
+ main_ledger->sort(cmp_entry_date());
+ main_ledger->print(std::cout, regexps, true);
+ }
+ else if (command == "equity") {
+ equity_ledger(std::cout, regexps);
+ }
+
+#if 0
+ // Deleting the main ledger just isn't necessary at this point.
+ delete main_ledger;
+#endif
}
// main.cc ends here.
diff --git a/parse.cc b/parse.cc
index 19ab1219..6a79eae8 100644
--- a/parse.cc
+++ b/parse.cc
@@ -7,28 +7,34 @@
namespace ledger {
+static inline char * skip_ws(char * ptr)
+{
+ while (std::isspace(*ptr))
+ ptr++;
+ return ptr;
+}
+
static inline 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) {
+ // 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
+ } else {
p = std::strchr(buf, ' ');
+ }
if (! p)
return NULL;
*p++ = '\0';
- while (std::isspace(*p))
- p++;
- return p;
+ return skip_ws(p);
}
static const char *formats[] = {
@@ -44,17 +50,17 @@ static const char *formats[] = {
NULL
};
-bool parse_date(const std::string& date_str, std::time_t * result,
+bool parse_date(const char * date_str, std::time_t * result,
const int year = -1)
{
struct std::tm when;
- std::time_t now = std::time(NULL);
+ 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)) {
+ if (strptime(date_str, *f, &when)) {
when.tm_hour = 0;
when.tm_min = 0;
when.tm_sec = 0;
@@ -78,14 +84,130 @@ bool parse_date(const std::string& date_str, std::time_t * result,
return false;
}
-static int linenum = 0;
+#define MAX_LINE 1024
+
+static int linenum;
+
+transaction * parse_transaction(std::istream& in, state * ledger)
+{
+ transaction * xact = new transaction();
+
+ static char line[MAX_LINE + 1];
+ in.getline(line, MAX_LINE);
+ linenum++;
+
+ char * p = line;
+ p = skip_ws(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';
+ xact->note = skip_ws(note_str);
+ }
+
+ for (char * t = cost_str + (std::strlen(cost_str) - 1);
+ std::isspace(*t);
+ t--)
+ *t = '\0';
+
+ xact->cost = create_amount(cost_str);
+ }
+
+ 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';
+ }
+
+ xact->acct = ledger->find_account(p);
-static void finalize_entry(entry * curr, bool compute_balances)
+ if (ledger->compute_balances && xact->cost)
+ xact->acct->balance.credit(xact->cost);
+
+ return xact;
+}
+
+entry * parse_entry(std::istream& in, state * ledger)
{
- assert(curr);
- assert(! curr->xacts.empty());
+ entry * curr = new entry;
+
+ static char line[MAX_LINE + 1];
+ in.getline(line, MAX_LINE);
+ linenum++;
+
+ // Parse the date
+
+ char * next = next_element(line);
+ if (! parse_date(line, &curr->date, ledger->current_year)) {
+ std::cerr << "Error, line " << linenum
+ << ": Failed to parse date: " << line << std::endl;
+ return NULL;
+ }
- // Scan through and compute the total balance for the entry.
+ // Parse the optional cleared flag: *
+
+ if (*next == '*') {
+ curr->cleared = true;
+ next = skip_ws(++next);
+ }
+
+ // Parse the optional code: (TEXT)
+
+ if (*next == '(') {
+ if (char * p = std::strchr(next++, ')')) {
+ *p++ = '\0';
+ curr->code = next;
+ next = skip_ws(p);
+ }
+ }
+
+ // Parse the description text
+
+ curr->desc = next;
+
+ // Parse all of the transactions associated with this entry
+
+ while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t'))
+ if (transaction * xact = parse_transaction(in, ledger))
+ curr->xacts.push_back(xact);
+
+ // If there were no transactions, throw away the entry
+
+ if (curr->xacts.empty()) {
+ delete curr;
+ return NULL;
+ }
+
+ // Scan through and compute the total balance for the entry. This
+ // is used for auto-calculating the value of entries with no cost,
+ // and the per-unit price of unpriced commodities.
totals balance;
@@ -139,10 +261,10 @@ static void finalize_entry(entry * curr, bool compute_balances)
if (! empty_allowed || balance.amounts.empty() ||
balance.amounts.size() != 1) {
- std::cerr << "Error, line " << (linenum - 1)
+ std::cerr << "Error, line " << linenum
<< ": Transaction entry is lacking an amount."
<< std::endl;
- return;
+ return NULL;
}
empty_allowed = false;
@@ -154,7 +276,7 @@ static void finalize_entry(entry * curr, bool compute_balances)
(*x)->cost = (*i).second->value();
(*x)->cost->negate();
- if (compute_balances)
+ if (ledger->compute_balances)
(*x)->acct->balance.credit((*x)->cost);
}
@@ -162,47 +284,56 @@ static void finalize_entry(entry * curr, bool compute_balances)
// 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;
+ for (state::virtual_map_iterator m = ledger->virtual_mapping.begin();
+ m != ledger->virtual_mapping.end();
+ m++) {
+ std::list<transaction *> xacts;
- assert((*i)->is_virtual);
- assert((*i)->cost);
+ 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;
- 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;
- }
+ for (std::list<transaction *>::iterator i = (*m).second->begin();
+ i != (*m).second->end();
+ i++) {
+ transaction * 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;
+ }
+
+ t->is_virtual = (*i)->is_virtual;
+ t->must_balance = (*i)->must_balance;
- 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.
- // 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;
- 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;
+ }
+ }
- for (std::list<transaction *>::iterator y = xacts.begin();
- y != xacts.end();
+ 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) {
@@ -213,42 +344,28 @@ static void finalize_entry(entry * curr, bool compute_balances)
}
}
- 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);
- }
+ 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.
+ // 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);
+ 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);
- }
+ if (ledger->compute_balances)
+ (*x)->acct->balance.credit((*x)->cost);
}
}
// Compute the balances again, just to make sure it all comes out
- // right (i.e., to zero for every commodity).
+ // right (i.e., zero for every commodity).
if (! curr->validate()) {
std::cerr << "Error, line " << (linenum - 1)
@@ -256,230 +373,113 @@ static void finalize_entry(entry * curr, bool compute_balances)
<< std::endl;
curr->print(std::cerr);
curr->validate(true);
- return;
+ delete curr;
+ return NULL;
}
-
- // If it's OK, add it to the general ledger's list of entries.
-
- main_ledger.entries.push_back(curr);
+ return curr;
}
-//////////////////////////////////////////////////////////////////////
-//
-// Ledger parser
-//
-
-bool parse_ledger(std::istream& in, bool compute_balances)
+void parse_automated_transactions(std::istream& in, state * ledger)
{
- char line[1024];
- int current_year = -1;
- entry * curr = NULL;
+ static char line[MAX_LINE + 1];
- while (! in.eof()) {
- in.getline(line, 1023);
- linenum++;
-
- if (line[0] == '\n') {
- continue;
- }
- else if (std::isdigit(line[0])) {
- if (curr && ! curr->xacts.empty())
- finalize_entry(curr, compute_balances);
- curr = new entry;
-
- char * next = next_element(line);
- if (! parse_date(line, &curr->date, current_year)) {
- std::cerr << "Error, line " << linenum
- << ": Failed to parse date: " << line << std::endl;
- continue;
- }
-
- if (*next == '*') {
- curr->cleared = true;
-
- next++;
- while (std::isspace(*next))
- next++;
- }
-
- 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();
-
- 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;
- }
+ std::list<mask> * masks = NULL;
- 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;
- }
+ while (! in.eof() && in.peek() == '=') {
+ in.getline(line, MAX_LINE);
+ linenum++;
- for (char * t = cost_str + (std::strlen(cost_str) - 1);
- std::isspace(*t);
- t--)
- *t = '\0';
+ char * p = line + 1;
+ p = skip_ws(p);
- xact->cost = create_amount(cost_str);
- }
+ if (! masks)
+ masks = new std::list<mask>;
+ masks->push_back(mask(p));
+ }
- if (*p == '[' || *p == '(') {
- xact->is_virtual = true;
- xact->specified = true;
- xact->must_balance = *p == '[';
- p++;
+ std::list<transaction *> * xacts = NULL;
- char * e = p + (std::strlen(p) - 1);
- assert(*e == ')' || *e == ']');
- *e = '\0';
- }
+ while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) {
+ if (transaction * xact = parse_transaction(in, ledger)) {
+ if (! xacts)
+ xacts = new std::list<transaction *>;
- if (xact->is_virtual && ! main_ledger.compute_virtual) {
- delete xact;
+ if (! xact->cost) {
+ std::cerr << "Error, line " << (linenum - 1)
+ << ": All automated transactions must have a value."
+ << std::endl;
} else {
- xact->acct = main_ledger.find_account(p);
- if (compute_balances && xact->cost)
- xact->acct->balance.credit(xact->cost);
-
- curr->xacts.push_back(xact);
+ xacts->push_back(xact);
}
}
- else if (line[0] == 'Y') {
- current_year = std::atoi(line + 2);
- }
}
- if (curr && ! curr->xacts.empty())
- finalize_entry(curr, compute_balances);
-
- return true;
+ if (masks && xacts)
+ ledger->virtual_mapping.insert(state::virtual_map_pair(masks, xacts));
+ else if (masks)
+ delete masks;
+ else if (xacts)
+ delete xacts;
}
-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;
+//////////////////////////////////////////////////////////////////////
+//
+// Ledger parser
+//
- opening->date = std::time(NULL);
- opening->cleared = true;
- opening->desc = "Opening Balance";
+state * parse_ledger(std::istream& in, regexps_map& regexps,
+ bool compute_balances)
+{
+ static char line[MAX_LINE + 1];
+ char c;
- 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;
- }
+ state * ledger = new state;
+ main_ledger = ledger;
- if (! masks)
- masks = new std::list<mask>;
+ ledger->compute_balances = compute_balances;
- masks->push_back(mask(line));
+ linenum = 0;
+ while (! in.eof()) {
+ switch (in.peek()) {
+ case -1: // end of file
+ return ledger;
+
+ case '\n':
+ linenum++;
+ case '\r': // skip blank lines
+ in.get(c);
+ break;
+
+ case 'Y': // set the current year
+ in >> c;
+ in >> ledger->current_year;
+ break;
+
+ case ';': // a comment line
+ in.getline(line, MAX_LINE);
+ linenum++;
+ break;
+
+ case '-':
+ case '+': // permanent regexps
+ in.getline(line, MAX_LINE);
+ linenum++;
+
+ // Add the regexp to whatever masks currently exist
+ regexps.push_back(mask(line));
+ break;
+
+ case '=': // automated transactions
+ parse_automated_transactions(in, ledger);
+ break;
+
+ default:
+ if (entry * ent = parse_entry(in, ledger))
+ ledger->entries.push_back(ent);
+ break;
}
}
-
- if (xacts) {
- std::pair<state::virtual_map_iterator, bool> result =
- main_ledger.virtual_mapping.insert
- (state::virtual_map_pair(masks, xacts));
- assert(result.second);
- }
+ return ledger;
}
} // namespace ledger
diff --git a/register.cc b/register.cc
index 41f1fc48..38579ce0 100644
--- a/register.cc
+++ b/register.cc
@@ -28,39 +28,23 @@ static std::string truncated(const std::string& str, int width)
// Register printing code
//
-void print_register(int argc, char ** argv, regexps_t& regexps,
- std::ostream& out)
+void print_register(const std::string& acct_name, std::ostream& out,
+ regexps_map& regexps)
{
- optind = 1;
-
- // Find out which account this register is to be printed for
-
- if (optind == argc) {
- std::cerr << ("Error: Must specify an account name "
- "after the 'register' command.") << std::endl;
- return;
- }
-
- account * acct = main_ledger.find_account(argv[optind++], false);
+ account * acct = main_ledger->find_account(acct_name, false);
if (! acct) {
- std::cerr << "Error: Unknown account name: " << argv[optind - 1]
+ std::cerr << "Error: Unknown account name: " << acct_name
<< std::endl;
return;
}
- // Compile the list of specified regular expressions, which can be
- // specified on the command line, or using an include/exclude file
-
- for (; optind < argc; optind++)
- record_regexp(argv[optind], regexps);
-
// Walk through all of the ledger entries, printing their register
// formatted equivalent
totals balance;
- for (entries_iterator i = main_ledger.entries.begin();
- i != main_ledger.entries.end();
+ for (entries_list_iterator i = main_ledger->entries.begin();
+ i != main_ledger->entries.end();
i++) {
if (! (*i)->matches(regexps))
continue;