Moved definition of virtual accounts into the ledger data file

itself.  They are now called "automated transactions".  Some
rearchitecting.
This commit is contained in:
John Wiegley 2003-10-04 07:48:21 +00:00
parent cb9de0d695
commit 2c10922614
10 changed files with 494 additions and 540 deletions

View file

@ -1,3 +1,7 @@
define GNUCASH
true
endef
CODE = amount.cc \ CODE = amount.cc \
ledger.cc \ ledger.cc \
parse.cc \ parse.cc \
@ -8,9 +12,9 @@ CODE = amount.cc \
OBJS = $(patsubst %.cc,%.o,$(CODE)) OBJS = $(patsubst %.cc,%.o,$(CODE))
CFLAGS = -Wall -ansi -pedantic # -DDEBUG=1 CFLAGS = -Wall -ansi -pedantic
DFLAGS = -O3 -fomit-frame-pointer -mcpu=pentium #DFLAGS = -O3 -fomit-frame-pointer -mcpu=pentium
#DFLAGS = -g DFLAGS = -g -DDEBUG=1
INCS = INCS =
LIBS = -lgmpxx -lgmp -lpcre LIBS = -lgmpxx -lgmp -lpcre

View file

@ -208,8 +208,8 @@ amount * gmp_amount::street() const
for (int cycles = 0; cycles < 10; cycles++) { for (int cycles = 0; cycles < 10; cycles++) {
totals::iterator pi = totals::iterator pi =
main_ledger.prices.amounts.find(amt->comm_symbol()); main_ledger->prices.amounts.find(amt->comm_symbol());
if (pi == main_ledger.prices.amounts.end()) { if (pi == main_ledger->prices.amounts.end()) {
using namespace std; using namespace std;
if (! get_quotes) if (! get_quotes)
@ -231,7 +231,7 @@ amount * gmp_amount::street() const
char * p = strchr(buf, '\n'); char * p = strchr(buf, '\n');
if (p) *p = '\0'; 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; continue;
} }
break; break;
@ -347,10 +347,9 @@ static std::string amount_to_str(const commodity * comm, const mpz_t val,
else if (! comm->thousands) else if (! comm->thousands)
s << quotient; s << quotient;
else { else {
// jww (2003-09-29): use a smarter starting value
bool printed = false; bool printed = false;
// jww (2003-09-29): use a smarter starting value for `powers'
for (int powers = 27; powers >= 0; powers -= 3) { for (int powers = 27; powers >= 0; powers -= 3) {
mpz_ui_pow_ui(divisor, 10, powers); mpz_ui_pow_ui(divisor, 10, powers);
mpz_tdiv_q(temp, quotient, divisor); mpz_tdiv_q(temp, quotient, divisor);
@ -554,8 +553,9 @@ static commodity * parse_amount(mpz_t out, const char * num,
commodity * comm = NULL; commodity * comm = NULL;
if (saw_commodity) { if (saw_commodity) {
commodities_iterator item = main_ledger.commodities.find(symbol.c_str()); commodities_map_iterator item =
if (item == main_ledger.commodities.end()) { main_ledger->commodities.find(symbol.c_str());
if (item == main_ledger->commodities.end()) {
comm = new commodity(symbol, prefix, separate, comm = new commodity(symbol, prefix, separate,
thousands, european, precision); thousands, european, precision);
} else { } else {

View file

@ -5,17 +5,17 @@
namespace ledger { namespace ledger {
extern bool show_cleared; 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 std::time_t begin_date;
extern bool have_beginning; extern bool have_beginning;
extern std::time_t end_date; extern std::time_t end_date;
extern bool have_ending; 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, static void display_total(std::ostream& out, totals& balance,
account * acct, bool top_level) account * acct, bool top_level)
{ {
@ -26,7 +26,7 @@ static void display_total(std::ostream& out, totals& balance,
displayed = true; displayed = true;
acct->balance.print(out, 20); acct->balance.print(out, 20);
if (! no_subtotals && top_level) if (show_subtotals && top_level)
balance.credit(acct->balance); balance.credit(acct->balance);
if (acct->parent && ! full_names && ! top_level) { 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 // 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 != acct->children.end();
i++) i++)
display_total(out, balance, (*i).second, ! displayed); display_total(out, balance, (*i).second, ! displayed);
@ -51,37 +51,13 @@ static void display_total(std::ostream& out, totals& balance,
// Balance reporting code // Balance reporting code
// //
void report_balances(int argc, char ** argv, regexps_t& regexps, void report_balances(std::ostream& out, regexps_map& regexps)
std::ostream& out)
{ {
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 // Walk through all of the ledger entries, computing the account
// totals // totals
for (entries_iterator i = main_ledger.entries.begin(); for (entries_list_iterator i = main_ledger->entries.begin();
i != main_ledger.entries.end(); i != main_ledger->entries.end();
i++) { i++) {
if ((have_beginning && difftime((*i)->date, begin_date) < 0) || if ((have_beginning && difftime((*i)->date, begin_date) < 0) ||
(have_ending && difftime((*i)->date, end_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(); for (std::list<transaction *>::iterator x = (*i)->xacts.begin();
x != (*i)->xacts.end(); x != (*i)->xacts.end();
x++) { x++) {
if (! show_virtual && (*x)->is_virtual)
continue;
for (account * acct = (*x)->acct; for (account * acct = (*x)->acct;
acct; acct;
acct = no_subtotals ? NULL : acct->parent) { acct = show_subtotals ? acct->parent : NULL) {
if (acct->checked == 0) { if (acct->checked == 0) {
if (regexps.empty()) { if (regexps.empty()) {
if (! (show_children || ! acct->parent)) if (! (show_children || ! acct->parent))
@ -131,14 +110,14 @@ void report_balances(int argc, char ** argv, regexps_t& regexps,
totals balance; totals balance;
for (accounts_iterator i = main_ledger.accounts.begin(); for (accounts_map_iterator i = main_ledger->accounts.begin();
i != main_ledger.accounts.end(); i != main_ledger->accounts.end();
i++) i++)
display_total(out, balance, (*i).second, true); display_total(out, balance, (*i).second, true);
// Print the total of all the balances shown // Print the total of all the balances shown
if (! no_subtotals && ! balance.is_zero()) { if (show_subtotals && ! balance.is_zero()) {
out << "--------------------" << std::endl; out << "--------------------" << std::endl;
balance.print(out, 20); balance.print(out, 20);
out << std::endl; out << std::endl;

View file

@ -2,7 +2,7 @@
namespace ledger { namespace ledger {
static void equity_entry(account * acct, regexps_t& regexps, static void equity_entry(account * acct, regexps_map& regexps,
std::ostream& out) std::ostream& out)
{ {
if (! acct->balance.is_zero() && if (! acct->balance.is_zero() &&
@ -27,7 +27,7 @@ static void equity_entry(account * acct, regexps_t& regexps,
opening.xacts.push_back(xact); opening.xacts.push_back(xact);
xact = new transaction(); 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 = (*i).second->street();
xact->cost->negate(); xact->cost->negate();
opening.xacts.push_back(xact); opening.xacts.push_back(xact);
@ -38,7 +38,7 @@ static void equity_entry(account * acct, regexps_t& regexps,
// Display balances for all child accounts // 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 != acct->children.end();
i++) i++)
equity_entry((*i).second, regexps, out); equity_entry((*i).second, regexps, out);
@ -51,23 +51,14 @@ static void equity_entry(account * acct, regexps_t& regexps,
// balances. // balances.
// //
void equity_ledger(int argc, char ** argv, regexps_t& regexps, void equity_ledger(std::ostream& out, regexps_map& regexps)
std::ostream& out)
{ {
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 // The account have their current totals already generated as a
// result of parsing. We just have to output those values. // result of parsing. We just have to output those values.
// totals // totals
for (accounts_iterator i = main_ledger.accounts.begin(); for (accounts_map_iterator i = main_ledger->accounts.begin();
i != main_ledger.accounts.end(); i != main_ledger->accounts.end();
i++) i++)
equity_entry((*i).second, regexps, out); equity_entry((*i).second, regexps, out);
} }

View file

@ -9,16 +9,15 @@ extern "C" {
namespace ledger { namespace ledger {
static account * curr_account; static account * curr_account;
static std::string curr_account_id; static std::string curr_account_id;
static entry * curr_entry; static entry * curr_entry;
static commodity * entry_comm; static commodity * entry_comm;
static commodity * curr_comm; static commodity * curr_comm;
static amount * curr_value; static amount * curr_value;
static std::string curr_quant; static std::string curr_quant;
static XML_Parser current_parser; static XML_Parser current_parser;
static bool do_compute; static accounts_map accounts_by_id;
static accounts_t accounts_by_id;
static enum { static enum {
NO_ACTION, NO_ACTION,
@ -98,21 +97,21 @@ static void endElement(void *userData, const char *name)
if (std::strcmp(name, "gnc:account") == 0) { if (std::strcmp(name, "gnc:account") == 0) {
assert(curr_account); assert(curr_account);
if (! curr_account->parent) if (! curr_account->parent)
main_ledger.accounts.insert(accounts_entry(curr_account->name, main_ledger->accounts.insert(accounts_map_pair(curr_account->name,
curr_account)); curr_account));
accounts_by_id.insert(accounts_entry(curr_account_id, curr_account)); accounts_by_id.insert(accounts_map_pair(curr_account_id, curr_account));
curr_account = NULL; curr_account = NULL;
} }
else if (std::strcmp(name, "gnc:commodity") == 0) { else if (std::strcmp(name, "gnc:commodity") == 0) {
assert(curr_comm); assert(curr_comm);
main_ledger.commodities.insert(commodities_entry(curr_comm->symbol, main_ledger->commodities.insert(commodities_map_pair(curr_comm->symbol,
curr_comm)); curr_comm));
curr_comm = NULL; curr_comm = NULL;
} }
else if (std::strcmp(name, "gnc:transaction") == 0) { else if (std::strcmp(name, "gnc:transaction") == 0) {
assert(curr_entry); assert(curr_entry);
assert(curr_entry->validate()); assert(curr_entry->validate());
main_ledger.entries.push_back(curr_entry); main_ledger->entries.push_back(curr_entry);
curr_entry = NULL; curr_entry = NULL;
} }
action = NO_ACTION; action = NO_ACTION;
@ -130,11 +129,11 @@ static void dataHandler(void *userData, const char *s, int len)
break; break;
case ACCOUNT_PARENT: { 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()); assert(i != accounts_by_id.end());
curr_account->parent = (*i).second; curr_account->parent = (*i).second;
(*i).second->children.insert(accounts_entry(curr_account->name, (*i).second->children.insert(accounts_map_pair(curr_account->name,
curr_account)); curr_account));
break; break;
} }
@ -142,9 +141,9 @@ static void dataHandler(void *userData, const char *s, int len)
if (curr_comm) if (curr_comm)
curr_comm->symbol = std::string(s, len); curr_comm->symbol = std::string(s, len);
else if (curr_account) 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) else if (curr_entry)
entry_comm = main_ledger.commodities[std::string(s, len)]; entry_comm = main_ledger->commodities[std::string(s, len)];
break; break;
case COMM_NAME: case COMM_NAME:
@ -186,7 +185,7 @@ static void dataHandler(void *userData, const char *s, int len)
break; break;
case XACT_ACCOUNT: { 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()) { if (i == accounts_by_id.end()) {
std::cerr << "Could not find account " << std::string(s, len) std::cerr << "Could not find account " << std::string(s, len)
<< std::endl; << std::endl;
@ -208,7 +207,7 @@ static void dataHandler(void *userData, const char *s, int len)
if (curr_value) if (curr_value)
delete curr_value; delete curr_value;
if (do_compute) if (main_ledger->compute_balances)
xact->acct->balance.credit(xact->cost); xact->acct->balance.credit(xact->cost);
break; 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]; char buf[BUFSIZ];
state * ledger = new state;
main_ledger = ledger;
ledger->compute_balances = compute_balances;
action = NO_ACTION; action = NO_ACTION;
do_compute = compute_balances;
curr_account = NULL; curr_account = NULL;
curr_entry = NULL; curr_entry = NULL;
curr_value = 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 // GnuCash uses the USD commodity without defining it, which really
// means to use $. // means to use $.
commodity * usd = new commodity("$", true, false, true, false, 2); 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); XML_Parser parser = XML_ParserCreate(NULL);
current_parser = parser; current_parser = parser;
@ -258,7 +261,7 @@ bool parse_gnucash(std::istream& in, bool compute_balances)
std::cerr << XML_ErrorString(XML_GetErrorCode(parser)) std::cerr << XML_ErrorString(XML_GetErrorCode(parser))
<< " at line " << XML_GetCurrentLineNumber(parser) << " at line " << XML_GetCurrentLineNumber(parser)
<< std::endl; << std::endl;
return false; return NULL;
} }
} }
XML_ParserFree(parser); XML_ParserFree(parser);
@ -267,7 +270,7 @@ bool parse_gnucash(std::istream& in, bool compute_balances)
curr_account_id.clear(); curr_account_id.clear();
curr_quant.clear(); curr_quant.clear();
return true; return ledger;
} }
} // namespace ledger } // namespace ledger

View file

@ -4,10 +4,8 @@
namespace ledger { namespace ledger {
bool use_warnings = false; bool use_warnings = false;
state main_ledger; state * main_ledger;
std::list<mask> regexps;
const std::string transaction::acct_as_str() const 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 // jww (2003-10-03): If we are shortcutting, don't print the
// "per-unit price" of a commodity, if it is not necessary. // "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; out << std::endl;
@ -98,8 +96,7 @@ bool entry::validate(bool show_unaccounted) const
for (std::list<transaction *>::const_iterator x = xacts.begin(); for (std::list<transaction *>::const_iterator x = xacts.begin();
x != xacts.end(); x != xacts.end();
x++) x++)
if ((*x)->cost && (*x)->must_balance && if ((*x)->cost && (*x)->must_balance)
(! (*x)->is_virtual || main_ledger.compute_virtual))
balance.credit((*x)->cost->value()); balance.credit((*x)->cost->value());
if (show_unaccounted && ! balance.is_zero()) { if (show_unaccounted && ! balance.is_zero()) {
@ -132,16 +129,12 @@ bool entry::matches(const std::list<mask>& regexps) const
} }
} }
#ifdef DO_CLEANUP
totals::~totals() totals::~totals()
{ {
for (iterator i = amounts.begin(); i != amounts.end(); i++) for (iterator i = amounts.begin(); i != amounts.end(); i++)
delete (*i).second; delete (*i).second;
} }
#endif // DO_CLEANUP
void totals::credit(const totals& other) void totals::credit(const totals& other)
{ {
for (const_iterator i = other.amounts.begin(); 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. // Print out the entire ledger that was read in, sorted by date.
// This can be used to "wash" ugly ledger files. // This can be used to "wash" ugly ledger files.
void print_ledger(int argc, char ** argv, regexps_t& regexps, void state::print(std::ostream& out, regexps_map& regexps,
std::ostream& out) bool shortcut) const
{ {
bool use_shortcuts = true; for (entries_list_const_iterator i = entries.begin();
i != entries.end();
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();
i++) i++)
if ((*i)->matches(regexps)) if ((*i)->matches(regexps))
(*i)->print(out, use_shortcuts); (*i)->print(out, shortcut);
} }
mask::mask(const std::string& pat) : exclude(false) mask::mask(const std::string& pat) : exclude(false)
@ -236,12 +207,7 @@ mask::mask(const std::string& pat) : exclude(false)
<< std::endl; << std::endl;
} }
void record_regexp(const std::string& pattern, regexps_t& regexps) void read_regexps(const std::string& path, regexps_map& regexps)
{
regexps.push_back(mask(pattern));
}
void read_regexps(const std::string& path, regexps_t& regexps)
{ {
if (access(path.c_str(), R_OK) != -1) { if (access(path.c_str(), R_OK) != -1) {
std::ifstream file(path.c_str()); std::ifstream file(path.c_str());
@ -250,12 +216,12 @@ void read_regexps(const std::string& path, regexps_t& regexps)
char buf[80]; char buf[80];
file.getline(buf, 79); file.getline(buf, 79);
if (*buf && ! std::isspace(*buf)) 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) bool * by_exclusion)
{ {
assert(! regexps.empty()); assert(! regexps.empty());
@ -285,28 +251,24 @@ bool matches(const regexps_t& regexps, const std::string& str,
return match; return match;
} }
#ifdef DO_CLEANUP
state::~state() state::~state()
{ {
for (commodities_iterator i = commodities.begin(); for (commodities_map_iterator i = commodities.begin();
i != commodities.end(); i != commodities.end();
i++) i++)
delete (*i).second; delete (*i).second;
for (accounts_iterator i = accounts.begin(); for (accounts_map_iterator i = accounts.begin();
i != accounts.end(); i != accounts.end();
i++) i++)
delete (*i).second; delete (*i).second;
for (entries_iterator i = entries.begin(); for (entries_list_iterator i = entries.begin();
i != entries.end(); i != entries.end();
i++) i++)
delete *i; delete *i;
} }
#endif // DO_CLEANUP
void state::record_price(const std::string& setting) void state::record_price(const std::string& setting)
{ {
char buf[128]; 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) 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()) if (i != accounts_cache.end())
return (*i).second; return (*i).second;
@ -338,26 +300,26 @@ account * state::find_account(const std::string& name, bool create)
tok; tok;
tok = std::strtok(NULL, ":")) { tok = std::strtok(NULL, ":")) {
if (! current) { if (! current) {
accounts_iterator i = accounts.find(tok); accounts_map_iterator i = accounts.find(tok);
if (i == accounts.end()) { if (i == accounts.end()) {
if (! create) { if (! create) {
delete[] buf; delete[] buf;
return NULL; return NULL;
} }
current = new account(tok); current = new account(tok);
accounts.insert(accounts_entry(tok, current)); accounts.insert(accounts_map_pair(tok, current));
} else { } else {
current = (*i).second; current = (*i).second;
} }
} else { } else {
accounts_iterator i = current->children.find(tok); accounts_map_iterator i = current->children.find(tok);
if (i == current->children.end()) { if (i == current->children.end()) {
if (! create) { if (! create) {
delete[] buf; delete[] buf;
return NULL; return NULL;
} }
current = new account(tok, current); current = new account(tok, current);
current->parent->children.insert(accounts_entry(tok, current)); current->parent->children.insert(accounts_map_pair(tok, current));
} else { } else {
current = (*i).second; current = (*i).second;
} }
@ -367,7 +329,7 @@ account * state::find_account(const std::string& name, bool create)
delete[] buf; delete[] buf;
if (current) if (current)
accounts_cache.insert(accounts_entry(name, current)); accounts_cache.insert(accounts_map_pair(name, current));
return current; return current;
} }

View file

@ -1,5 +1,5 @@
#ifndef _LEDGER_H #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); bool thou = true, bool euro = false, int prec = 2);
}; };
typedef std::map<const std::string, commodity *> commodities_t; typedef std::map<const std::string, commodity *> commodities_map;
typedef commodities_t::iterator commodities_iterator; typedef commodities_map::iterator commodities_map_iterator;
typedef std::pair<const std::string, commodity *> commodities_entry; typedef std::pair<const std::string, commodity *> commodities_map_pair;
class amount class amount
@ -90,11 +90,11 @@ struct mask
mask(const std::string& pattern); 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 record_regexp(const std::string& pattern, regexps_map& regexps);
void read_regexps(const std::string& path, regexps_t& regexps); void read_regexps(const std::string& path, regexps_map& regexps);
bool matches(const regexps_t& regexps, const std::string& str, bool matches(const regexps_map& regexps, const std::string& str,
bool * by_exclusion = NULL); bool * by_exclusion = NULL);
@ -114,12 +114,10 @@ struct transaction
: acct(_acct), cost(_cost), : acct(_acct), cost(_cost),
is_virtual(false), must_balance(true), specified(false) {} is_virtual(false), must_balance(true), specified(false) {}
#ifdef DO_CLEANUP
~transaction() { ~transaction() {
if (cost) if (cost)
delete cost; delete cost;
} }
#endif
const std::string acct_as_str() const; const std::string acct_as_str() const;
@ -140,7 +138,6 @@ struct entry
entry() : cleared(false) {} entry() : cleared(false) {}
#ifdef DO_CLEANUP
// If we're running as a command-line tool, it's cheaper to just // 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 // throw away the heap on exit, than spend time freeing things up
// like a good citizen. // like a good citizen.
@ -152,7 +149,6 @@ struct entry
delete *i; delete *i;
} }
} }
#endif
bool matches(const std::list<mask>& regexps) const; bool matches(const std::list<mask>& regexps) const;
bool validate(bool show_unaccounted = false) const; bool validate(bool show_unaccounted = false) const;
@ -166,22 +162,21 @@ struct cmp_entry_date {
} }
}; };
typedef std::vector<entry *> entries_t; typedef std::vector<entry *> entries_list;
typedef entries_t::iterator entries_iterator; typedef entries_list::iterator entries_list_iterator;
typedef entries_list::const_iterator entries_list_const_iterator;
struct totals struct totals
{ {
typedef std::map<const std::string, amount *> map; typedef std::map<const std::string, amount *> map;
typedef map::iterator iterator; typedef map::iterator iterator;
typedef map::const_iterator const_iterator; typedef map::const_iterator const_iterator;
typedef std::pair<const std::string, amount *> pair; typedef std::pair<const std::string, amount *> pair;
map amounts; map amounts;
#ifdef DO_CLEANUP
~totals(); ~totals();
#endif
void credit(const amount * val) { void credit(const amount * val) {
std::pair<iterator, bool> result = std::pair<iterator, bool> result =
@ -202,9 +197,9 @@ struct totals
}; };
typedef std::map<const std::string, account *> accounts_t; typedef std::map<const std::string, account *> accounts_map;
typedef accounts_t::iterator accounts_iterator; typedef accounts_map::iterator accounts_map_iterator;
typedef std::pair<const std::string, account *> accounts_entry; typedef std::pair<const std::string, account *> accounts_map_pair;
struct account struct account
{ {
@ -216,7 +211,7 @@ struct account
#endif #endif
totals balance; // optional, parse-time computed balance totals balance; // optional, parse-time computed balance
int checked; // 'balance' uses this for speed's sake int checked; // 'balance' uses this for speed's sake
accounts_t children; accounts_map children;
mutable std::string full_name; mutable std::string full_name;
@ -238,11 +233,12 @@ struct account
struct state struct state
{ {
commodities_t commodities; commodities_map commodities;
accounts_t accounts; accounts_map accounts;
accounts_t accounts_cache; // maps full names to accounts accounts_map accounts_cache; // maps full names to accounts
entries_t entries; entries_list entries;
totals prices; totals prices;
int current_year;
typedef std::map<std::list<mask> *, typedef std::map<std::list<mask> *,
std::list<transaction *> *> virtual_map; std::list<transaction *> *> virtual_map;
@ -252,30 +248,31 @@ struct state
typedef virtual_map::const_iterator virtual_map_iterator; typedef virtual_map::const_iterator virtual_map_iterator;
std::string mapping_file; bool compute_balances;
virtual_map virtual_mapping; virtual_map virtual_mapping;
bool compute_virtual;
state() : mapping_file(".mapping"), compute_virtual(true) {}
#ifdef DO_CLEANUP
~state(); ~state();
#endif
void record_price(const std::string& setting); 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); account * find_account(const std::string& name, bool create = true);
}; };
extern state main_ledger; extern state * main_ledger;
extern bool use_warnings; extern bool use_warnings;
inline commodity::commodity(const std::string& sym, bool pre, bool sep, inline commodity::commodity(const std::string& sym, bool pre, bool sep,
bool thou, bool euro, int prec) bool thou, bool euro, int prec)
: symbol(sym), prefix(pre), separate(sep), : symbol(sym), prefix(pre), separate(sep),
thousands(thou), european(euro), precision(prec) { thousands(thou), european(euro), precision(prec) {
std::pair<commodities_iterator, bool> result = std::pair<commodities_map_iterator, bool> result =
main_ledger.commodities.insert(commodities_entry(sym, this)); main_ledger->commodities.insert(commodities_map_pair(sym, this));
assert(result.second); assert(result.second);
} }

126
main.cc
View file

@ -1,27 +1,31 @@
#include "ledger.h" #include "ledger.h"
#define LEDGER_VERSION "1.1"
#include <fstream> #include <fstream>
namespace ledger { namespace ledger {
extern bool parse_ledger(std::istream& in, bool compute_balances); extern state * parse_ledger(std::istream& in, regexps_map& regexps,
extern void parse_virtual_mappings(const std::string& path); bool compute_balances);
extern bool parse_date(const std::string& date_str, std::time_t * result,
const int year = -1);
#ifdef READ_GNUCASH #ifdef READ_GNUCASH
extern bool parse_gnucash(std::istream& in, bool compute_balances); extern state * parse_gnucash(std::istream& in, bool compute_balances);
#endif #endif
extern void report_balances(int argc, char ** argv, regexps_t& regexps, extern bool parse_date(const char * date_str, std::time_t * result,
std::ostream& out); const int year = -1);
extern void print_register(int argc, char ** argv, regexps_t& regexps,
std::ostream& out); extern void report_balances(std::ostream& out, regexps_map& regexps);
extern void print_ledger(int argc, char ** argv, regexps_t& regexps, extern void print_register(const std::string& acct_name, std::ostream& out,
std::ostream& out); regexps_map& regexps);
extern void equity_ledger(int argc, char ** argv, regexps_t& regexps, extern void equity_ledger(std::ostream& out, regexps_map& regexps);
std::ostream& out);
bool show_cleared; bool show_cleared;
bool show_virtual;
bool get_quotes; bool get_quotes;
bool show_children;
bool show_empty;
bool show_subtotals;
bool full_names;
std::time_t begin_date; std::time_t begin_date;
bool have_beginning; bool have_beginning;
@ -76,20 +80,22 @@ static void show_help(std::ostream& out)
int main(int argc, char * argv[]) int main(int argc, char * argv[])
{ {
std::istream * file = NULL; std::istream * file = NULL;
regexps_t regexps;
have_beginning = false; regexps_map regexps;
have_ending = false;
show_cleared = false;
const char * p = std::getenv("MAPPINGS"); have_beginning = false;
if (p) have_ending = false;
main_ledger.mapping_file = p; show_cleared = false;
show_virtual = true;
show_children = false;
show_empty = false;
show_subtotals = true;
full_names = false;
// Parse the command-line options // Parse the command-line options
int c; 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)) { switch (char(c)) {
case 'b': case 'b':
case 'e': { case 'e': {
@ -164,12 +170,16 @@ int main(int argc, char * argv[])
have_ending = true; have_ending = true;
break; break;
case 'C': show_cleared = true; break;
case 'h': show_help(std::cout); break; case 'h': show_help(std::cout); break;
case 'R': main_ledger.compute_virtual = false; break; case 'f': file = new std::ifstream(optarg); break;
case 'V': main_ledger.mapping_file = optarg; break;
case 'w': use_warnings = true; break; case 'C': show_cleared = true; break;
case 'f': file = new std::ifstream(optarg); 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 // -i path-to-file-of-regexps
case 'i': case 'i':
@ -186,10 +196,10 @@ int main(int argc, char * argv[])
char buf[80]; char buf[80];
pricedb.getline(buf, 79); pricedb.getline(buf, 79);
if (*buf && ! std::isspace(*buf)) if (*buf && ! std::isspace(*buf))
main_ledger.record_price(buf); main_ledger->record_price(buf);
} }
} else { } else {
main_ledger.record_price(optarg); main_ledger->record_price(optarg);
} }
break; break;
@ -199,7 +209,7 @@ int main(int argc, char * argv[])
case 'v': case 'v':
std::cout std::cout
<< "Ledger Accouting Tool 1.0" << std::endl << "Ledger Accouting Tool " LEDGER_VERSION << std::endl
<< " Copyright (c) 2003 John Wiegley <johnw@newartisans.com>" << " Copyright (c) 2003 John Wiegley <johnw@newartisans.com>"
<< std::endl << std::endl << std::endl << std::endl
<< "This program is made available under the terms of the BSD" << "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); file = new std::ifstream(p);
if (! file || ! *file) { 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; << std::endl;
return 1; return 1;
} }
@ -250,13 +261,17 @@ int main(int argc, char * argv[])
// Read the command word // Read the command word
const std::string command = argv[optind]; const std::string command = argv[optind++];
// Parse any virtual mappings being used int optind_begin = optind;
if (command == "register") {
if (main_ledger.compute_virtual && if (optind == argc) {
access(main_ledger.mapping_file.c_str(), R_OK) >= 0) std::cerr << ("Error: Must specify an account name "
parse_virtual_mappings(main_ledger.mapping_file); "after the 'register' command.") << std::endl;
return 1;
}
optind++;
}
// Parse the ledger // Parse the ledger
@ -266,23 +281,42 @@ int main(int argc, char * argv[])
file->seekg(0); file->seekg(0);
if (std::strncmp(buf, "<?xml version=\"1.0\"?>", 21) == 0) if (std::strncmp(buf, "<?xml version=\"1.0\"?>", 21) == 0)
parse_gnucash(*file, command == "equity"); main_ledger = parse_gnucash(*file, command == "equity");
else else
#endif #endif
parse_ledger(*file, command == "equity"); main_ledger = parse_ledger(*file, regexps, command == "equity");
delete file; 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 // Process the command
if (command == "balance") if (command == "balance") {
report_balances(argc - optind, &argv[optind], regexps, std::cout); report_balances(std::cout, regexps);
else if (command == "register") }
print_register(argc - optind, &argv[optind], regexps, std::cout); else if (command == "register") {
else if (command == "print") print_register(argv[optind_begin], std::cout, regexps);
print_ledger(argc - optind, &argv[optind], regexps, std::cout); }
else if (command == "equity") else if (command == "print") {
equity_ledger(argc - optind, &argv[optind], regexps, std::cout); 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. // main.cc ends here.

570
parse.cc
View file

@ -7,28 +7,34 @@
namespace ledger { 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) static inline char * next_element(char * buf, bool variable = false)
{ {
char * p; char * p;
// Convert any tabs to spaces, for simplicity's sake if (variable) {
for (p = buf; *p; p++) // Convert any tabs to spaces, for simplicity's sake
if (*p == '\t') for (p = buf; *p; p++)
*p = ' '; if (*p == '\t')
*p = ' ';
if (variable)
p = std::strstr(buf, " "); p = std::strstr(buf, " ");
else } else {
p = std::strchr(buf, ' '); p = std::strchr(buf, ' ');
}
if (! p) if (! p)
return NULL; return NULL;
*p++ = '\0'; *p++ = '\0';
while (std::isspace(*p))
p++;
return p; return skip_ws(p);
} }
static const char *formats[] = { static const char *formats[] = {
@ -44,17 +50,17 @@ static const char *formats[] = {
NULL 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) const int year = -1)
{ {
struct std::tm when; 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); struct std::tm * now_tm = std::localtime(&now);
for (const char ** f = formats; *f; f++) { for (const char ** f = formats; *f; f++) {
memset(&when, INT_MAX, sizeof(struct std::tm)); 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_hour = 0;
when.tm_min = 0; when.tm_min = 0;
when.tm_sec = 0; when.tm_sec = 0;
@ -78,14 +84,130 @@ bool parse_date(const std::string& date_str, std::time_t * result,
return false; return false;
} }
static int linenum = 0; #define MAX_LINE 1024
static void finalize_entry(entry * curr, bool compute_balances) static int linenum;
transaction * parse_transaction(std::istream& in, state * ledger)
{ {
assert(curr); transaction * xact = new transaction();
assert(! curr->xacts.empty());
// Scan through and compute the total balance for the entry. 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);
if (ledger->compute_balances && xact->cost)
xact->acct->balance.credit(xact->cost);
return xact;
}
entry * parse_entry(std::istream& in, state * ledger)
{
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;
}
// 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; totals balance;
@ -139,10 +261,10 @@ static void finalize_entry(entry * curr, bool compute_balances)
if (! empty_allowed || balance.amounts.empty() || if (! empty_allowed || balance.amounts.empty() ||
balance.amounts.size() != 1) { balance.amounts.size() != 1) {
std::cerr << "Error, line " << (linenum - 1) std::cerr << "Error, line " << linenum
<< ": Transaction entry is lacking an amount." << ": Transaction entry is lacking an amount."
<< std::endl; << std::endl;
return; return NULL;
} }
empty_allowed = false; empty_allowed = false;
@ -154,7 +276,7 @@ static void finalize_entry(entry * curr, bool compute_balances)
(*x)->cost = (*i).second->value(); (*x)->cost = (*i).second->value();
(*x)->cost->negate(); (*x)->cost->negate();
if (compute_balances) if (ledger->compute_balances)
(*x)->acct->balance.credit((*x)->cost); (*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 // transactions and create new virtual transactions for all that
// apply. // apply.
if (main_ledger.compute_virtual) { for (state::virtual_map_iterator m = ledger->virtual_mapping.begin();
for (state::virtual_map_iterator m = main_ledger.virtual_mapping.begin(); m != ledger->virtual_mapping.end();
m != main_ledger.virtual_mapping.end(); m++) {
m++) { std::list<transaction *> xacts;
std::list<transaction *> xacts;
for (std::list<transaction *>::iterator x = curr->xacts.begin(); for (std::list<transaction *>::iterator x = curr->xacts.begin();
x != curr->xacts.end(); x != curr->xacts.end();
x++) { x++) {
if ((*x)->is_virtual || if ((*x)->is_virtual ||
! ledger::matches(*((*m).first), (*x)->acct->as_str())) ! ledger::matches(*((*m).first), (*x)->acct->as_str()))
continue; continue;
for (std::list<transaction *>::iterator i = (*m).second->begin(); for (std::list<transaction *>::iterator i = (*m).second->begin();
i != (*m).second->end(); i != (*m).second->end();
i++) { i++) {
transaction * t; transaction * t;
assert((*i)->is_virtual); if ((*i)->cost->comm()) {
assert((*i)->cost); 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 ((*i)->cost->comm()) { t->is_virtual = (*i)->is_virtual;
t = new transaction((*i)->acct, (*i)->cost); t->must_balance = (*i)->must_balance;
} else {
amount * temp = (*x)->cost->value(); // If there is already a virtual transaction for the
t = new transaction((*i)->acct, temp->value((*i)->cost)); // account under consideration, and it's `must_balance'
delete temp; // 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;
} }
}
t->is_virtual = true; if (! added)
t->must_balance = (*i)->must_balance; for (std::list<transaction *>::iterator y = curr->xacts.begin();
y != curr->xacts.end();
// 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++) { y++) {
if ((*y)->is_virtual && (*y)->acct == t->acct && if ((*y)->is_virtual && (*y)->acct == t->acct &&
(*y)->must_balance == t->must_balance) { (*y)->must_balance == t->must_balance) {
@ -213,42 +344,28 @@ static void finalize_entry(entry * curr, bool compute_balances)
} }
} }
if (! added) if (! added)
for (std::list<transaction *>::iterator y = curr->xacts.begin(); xacts.push_back(t);
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 // Add to the current entry any virtual transactions which were
// created. We have to do this afterward, otherwise the // created. We have to do this afterward, otherwise the
// iteration above is screwed up if we try adding new // iteration above is screwed up if we try adding new
// transactions during the traversal. // transactions during the traversal.
for (std::list<transaction *>::iterator x = xacts.begin(); for (std::list<transaction *>::iterator x = xacts.begin();
x != xacts.end(); x != xacts.end();
x++) { x++) {
curr->xacts.push_back(*x); curr->xacts.push_back(*x);
if (compute_balances) if (ledger->compute_balances)
(*x)->acct->balance.credit((*x)->cost); (*x)->acct->balance.credit((*x)->cost);
}
} }
} }
// Compute the balances again, just to make sure it all comes out // 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()) { if (! curr->validate()) {
std::cerr << "Error, line " << (linenum - 1) std::cerr << "Error, line " << (linenum - 1)
@ -256,12 +373,53 @@ static void finalize_entry(entry * curr, bool compute_balances)
<< std::endl; << std::endl;
curr->print(std::cerr); curr->print(std::cerr);
curr->validate(true); curr->validate(true);
return; delete curr;
return NULL;
}
return curr;
}
void parse_automated_transactions(std::istream& in, state * ledger)
{
static char line[MAX_LINE + 1];
std::list<mask> * masks = NULL;
while (! in.eof() && in.peek() == '=') {
in.getline(line, MAX_LINE);
linenum++;
char * p = line + 1;
p = skip_ws(p);
if (! masks)
masks = new std::list<mask>;
masks->push_back(mask(p));
} }
// If it's OK, add it to the general ledger's list of entries. std::list<transaction *> * xacts = NULL;
main_ledger.entries.push_back(curr); while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) {
if (transaction * xact = parse_transaction(in, ledger)) {
if (! xacts)
xacts = new std::list<transaction *>;
if (! xact->cost) {
std::cerr << "Error, line " << (linenum - 1)
<< ": All automated transactions must have a value."
<< std::endl;
} else {
xacts->push_back(xact);
}
}
}
if (masks && xacts)
ledger->virtual_mapping.insert(state::virtual_map_pair(masks, xacts));
else if (masks)
delete masks;
else if (xacts)
delete xacts;
} }
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@ -269,217 +427,59 @@ static void finalize_entry(entry * curr, bool compute_balances)
// Ledger parser // Ledger parser
// //
bool parse_ledger(std::istream& in, bool compute_balances) state * parse_ledger(std::istream& in, regexps_map& regexps,
bool compute_balances)
{ {
char line[1024]; static char line[MAX_LINE + 1];
int current_year = -1; char c;
entry * curr = NULL;
state * ledger = new state;
main_ledger = ledger;
ledger->compute_balances = compute_balances;
linenum = 0;
while (! in.eof()) { while (! in.eof()) {
in.getline(line, 1023); switch (in.peek()) {
linenum++; case -1: // end of file
return ledger;
if (line[0] == '\n') { case '\n':
continue; linenum++;
} case '\r': // skip blank lines
else if (std::isdigit(line[0])) { in.get(c);
if (curr && ! curr->xacts.empty()) break;
finalize_entry(curr, compute_balances);
curr = new entry;
char * next = next_element(line); case 'Y': // set the current year
if (! parse_date(line, &curr->date, current_year)) { in >> c;
std::cerr << "Error, line " << linenum in >> ledger->current_year;
<< ": Failed to parse date: " << line << std::endl; break;
continue;
}
if (*next == '*') { case ';': // a comment line
curr->cleared = true; in.getline(line, MAX_LINE);
linenum++;
break;
next++; case '-':
while (std::isspace(*next)) case '+': // permanent regexps
next++; in.getline(line, MAX_LINE);
} linenum++;
if (*next == '(') { // Add the regexp to whatever masks currently exist
char * p = std::strchr(next, ')'); regexps.push_back(mask(line));
if (p) { break;
*p++ = '\0';
curr->code = next;
next = p;
next++; case '=': // automated transactions
while (std::isspace(*next)) parse_automated_transactions(in, ledger);
next++; break;
}
}
curr->desc = next; default:
} if (entry * ent = parse_entry(in, ledger))
else if (curr && std::isspace(line[0])) { ledger->entries.push_back(ent);
transaction * xact = new transaction(); break;
char * p = line;
while (std::isspace(*p))
p++;
// The call to `next_element' will skip past the account name,
// and return a pointer to the beginning of the amount. Once
// we know where the amount is, we can strip off any
// transaction note, and parse it.
char * cost_str = next_element(p, true);
char * note_str;
// If there is no amount given, it is intended as an implicit
// amount; we must use the opposite of the value of the
// preceding transaction.
if (! cost_str || ! *cost_str || *cost_str == ';') {
if (cost_str && *cost_str) {
while (*cost_str == ';' || std::isspace(*cost_str))
cost_str++;
xact->note = cost_str;
}
xact->cost = NULL;
}
else {
note_str = std::strchr(cost_str, ';');
if (note_str) {
*note_str++ = '\0';
while (std::isspace(*note_str))
note_str++;
xact->note = note_str;
}
for (char * t = cost_str + (std::strlen(cost_str) - 1);
std::isspace(*t);
t--)
*t = '\0';
xact->cost = create_amount(cost_str);
}
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';
}
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);
}
}
else if (line[0] == 'Y') {
current_year = std::atoi(line + 2);
} }
} }
return ledger;
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 } // namespace ledger

View file

@ -28,39 +28,23 @@ static std::string truncated(const std::string& str, int width)
// Register printing code // Register printing code
// //
void print_register(int argc, char ** argv, regexps_t& regexps, void print_register(const std::string& acct_name, std::ostream& out,
std::ostream& out) regexps_map& regexps)
{ {
optind = 1; account * acct = main_ledger->find_account(acct_name, false);
// 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);
if (! acct) { if (! acct) {
std::cerr << "Error: Unknown account name: " << argv[optind - 1] std::cerr << "Error: Unknown account name: " << acct_name
<< std::endl; << std::endl;
return; 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 // Walk through all of the ledger entries, printing their register
// formatted equivalent // formatted equivalent
totals balance; totals balance;
for (entries_iterator i = main_ledger.entries.begin(); for (entries_list_iterator i = main_ledger->entries.begin();
i != main_ledger.entries.end(); i != main_ledger->entries.end();
i++) { i++) {
if (! (*i)->matches(regexps)) if (! (*i)->matches(regexps))
continue; continue;