*** empty log message ***
This commit is contained in:
parent
331f389cc6
commit
072d5131b4
9 changed files with 723 additions and 380 deletions
8
Makefile
8
Makefile
|
|
@ -2,13 +2,7 @@ define GNUCASH
|
||||||
true
|
true
|
||||||
endef
|
endef
|
||||||
|
|
||||||
CODE = amount.cc \
|
CODE = amount.cc ledger.cc parse.cc reports.cc
|
||||||
ledger.cc \
|
|
||||||
parse.cc \
|
|
||||||
balance.cc \
|
|
||||||
register.cc \
|
|
||||||
equity.cc \
|
|
||||||
main.cc
|
|
||||||
|
|
||||||
OBJS = $(patsubst %.cc,%.o,$(CODE))
|
OBJS = $(patsubst %.cc,%.o,$(CODE))
|
||||||
|
|
||||||
|
|
|
||||||
128
balance.cc
128
balance.cc
|
|
@ -1,128 +0,0 @@
|
||||||
#include "ledger.h"
|
|
||||||
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
namespace ledger {
|
|
||||||
|
|
||||||
extern bool show_cleared;
|
|
||||||
extern bool show_virtual;
|
|
||||||
extern bool show_children;
|
|
||||||
extern bool show_empty;
|
|
||||||
extern bool show_subtotals;
|
|
||||||
extern bool full_names;
|
|
||||||
extern bool get_quotes;
|
|
||||||
|
|
||||||
extern std::time_t begin_date;
|
|
||||||
extern bool have_beginning;
|
|
||||||
extern std::time_t end_date;
|
|
||||||
extern bool have_ending;
|
|
||||||
|
|
||||||
static void display_total(std::ostream& out, totals& balance,
|
|
||||||
account * acct, bool top_level)
|
|
||||||
{
|
|
||||||
bool displayed = false;
|
|
||||||
|
|
||||||
if (acct->checked == 1 &&
|
|
||||||
(show_empty || ! acct->balance.is_zero())) {
|
|
||||||
displayed = true;
|
|
||||||
|
|
||||||
acct->balance.print(out, 20);
|
|
||||||
if (show_subtotals && top_level)
|
|
||||||
balance.credit(acct->balance);
|
|
||||||
|
|
||||||
if (acct->parent && ! full_names && ! top_level) {
|
|
||||||
for (const account * a = acct; a; a = a->parent)
|
|
||||||
out << " ";
|
|
||||||
out << acct->name << std::endl;
|
|
||||||
} else {
|
|
||||||
out << " " << acct->as_str() << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display balances for all child accounts
|
|
||||||
|
|
||||||
for (accounts_map_iterator i = acct->children.begin();
|
|
||||||
i != acct->children.end();
|
|
||||||
i++)
|
|
||||||
display_total(out, balance, (*i).second, ! displayed);
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Balance reporting code
|
|
||||||
//
|
|
||||||
|
|
||||||
void report_balances(std::ostream& out, regexps_map& regexps)
|
|
||||||
{
|
|
||||||
// Walk through all of the ledger entries, computing the account
|
|
||||||
// totals
|
|
||||||
|
|
||||||
for (entries_list_iterator i = main_ledger->entries.begin();
|
|
||||||
i != main_ledger->entries.end();
|
|
||||||
i++) {
|
|
||||||
if ((have_beginning && difftime((*i)->date, begin_date) < 0) ||
|
|
||||||
(have_ending && difftime((*i)->date, end_date) >= 0) ||
|
|
||||||
(show_cleared && ! (*i)->cleared))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
for (std::list<transaction *>::iterator x = (*i)->xacts.begin();
|
|
||||||
x != (*i)->xacts.end();
|
|
||||||
x++) {
|
|
||||||
if (! show_virtual && (*x)->is_virtual)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
for (account * acct = (*x)->acct;
|
|
||||||
acct;
|
|
||||||
acct = show_subtotals ? acct->parent : NULL) {
|
|
||||||
if (acct->checked == 0) {
|
|
||||||
if (regexps.empty()) {
|
|
||||||
if (! (show_children || ! acct->parent))
|
|
||||||
acct->checked = 2;
|
|
||||||
else
|
|
||||||
acct->checked = 1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
bool by_exclusion;
|
|
||||||
bool match = matches(regexps, acct->as_str(),
|
|
||||||
&by_exclusion);
|
|
||||||
if (! match) {
|
|
||||||
acct->checked = 2;
|
|
||||||
}
|
|
||||||
else if (by_exclusion) {
|
|
||||||
if (! (show_children || ! acct->parent))
|
|
||||||
acct->checked = 2;
|
|
||||||
else
|
|
||||||
acct->checked = 1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
acct->checked = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (acct->checked == 1)
|
|
||||||
acct->balance.credit((*x)->cost->street(get_quotes));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Walk through all the top-level accounts, giving the balance
|
|
||||||
// report for each, and then for each of their children.
|
|
||||||
|
|
||||||
totals balance;
|
|
||||||
|
|
||||||
for (accounts_map_iterator i = main_ledger->accounts.begin();
|
|
||||||
i != main_ledger->accounts.end();
|
|
||||||
i++)
|
|
||||||
display_total(out, balance, (*i).second, true);
|
|
||||||
|
|
||||||
// Print the total of all the balances shown
|
|
||||||
|
|
||||||
if (show_subtotals && ! balance.is_zero()) {
|
|
||||||
out << "--------------------" << std::endl;
|
|
||||||
balance.print(out, 20);
|
|
||||||
out << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ledger
|
|
||||||
68
equity.cc
68
equity.cc
|
|
@ -1,68 +0,0 @@
|
||||||
#include "ledger.h"
|
|
||||||
|
|
||||||
namespace ledger {
|
|
||||||
|
|
||||||
extern bool get_quotes;
|
|
||||||
|
|
||||||
static void equity_entry(account * acct, regexps_map& regexps,
|
|
||||||
std::ostream& out)
|
|
||||||
{
|
|
||||||
if (! acct->balance.is_zero() &&
|
|
||||||
(regexps.empty() || matches(regexps, acct->as_str()))) {
|
|
||||||
entry opening;
|
|
||||||
|
|
||||||
opening.date = std::time(NULL);
|
|
||||||
opening.cleared = true;
|
|
||||||
opening.desc = "Opening Balance";
|
|
||||||
|
|
||||||
transaction * xact;
|
|
||||||
for (totals::const_iterator i = acct->balance.amounts.begin();
|
|
||||||
i != acct->balance.amounts.end();
|
|
||||||
i++) {
|
|
||||||
// Skip it, if there is a zero balance for the commodity
|
|
||||||
if ((*i).second->is_zero())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
xact = new transaction();
|
|
||||||
xact->acct = const_cast<account *>(acct);
|
|
||||||
xact->cost = (*i).second->street(get_quotes);
|
|
||||||
opening.xacts.push_back(xact);
|
|
||||||
|
|
||||||
xact = new transaction();
|
|
||||||
xact->acct = main_ledger->find_account("Equity:Opening Balances");
|
|
||||||
xact->cost = (*i).second->street(get_quotes);
|
|
||||||
xact->cost->negate();
|
|
||||||
opening.xacts.push_back(xact);
|
|
||||||
}
|
|
||||||
|
|
||||||
opening.print(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display balances for all child accounts
|
|
||||||
|
|
||||||
for (accounts_map_iterator i = acct->children.begin();
|
|
||||||
i != acct->children.end();
|
|
||||||
i++)
|
|
||||||
equity_entry((*i).second, regexps, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Create an Equity file based on a ledger. This is used for
|
|
||||||
// archiving past years, and starting out a new year with compiled
|
|
||||||
// balances.
|
|
||||||
//
|
|
||||||
|
|
||||||
void equity_ledger(std::ostream& out, regexps_map& regexps)
|
|
||||||
{
|
|
||||||
// The account have their current totals already generated as a
|
|
||||||
// result of parsing. We just have to output those values.
|
|
||||||
// totals
|
|
||||||
|
|
||||||
for (accounts_map_iterator i = main_ledger->accounts.begin();
|
|
||||||
i != main_ledger->accounts.end();
|
|
||||||
i++)
|
|
||||||
equity_entry((*i).second, regexps, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ledger
|
|
||||||
65
ledger.cc
65
ledger.cc
|
|
@ -107,7 +107,7 @@ bool entry::validate(bool show_unaccounted) const
|
||||||
return balance.is_zero(); // must balance to 0.0
|
return balance.is_zero(); // must balance to 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
bool entry::matches(const std::list<mask>& regexps) const
|
bool entry::matches(const regexps_map& regexps) const
|
||||||
{
|
{
|
||||||
if (regexps.empty() || (ledger::matches(regexps, code) ||
|
if (regexps.empty() || (ledger::matches(regexps, code) ||
|
||||||
ledger::matches(regexps, desc))) {
|
ledger::matches(regexps, desc))) {
|
||||||
|
|
@ -216,7 +216,7 @@ void read_regexps(const std::string& path, regexps_map& 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))
|
||||||
regexps.push_back(mask(buf));
|
regexps.push_back(new mask(buf));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -226,28 +226,59 @@ bool matches(const regexps_map& regexps, const std::string& str,
|
||||||
{
|
{
|
||||||
assert(! regexps.empty());
|
assert(! regexps.empty());
|
||||||
|
|
||||||
// If the first pattern is an exclude, we assume all patterns match
|
bool match = false;
|
||||||
// if they don't match the exclude -- and by_exclusion will be set
|
bool definite = false;
|
||||||
// to true to reflect this "by default" behavior. But if the first
|
|
||||||
// pattern is an include, only accounts matching the include will
|
|
||||||
// match, and these are a positive match.
|
|
||||||
|
|
||||||
bool match = (*regexps.begin()).exclude;
|
// std::ofstream out("regex.out", std::ios_base::app);
|
||||||
if (match && by_exclusion)
|
// out << "Matching against: " << str << std::endl;
|
||||||
*by_exclusion = true;
|
|
||||||
|
|
||||||
for (std::list<mask>::const_iterator r = regexps.begin();
|
for (regexps_map_const_iterator r = regexps.begin();
|
||||||
r != regexps.end();
|
r != regexps.end();
|
||||||
r++) {
|
r++) {
|
||||||
int ovec[3];
|
// out << " Trying: " << (*r)->pattern << std::endl;
|
||||||
if (pcre_exec((*r).regexp, NULL, str.c_str(), str.length(),
|
|
||||||
0, 0, ovec, 3) >= 0) {
|
static int ovec[30];
|
||||||
if (by_exclusion)
|
int result = pcre_exec((*r)->regexp, NULL, str.c_str(), str.length(),
|
||||||
*by_exclusion = (*r).exclude;
|
0, 0, ovec, 30);
|
||||||
match = ! (*r).exclude;
|
if (result >= 0) {
|
||||||
|
// out << " Definite ";
|
||||||
|
|
||||||
|
match = ! (*r)->exclude;
|
||||||
|
// if (match)
|
||||||
|
// out << "match";
|
||||||
|
// else
|
||||||
|
// out << "unmatch";
|
||||||
|
|
||||||
|
definite = true;
|
||||||
|
} else {
|
||||||
|
assert(result == -1);
|
||||||
|
|
||||||
|
// out << " failure code = " << result << std::endl;
|
||||||
|
|
||||||
|
if ((*r)->exclude) {
|
||||||
|
if (! match) {
|
||||||
|
match = ! definite;
|
||||||
|
// if (match)
|
||||||
|
// out << " indefinite match by exclusion" << std::endl;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
definite = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// out << " Current status: "
|
||||||
|
// << (definite ? "definite " : "")
|
||||||
|
// << (match ? "match" : "not match") << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match && ! definite && by_exclusion) {
|
||||||
|
// out << " Note: Matched by exclusion rule" << std::endl;
|
||||||
|
*by_exclusion = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// out << " Final result: " << (match ? "match" : "not match")
|
||||||
|
// << std::endl;
|
||||||
|
|
||||||
return match;
|
return match;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
12
ledger.h
12
ledger.h
|
|
@ -1,5 +1,5 @@
|
||||||
#ifndef _LEDGER_H
|
#ifndef _LEDGER_H
|
||||||
#define _LEDGER_H "$Revision: 1.20 $"
|
#define _LEDGER_H "$Revision: 1.21 $"
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
|
|
@ -96,7 +96,9 @@ struct mask
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::list<mask> regexps_map;
|
typedef std::list<mask *> regexps_map;
|
||||||
|
typedef std::list<mask *>::iterator regexps_map_iterator;
|
||||||
|
typedef std::list<mask *>::const_iterator regexps_map_const_iterator;
|
||||||
|
|
||||||
void record_regexp(const std::string& pattern, regexps_map& regexps);
|
void record_regexp(const std::string& pattern, regexps_map& regexps);
|
||||||
void read_regexps(const std::string& path, regexps_map& regexps);
|
void read_regexps(const std::string& path, regexps_map& regexps);
|
||||||
|
|
@ -156,7 +158,7 @@ struct entry
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool matches(const std::list<mask>& regexps) const;
|
bool matches(const regexps_map& regexps) const;
|
||||||
bool validate(bool show_unaccounted = false) const;
|
bool validate(bool show_unaccounted = false) const;
|
||||||
|
|
||||||
void print(std::ostream& out, bool shortcut = true) const;
|
void print(std::ostream& out, bool shortcut = true) const;
|
||||||
|
|
@ -245,10 +247,10 @@ struct book
|
||||||
entries_list entries;
|
entries_list entries;
|
||||||
int current_year;
|
int current_year;
|
||||||
|
|
||||||
typedef std::map<std::list<mask> *,
|
typedef std::map<regexps_map *,
|
||||||
std::list<transaction *> *> virtual_map;
|
std::list<transaction *> *> virtual_map;
|
||||||
|
|
||||||
typedef std::pair<std::list<mask> *,
|
typedef std::pair<regexps_map *,
|
||||||
std::list<transaction *> *> virtual_map_pair;
|
std::list<transaction *> *> virtual_map_pair;
|
||||||
|
|
||||||
typedef virtual_map::const_iterator virtual_map_iterator;
|
typedef virtual_map::const_iterator virtual_map_iterator;
|
||||||
|
|
|
||||||
32
main.cc
32
main.cc
|
|
@ -82,8 +82,8 @@ int main(int argc, char * argv[])
|
||||||
{
|
{
|
||||||
std::istream * file = NULL;
|
std::istream * file = NULL;
|
||||||
std::string prices;
|
std::string prices;
|
||||||
|
|
||||||
regexps_map regexps;
|
regexps_map regexps;
|
||||||
|
int index;
|
||||||
|
|
||||||
have_beginning = false;
|
have_beginning = false;
|
||||||
have_ending = false;
|
have_ending = false;
|
||||||
|
|
@ -217,6 +217,8 @@ int main(int argc, char * argv[])
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
index = optind;
|
||||||
|
|
||||||
if (use_warnings && (have_beginning || have_ending)) {
|
if (use_warnings && (have_beginning || have_ending)) {
|
||||||
std::cout << "Reporting";
|
std::cout << "Reporting";
|
||||||
|
|
||||||
|
|
@ -252,18 +254,24 @@ 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[index++];
|
||||||
|
|
||||||
int optind_begin = optind;
|
int name_index = index;
|
||||||
if (command == "register") {
|
if (command == "register") {
|
||||||
if (optind == argc) {
|
if (optind == argc) {
|
||||||
std::cerr << ("Error: Must specify an account name "
|
std::cerr << ("Error: Must specify an account name "
|
||||||
"after the 'register' command.") << std::endl;
|
"after the 'register' command.") << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
optind++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compile the list of specified regular expressions, which can be
|
||||||
|
// specified after the command, or using the '-i FILE' option
|
||||||
|
|
||||||
|
for (; index < argc; index++)
|
||||||
|
regexps.push_back(new mask(argv[index]));
|
||||||
|
|
||||||
// Parse the ledger
|
// Parse the ledger
|
||||||
|
|
||||||
#ifdef READ_GNUCASH
|
#ifdef READ_GNUCASH
|
||||||
|
|
@ -282,12 +290,6 @@ int main(int argc, char * argv[])
|
||||||
if (! main_ledger)
|
if (! main_ledger)
|
||||||
std::exit(1);
|
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]));
|
|
||||||
|
|
||||||
// Record any prices specified by the user
|
// Record any prices specified by the user
|
||||||
|
|
||||||
if (! prices.empty()) {
|
if (! prices.empty()) {
|
||||||
|
|
@ -310,7 +312,7 @@ int main(int argc, char * argv[])
|
||||||
report_balances(std::cout, regexps);
|
report_balances(std::cout, regexps);
|
||||||
}
|
}
|
||||||
else if (command == "register") {
|
else if (command == "register") {
|
||||||
print_register(argv[optind_begin], std::cout, regexps);
|
print_register(argv[name_index], std::cout, regexps);
|
||||||
}
|
}
|
||||||
else if (command == "print") {
|
else if (command == "print") {
|
||||||
main_ledger->sort(cmp_entry_date());
|
main_ledger->sort(cmp_entry_date());
|
||||||
|
|
@ -325,6 +327,14 @@ int main(int argc, char * argv[])
|
||||||
// this point.
|
// this point.
|
||||||
|
|
||||||
delete main_ledger;
|
delete main_ledger;
|
||||||
|
|
||||||
|
// Delete the known regexp maps.
|
||||||
|
|
||||||
|
for (regexps_map_iterator r = regexps.begin();
|
||||||
|
r != regexps.end();
|
||||||
|
r++) {
|
||||||
|
delete *r;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
14
parse.cc
14
parse.cc
|
|
@ -24,7 +24,11 @@ static inline char * next_element(char * buf, bool variable = false)
|
||||||
*p = '\0';
|
*p = '\0';
|
||||||
return skip_ws(p + 1);
|
return skip_ws(p + 1);
|
||||||
}
|
}
|
||||||
else if (*(p + 1) == ' ' || *(p + 1) == '\t') {
|
else if (*p == '\t') {
|
||||||
|
*p = '\0';
|
||||||
|
return skip_ws(p + 1);
|
||||||
|
}
|
||||||
|
else if (*(p + 1) == ' ') {
|
||||||
*p = '\0';
|
*p = '\0';
|
||||||
return skip_ws(p + 2);
|
return skip_ws(p + 2);
|
||||||
}
|
}
|
||||||
|
|
@ -407,7 +411,7 @@ void parse_automated_transactions(std::istream& in, book * ledger)
|
||||||
{
|
{
|
||||||
static char line[MAX_LINE + 1];
|
static char line[MAX_LINE + 1];
|
||||||
|
|
||||||
std::list<mask> * masks = NULL;
|
regexps_map * masks = NULL;
|
||||||
|
|
||||||
while (! in.eof() && in.peek() == '=') {
|
while (! in.eof() && in.peek() == '=') {
|
||||||
in.getline(line, MAX_LINE);
|
in.getline(line, MAX_LINE);
|
||||||
|
|
@ -417,8 +421,8 @@ void parse_automated_transactions(std::istream& in, book * ledger)
|
||||||
p = skip_ws(p);
|
p = skip_ws(p);
|
||||||
|
|
||||||
if (! masks)
|
if (! masks)
|
||||||
masks = new std::list<mask>;
|
masks = new regexps_map;
|
||||||
masks->push_back(mask(p));
|
masks->push_back(new mask(p));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::list<transaction *> * xacts = NULL;
|
std::list<transaction *> * xacts = NULL;
|
||||||
|
|
@ -490,7 +494,7 @@ book * parse_ledger(std::istream& in, regexps_map& regexps,
|
||||||
linenum++;
|
linenum++;
|
||||||
|
|
||||||
// Add the regexp to whatever masks currently exist
|
// Add the regexp to whatever masks currently exist
|
||||||
regexps.push_back(mask(line));
|
regexps.push_back(new mask(line));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '=': // automated transactions
|
case '=': // automated transactions
|
||||||
|
|
|
||||||
138
register.cc
138
register.cc
|
|
@ -1,138 +0,0 @@
|
||||||
#include "ledger.h"
|
|
||||||
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
namespace ledger {
|
|
||||||
|
|
||||||
extern bool show_cleared;
|
|
||||||
extern bool get_quotes;
|
|
||||||
|
|
||||||
extern std::time_t begin_date;
|
|
||||||
extern bool have_beginning;
|
|
||||||
extern std::time_t end_date;
|
|
||||||
extern bool have_ending;
|
|
||||||
|
|
||||||
static std::string truncated(const std::string& str, int width)
|
|
||||||
{
|
|
||||||
char buf[256];
|
|
||||||
memset(buf, '\0', 255);
|
|
||||||
std::strncpy(buf, str.c_str(), width);
|
|
||||||
if (buf[width - 1])
|
|
||||||
std::strcpy(&buf[width - 3], "...");
|
|
||||||
else
|
|
||||||
buf[width] = '\0';
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Register printing code
|
|
||||||
//
|
|
||||||
|
|
||||||
void print_register(const std::string& acct_name, std::ostream& out,
|
|
||||||
regexps_map& regexps)
|
|
||||||
{
|
|
||||||
account * acct = main_ledger->find_account(acct_name, false);
|
|
||||||
if (! acct) {
|
|
||||||
std::cerr << "Error: Unknown account name: " << acct_name
|
|
||||||
<< std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Walk through all of the ledger entries, printing their register
|
|
||||||
// formatted equivalent
|
|
||||||
|
|
||||||
totals balance;
|
|
||||||
|
|
||||||
for (entries_list_iterator i = main_ledger->entries.begin();
|
|
||||||
i != main_ledger->entries.end();
|
|
||||||
i++) {
|
|
||||||
if (! (*i)->matches(regexps))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
for (std::list<transaction *>::iterator x = (*i)->xacts.begin();
|
|
||||||
x != (*i)->xacts.end();
|
|
||||||
x++) {
|
|
||||||
if ((*x)->acct != acct || ! show_cleared && (*i)->cleared)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
char buf[32];
|
|
||||||
std::strftime(buf, 31, "%m.%d ", std::localtime(&(*i)->date));
|
|
||||||
out << buf;
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
if ((*i)->cleared)
|
|
||||||
out << "* ";
|
|
||||||
else
|
|
||||||
out << " ";
|
|
||||||
|
|
||||||
out.width(4);
|
|
||||||
if ((*i)->code.empty())
|
|
||||||
out << " ";
|
|
||||||
else
|
|
||||||
out << std::left << (*i)->code;
|
|
||||||
#endif
|
|
||||||
out << " ";
|
|
||||||
|
|
||||||
out.width(30);
|
|
||||||
if ((*i)->desc.empty())
|
|
||||||
out << " ";
|
|
||||||
else
|
|
||||||
out << std::left << truncated((*i)->desc, 30);
|
|
||||||
out << " ";
|
|
||||||
|
|
||||||
// Always display the street value, if prices have been
|
|
||||||
// specified
|
|
||||||
|
|
||||||
amount * street = (*x)->cost->street(get_quotes);
|
|
||||||
balance.credit(street);
|
|
||||||
|
|
||||||
// If there are two transactions, use the one which does not
|
|
||||||
// refer to this account. If there are more than two, we will
|
|
||||||
// just have to print all of the splits, like gnucash does.
|
|
||||||
|
|
||||||
transaction * xact;
|
|
||||||
if ((*i)->xacts.size() == 2) {
|
|
||||||
if (*x == (*i)->xacts.front())
|
|
||||||
xact = (*i)->xacts.back();
|
|
||||||
else
|
|
||||||
xact = (*i)->xacts.front();
|
|
||||||
} else {
|
|
||||||
xact = *x;
|
|
||||||
}
|
|
||||||
|
|
||||||
out.width(22);
|
|
||||||
out << std::left << truncated(xact->acct_as_str(), 22) << " ";
|
|
||||||
|
|
||||||
out.width(12);
|
|
||||||
out << std::right << street->as_str(true);
|
|
||||||
delete street;
|
|
||||||
|
|
||||||
balance.print(out, 12);
|
|
||||||
|
|
||||||
out << std::endl;
|
|
||||||
|
|
||||||
if (xact != *x)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
for (std::list<transaction *>::iterator y = (*i)->xacts.begin();
|
|
||||||
y != (*i)->xacts.end();
|
|
||||||
y++) {
|
|
||||||
if (*x == *y)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
out << " ";
|
|
||||||
|
|
||||||
out.width(22);
|
|
||||||
out << std::left << truncated((*y)->acct_as_str(), 22) << " ";
|
|
||||||
|
|
||||||
out.width(12);
|
|
||||||
street = (*y)->cost->street(get_quotes);
|
|
||||||
out << std::right << street->as_str(true) << std::endl;
|
|
||||||
delete street;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ledger
|
|
||||||
636
reports.cc
Normal file
636
reports.cc
Normal file
|
|
@ -0,0 +1,636 @@
|
||||||
|
#include "ledger.h"
|
||||||
|
|
||||||
|
#define LEDGER_VERSION "1.1"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
namespace ledger {
|
||||||
|
|
||||||
|
extern book * parse_ledger(std::istream& in, regexps_map& regexps,
|
||||||
|
bool compute_balances);
|
||||||
|
#ifdef READ_GNUCASH
|
||||||
|
extern book * parse_gnucash(std::istream& in, bool compute_balances);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern bool parse_date(const char * date_str, std::time_t * result,
|
||||||
|
const int year = -1);
|
||||||
|
extern void parse_price_setting(const std::string& setting);
|
||||||
|
|
||||||
|
|
||||||
|
static bool show_cleared;
|
||||||
|
static bool show_virtual;
|
||||||
|
static bool get_quotes;
|
||||||
|
static bool show_children;
|
||||||
|
static bool show_empty;
|
||||||
|
static bool show_subtotals;
|
||||||
|
static bool full_names;
|
||||||
|
|
||||||
|
static std::time_t begin_date;
|
||||||
|
static bool have_beginning;
|
||||||
|
static std::time_t end_date;
|
||||||
|
static bool have_ending;
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Balance reporting code
|
||||||
|
//
|
||||||
|
|
||||||
|
static void display_total(std::ostream& out, totals& balance,
|
||||||
|
account * acct, bool top_level,
|
||||||
|
int * headlines)
|
||||||
|
{
|
||||||
|
bool displayed = false;
|
||||||
|
|
||||||
|
if (acct->checked == 1 &&
|
||||||
|
(show_empty || ! acct->balance.is_zero())) {
|
||||||
|
displayed = true;
|
||||||
|
|
||||||
|
acct->balance.print(out, 20);
|
||||||
|
if (show_subtotals && top_level)
|
||||||
|
balance.credit(acct->balance);
|
||||||
|
|
||||||
|
if (acct->parent && ! full_names && ! top_level) {
|
||||||
|
for (const account * a = acct; a; a = a->parent)
|
||||||
|
out << " ";
|
||||||
|
out << acct->name << std::endl;
|
||||||
|
} else {
|
||||||
|
out << " " << acct->as_str() << std::endl;
|
||||||
|
(*headlines)++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display balances for all child accounts
|
||||||
|
|
||||||
|
for (accounts_map_iterator i = acct->children.begin();
|
||||||
|
i != acct->children.end();
|
||||||
|
i++)
|
||||||
|
display_total(out, balance, (*i).second, ! displayed, headlines);
|
||||||
|
}
|
||||||
|
|
||||||
|
void report_balances(std::ostream& out, regexps_map& regexps)
|
||||||
|
{
|
||||||
|
// Walk through all of the ledger entries, computing the account
|
||||||
|
// totals
|
||||||
|
|
||||||
|
for (entries_list_iterator i = main_ledger->entries.begin();
|
||||||
|
i != main_ledger->entries.end();
|
||||||
|
i++) {
|
||||||
|
if ((have_beginning && difftime((*i)->date, begin_date) < 0) ||
|
||||||
|
(have_ending && difftime((*i)->date, end_date) >= 0) ||
|
||||||
|
(show_cleared && ! (*i)->cleared))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (std::list<transaction *>::iterator x = (*i)->xacts.begin();
|
||||||
|
x != (*i)->xacts.end();
|
||||||
|
x++) {
|
||||||
|
if (! show_virtual && (*x)->is_virtual)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (account * acct = (*x)->acct;
|
||||||
|
acct;
|
||||||
|
acct = show_subtotals ? acct->parent : NULL) {
|
||||||
|
if (acct->checked == 0) {
|
||||||
|
if (regexps.empty()) {
|
||||||
|
if (! (show_children || ! acct->parent))
|
||||||
|
acct->checked = 2;
|
||||||
|
else
|
||||||
|
acct->checked = 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bool by_exclusion;
|
||||||
|
bool match = matches(regexps, acct->as_str(),
|
||||||
|
&by_exclusion);
|
||||||
|
if (! match) {
|
||||||
|
acct->checked = 2;
|
||||||
|
}
|
||||||
|
else if (by_exclusion) {
|
||||||
|
if (! (show_children || ! acct->parent))
|
||||||
|
acct->checked = 2;
|
||||||
|
else
|
||||||
|
acct->checked = 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
acct->checked = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (acct->checked == 1)
|
||||||
|
acct->balance.credit((*x)->cost->street(get_quotes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk through all the top-level accounts, giving the balance
|
||||||
|
// report for each, and then for each of their children.
|
||||||
|
|
||||||
|
totals balance;
|
||||||
|
int headlines = 0;
|
||||||
|
|
||||||
|
for (accounts_map_iterator i = main_ledger->accounts.begin();
|
||||||
|
i != main_ledger->accounts.end();
|
||||||
|
i++)
|
||||||
|
display_total(out, balance, (*i).second, true, &headlines);
|
||||||
|
|
||||||
|
// Print the total of all the balances shown
|
||||||
|
|
||||||
|
if (show_subtotals && headlines > 1) {
|
||||||
|
out << "--------------------" << std::endl;
|
||||||
|
balance.print(out, 20);
|
||||||
|
out << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Register printing code
|
||||||
|
//
|
||||||
|
|
||||||
|
static std::string truncated(const std::string& str, int width)
|
||||||
|
{
|
||||||
|
char buf[256];
|
||||||
|
memset(buf, '\0', 255);
|
||||||
|
std::strncpy(buf, str.c_str(), width);
|
||||||
|
if (buf[width - 1])
|
||||||
|
std::strcpy(&buf[width - 3], "...");
|
||||||
|
else
|
||||||
|
buf[width] = '\0';
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_register(const std::string& acct_name, std::ostream& out,
|
||||||
|
regexps_map& regexps)
|
||||||
|
{
|
||||||
|
account * acct = main_ledger->find_account(acct_name, false);
|
||||||
|
if (! acct) {
|
||||||
|
std::cerr << "Error: Unknown account name: " << acct_name
|
||||||
|
<< std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk through all of the ledger entries, printing their register
|
||||||
|
// formatted equivalent
|
||||||
|
|
||||||
|
totals balance;
|
||||||
|
|
||||||
|
for (entries_list_iterator i = main_ledger->entries.begin();
|
||||||
|
i != main_ledger->entries.end();
|
||||||
|
i++) {
|
||||||
|
if (! (*i)->matches(regexps))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (std::list<transaction *>::iterator x = (*i)->xacts.begin();
|
||||||
|
x != (*i)->xacts.end();
|
||||||
|
x++) {
|
||||||
|
if ((*x)->acct != acct || ! show_cleared && (*i)->cleared)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
char buf[32];
|
||||||
|
std::strftime(buf, 31, "%m.%d ", std::localtime(&(*i)->date));
|
||||||
|
out << buf;
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
if ((*i)->cleared)
|
||||||
|
out << "* ";
|
||||||
|
else
|
||||||
|
out << " ";
|
||||||
|
|
||||||
|
out.width(4);
|
||||||
|
if ((*i)->code.empty())
|
||||||
|
out << " ";
|
||||||
|
else
|
||||||
|
out << std::left << (*i)->code;
|
||||||
|
#endif
|
||||||
|
out << " ";
|
||||||
|
|
||||||
|
out.width(30);
|
||||||
|
if ((*i)->desc.empty())
|
||||||
|
out << " ";
|
||||||
|
else
|
||||||
|
out << std::left << truncated((*i)->desc, 30);
|
||||||
|
out << " ";
|
||||||
|
|
||||||
|
// Always display the street value, if prices have been
|
||||||
|
// specified
|
||||||
|
|
||||||
|
amount * street = (*x)->cost->street(get_quotes);
|
||||||
|
balance.credit(street);
|
||||||
|
|
||||||
|
// If there are two transactions, use the one which does not
|
||||||
|
// refer to this account. If there are more than two, we will
|
||||||
|
// just have to print all of the splits, like gnucash does.
|
||||||
|
|
||||||
|
transaction * xact;
|
||||||
|
if ((*i)->xacts.size() == 2) {
|
||||||
|
if (*x == (*i)->xacts.front())
|
||||||
|
xact = (*i)->xacts.back();
|
||||||
|
else
|
||||||
|
xact = (*i)->xacts.front();
|
||||||
|
} else {
|
||||||
|
xact = *x;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.width(22);
|
||||||
|
out << std::left << truncated(xact->acct_as_str(), 22) << " ";
|
||||||
|
|
||||||
|
out.width(12);
|
||||||
|
out << std::right << street->as_str(true);
|
||||||
|
delete street;
|
||||||
|
|
||||||
|
balance.print(out, 12);
|
||||||
|
|
||||||
|
out << std::endl;
|
||||||
|
|
||||||
|
if (! show_subtotals || xact != *x)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (std::list<transaction *>::iterator y = (*i)->xacts.begin();
|
||||||
|
y != (*i)->xacts.end();
|
||||||
|
y++) {
|
||||||
|
if (*x == *y)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
out << " ";
|
||||||
|
|
||||||
|
out.width(22);
|
||||||
|
out << std::left << truncated((*y)->acct_as_str(), 22) << " ";
|
||||||
|
|
||||||
|
out.width(12);
|
||||||
|
street = (*y)->cost->street(get_quotes);
|
||||||
|
out << std::right << street->as_str(true) << std::endl;
|
||||||
|
delete street;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Create an Equity file based on a ledger. This is used for
|
||||||
|
// archiving past years, and starting out a new year with compiled
|
||||||
|
// balances.
|
||||||
|
//
|
||||||
|
|
||||||
|
static void equity_entry(account * acct, regexps_map& regexps,
|
||||||
|
std::ostream& out)
|
||||||
|
{
|
||||||
|
if (! acct->balance.is_zero() &&
|
||||||
|
(regexps.empty() || matches(regexps, acct->as_str()))) {
|
||||||
|
entry opening;
|
||||||
|
|
||||||
|
opening.date = std::time(NULL);
|
||||||
|
opening.cleared = true;
|
||||||
|
opening.desc = "Opening Balance";
|
||||||
|
|
||||||
|
transaction * xact;
|
||||||
|
for (totals::const_iterator i = acct->balance.amounts.begin();
|
||||||
|
i != acct->balance.amounts.end();
|
||||||
|
i++) {
|
||||||
|
// Skip it, if there is a zero balance for the commodity
|
||||||
|
if ((*i).second->is_zero())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
xact = new transaction();
|
||||||
|
xact->acct = const_cast<account *>(acct);
|
||||||
|
xact->cost = (*i).second->street(get_quotes);
|
||||||
|
opening.xacts.push_back(xact);
|
||||||
|
|
||||||
|
xact = new transaction();
|
||||||
|
xact->acct = main_ledger->find_account("Equity:Opening Balances");
|
||||||
|
xact->cost = (*i).second->street(get_quotes);
|
||||||
|
xact->cost->negate();
|
||||||
|
opening.xacts.push_back(xact);
|
||||||
|
}
|
||||||
|
|
||||||
|
opening.print(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display balances for all child accounts
|
||||||
|
|
||||||
|
for (accounts_map_iterator i = acct->children.begin();
|
||||||
|
i != acct->children.end();
|
||||||
|
i++)
|
||||||
|
equity_entry((*i).second, regexps, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void equity_ledger(std::ostream& out, regexps_map& regexps)
|
||||||
|
{
|
||||||
|
// The account have their current totals already generated as a
|
||||||
|
// result of parsing. We just have to output those values.
|
||||||
|
// totals
|
||||||
|
|
||||||
|
for (accounts_map_iterator i = main_ledger->accounts.begin();
|
||||||
|
i != main_ledger->accounts.end();
|
||||||
|
i++)
|
||||||
|
equity_entry((*i).second, regexps, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ledger
|
||||||
|
|
||||||
|
using namespace ledger;
|
||||||
|
|
||||||
|
static void show_help(std::ostream& out)
|
||||||
|
{
|
||||||
|
std::cerr
|
||||||
|
<< "usage: ledger [options] COMMAND [options] [REGEXPS]" << std::endl
|
||||||
|
<< std::endl
|
||||||
|
<< "ledger options:" << std::endl
|
||||||
|
<< " -C also show cleared transactions" << std::endl
|
||||||
|
<< " -d DATE specify an implicit date range (e.g., -d april)"
|
||||||
|
<< std::endl
|
||||||
|
<< " -b DATE specify a beginning date" << std::endl
|
||||||
|
<< " -e DATE specify an ending date" << std::endl
|
||||||
|
<< " -c do not show future entries (same as -e TODAY)" << std::endl
|
||||||
|
<< " -f FILE specify pathname of ledger data file" << std::endl
|
||||||
|
<< " -h display this help text" << std::endl
|
||||||
|
<< " -R do not factor any virtual transactions" << std::endl
|
||||||
|
<< " -V FILE use virtual mappings listed in FILE" << std::endl
|
||||||
|
<< " -i FILE read the list of inclusion regexps from FILE" << std::endl
|
||||||
|
<< " -p FILE read the list of prices from FILE" << std::endl
|
||||||
|
<< " -P download price quotes from the Internet" << std::endl
|
||||||
|
<< " (works by running the command \"getquote SYMBOL\")"
|
||||||
|
<< std::endl
|
||||||
|
<< " -v display version information" << std::endl
|
||||||
|
<< " -w print out warnings where applicable" << std::endl
|
||||||
|
<< std::endl
|
||||||
|
<< "commands:" << std::endl
|
||||||
|
<< " balance show balance totals" << std::endl
|
||||||
|
<< " register display a register for ACCOUNT" << std::endl
|
||||||
|
<< " print print all ledger entries" << std::endl
|
||||||
|
<< " equity generate equity ledger for all entries" << std::endl
|
||||||
|
<< std::endl
|
||||||
|
<< "`balance' options:" << std::endl
|
||||||
|
<< " -F print each account's full name" << std::endl
|
||||||
|
<< " -n do not generate totals for parent accounts" << std::endl
|
||||||
|
<< " -s show sub-accounts in balance totals" << std::endl
|
||||||
|
<< " -S show empty accounts in balance totals" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Command-line parser and top-level logic.
|
||||||
|
//
|
||||||
|
|
||||||
|
int main(int argc, char * argv[])
|
||||||
|
{
|
||||||
|
std::istream * file = NULL;
|
||||||
|
std::string prices;
|
||||||
|
regexps_map regexps;
|
||||||
|
int index;
|
||||||
|
|
||||||
|
have_beginning = false;
|
||||||
|
have_ending = false;
|
||||||
|
show_cleared = false;
|
||||||
|
show_virtual = true;
|
||||||
|
show_children = false;
|
||||||
|
show_empty = false;
|
||||||
|
show_subtotals = true;
|
||||||
|
full_names = false;
|
||||||
|
|
||||||
|
// Parse the command-line options
|
||||||
|
|
||||||
|
int c;
|
||||||
|
while (-1 != (c = getopt(argc, argv, "+b:e:d:cChRV:wf:i:p:PvsSnF"))) {
|
||||||
|
switch (char(c)) {
|
||||||
|
case 'b':
|
||||||
|
case 'e': {
|
||||||
|
std::time_t when;
|
||||||
|
if (! parse_date(optarg, &when)) {
|
||||||
|
std::cerr << "Error: Bad date string: " << optarg << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == 'b') {
|
||||||
|
begin_date = when;
|
||||||
|
have_beginning = true;
|
||||||
|
} else {
|
||||||
|
end_date = when;
|
||||||
|
have_ending = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
case 'd': {
|
||||||
|
if (! parse_date(optarg, &begin_date)) {
|
||||||
|
std::cerr << "Error: Bad date string: " << optarg << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
have_beginning = true;
|
||||||
|
|
||||||
|
struct std::tm when, then;
|
||||||
|
std::memset(&then, 0, sizeof(struct std::tm));
|
||||||
|
|
||||||
|
std::time_t now = std::time(NULL);
|
||||||
|
struct std::tm * now_tm = std::localtime(&now);
|
||||||
|
|
||||||
|
for (const char ** f = formats; *f; f++) {
|
||||||
|
memset(&when, INT_MAX, sizeof(struct std::tm));
|
||||||
|
if (strptime(optarg, *f, &when)) {
|
||||||
|
then.tm_hour = 0;
|
||||||
|
then.tm_min = 0;
|
||||||
|
then.tm_sec = 0;
|
||||||
|
|
||||||
|
if (when.tm_year != -1)
|
||||||
|
then.tm_year = when.tm_year + 1;
|
||||||
|
else
|
||||||
|
then.tm_year = now_tm->tm_year;
|
||||||
|
|
||||||
|
if (std::strcmp(*f, "%Y") == 0) {
|
||||||
|
then.tm_mon = 0;
|
||||||
|
then.tm_mday = 1;
|
||||||
|
} else {
|
||||||
|
if (when.tm_mon != -1)
|
||||||
|
then.tm_mon = when.tm_mon + 1;
|
||||||
|
else
|
||||||
|
then.tm_mon = now_tm->tm_mon;
|
||||||
|
|
||||||
|
if (when.tm_mday != -1)
|
||||||
|
then.tm_mday = when.tm_mday + 1;
|
||||||
|
else
|
||||||
|
then.tm_mday = now_tm->tm_mday;
|
||||||
|
}
|
||||||
|
|
||||||
|
end_date = std::mktime(&then);
|
||||||
|
have_ending = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
case 'c':
|
||||||
|
end_date = std::time(NULL);
|
||||||
|
have_ending = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'h': show_help(std::cout); break;
|
||||||
|
case 'f': file = new std::ifstream(optarg); break;
|
||||||
|
|
||||||
|
case 'C': show_cleared = true; break;
|
||||||
|
case 'R': show_virtual = false; break;
|
||||||
|
case 'w': use_warnings = true; break;
|
||||||
|
case 's': show_children = true; break;
|
||||||
|
case 'S': show_empty = true; break;
|
||||||
|
case 'n': show_subtotals = false; break;
|
||||||
|
case 'F': full_names = true; break;
|
||||||
|
|
||||||
|
// -i path-to-file-of-regexps
|
||||||
|
case 'i':
|
||||||
|
read_regexps(optarg, regexps);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// -p "COMMODITY=PRICE"
|
||||||
|
// -p path-to-price-database
|
||||||
|
case 'p':
|
||||||
|
prices = optarg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'P':
|
||||||
|
get_quotes = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'v':
|
||||||
|
std::cout
|
||||||
|
<< "Ledger Accouting Tool " LEDGER_VERSION << std::endl
|
||||||
|
<< " Copyright (c) 2003 John Wiegley <johnw@newartisans.com>"
|
||||||
|
<< std::endl << std::endl
|
||||||
|
<< "This program is made available under the terms of the BSD"
|
||||||
|
<< std::endl
|
||||||
|
<< "Public License. See the LICENSE file included with the"
|
||||||
|
<< std::endl
|
||||||
|
<< "distribution for details and disclaimer." << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (optind == argc) {
|
||||||
|
show_help(std::cout);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
index = optind;
|
||||||
|
|
||||||
|
if (use_warnings && (have_beginning || have_ending)) {
|
||||||
|
std::cout << "Reporting";
|
||||||
|
|
||||||
|
if (have_beginning) {
|
||||||
|
char buf[32];
|
||||||
|
std::strftime(buf, 31, "%Y.%m.%d", std::localtime(&begin_date));
|
||||||
|
std::cout << " from " << buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (have_ending) {
|
||||||
|
char buf[32];
|
||||||
|
std::strftime(buf, 31, "%Y.%m.%d", std::localtime(&end_date));
|
||||||
|
std::cout << " until " << buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A ledger data file must be specified
|
||||||
|
|
||||||
|
if (! file) {
|
||||||
|
const char * p = std::getenv("LEDGER");
|
||||||
|
if (p)
|
||||||
|
file = new std::ifstream(p);
|
||||||
|
|
||||||
|
if (! file || ! *file) {
|
||||||
|
std::cerr << ("Please specify ledger file using -f option "
|
||||||
|
"or LEDGER environment variable.")
|
||||||
|
<< std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the command word
|
||||||
|
|
||||||
|
const std::string command = argv[index++];
|
||||||
|
|
||||||
|
int name_index = index;
|
||||||
|
if (command == "register") {
|
||||||
|
if (optind == argc) {
|
||||||
|
std::cerr << ("Error: Must specify an account name "
|
||||||
|
"after the 'register' command.") << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile the list of specified regular expressions, which can be
|
||||||
|
// specified after the command, or using the '-i FILE' option
|
||||||
|
|
||||||
|
for (; index < argc; index++)
|
||||||
|
regexps.push_back(new mask(argv[index]));
|
||||||
|
|
||||||
|
// Parse the ledger
|
||||||
|
|
||||||
|
#ifdef READ_GNUCASH
|
||||||
|
char buf[32];
|
||||||
|
file->get(buf, 31);
|
||||||
|
file->seekg(0);
|
||||||
|
|
||||||
|
if (std::strncmp(buf, "<?xml version=\"1.0\"?>", 21) == 0)
|
||||||
|
main_ledger = parse_gnucash(*file, command == "equity");
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
main_ledger = parse_ledger(*file, regexps, command == "equity");
|
||||||
|
|
||||||
|
delete file;
|
||||||
|
|
||||||
|
if (! main_ledger)
|
||||||
|
std::exit(1);
|
||||||
|
|
||||||
|
// Record any prices specified by the user
|
||||||
|
|
||||||
|
if (! prices.empty()) {
|
||||||
|
if (access(prices.c_str(), R_OK) != -1) {
|
||||||
|
std::ifstream pricedb(prices.c_str());
|
||||||
|
while (! pricedb.eof()) {
|
||||||
|
char buf[80];
|
||||||
|
pricedb.getline(buf, 79);
|
||||||
|
if (*buf && ! std::isspace(*buf))
|
||||||
|
parse_price_setting(buf);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parse_price_setting(prices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the command
|
||||||
|
|
||||||
|
if (command == "balance") {
|
||||||
|
report_balances(std::cout, regexps);
|
||||||
|
}
|
||||||
|
else if (command == "register") {
|
||||||
|
print_register(argv[name_index], std::cout, regexps);
|
||||||
|
}
|
||||||
|
else if (command == "print") {
|
||||||
|
main_ledger->sort(cmp_entry_date());
|
||||||
|
main_ledger->print(std::cout, regexps, true);
|
||||||
|
}
|
||||||
|
else if (command == "equity") {
|
||||||
|
equity_ledger(std::cout, regexps);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
// Ordinarily, deleting the main ledger just isn't necessary at
|
||||||
|
// this point.
|
||||||
|
|
||||||
|
delete main_ledger;
|
||||||
|
|
||||||
|
// Delete the known regexp maps.
|
||||||
|
|
||||||
|
for (regexps_map_iterator r = regexps.begin();
|
||||||
|
r != regexps.end();
|
||||||
|
r++) {
|
||||||
|
delete *r;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// reports.cc ends here.
|
||||||
Loading…
Add table
Reference in a new issue