Moved definition of virtual accounts into the ledger data file
itself. They are now called "automated transactions". Some rearchitecting.
This commit is contained in:
parent
cb9de0d695
commit
2c10922614
10 changed files with 494 additions and 540 deletions
10
Makefile
10
Makefile
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
14
amount.cc
14
amount.cc
|
|
@ -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 {
|
||||||
|
|
|
||||||
55
balance.cc
55
balance.cc
|
|
@ -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;
|
||||||
|
|
|
||||||
21
equity.cc
21
equity.cc
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
59
gnucash.cc
59
gnucash.cc
|
|
@ -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
|
||||||
|
|
|
||||||
80
ledger.cc
80
ledger.cc
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
71
ledger.h
71
ledger.h
|
|
@ -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
126
main.cc
|
|
@ -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
570
parse.cc
|
|
@ -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
|
||||||
|
|
|
||||||
28
register.cc
28
register.cc
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue