*** empty log message ***
This commit is contained in:
parent
072d5131b4
commit
4cb6433ccf
8 changed files with 198 additions and 447 deletions
9
Makefile
9
Makefile
|
|
@ -1,14 +1,9 @@
|
||||||
define GNUCASH
|
|
||||||
true
|
|
||||||
endef
|
|
||||||
|
|
||||||
CODE = amount.cc ledger.cc parse.cc reports.cc
|
CODE = amount.cc ledger.cc parse.cc reports.cc
|
||||||
|
|
||||||
OBJS = $(patsubst %.cc,%.o,$(CODE))
|
OBJS = $(patsubst %.cc,%.o,$(CODE))
|
||||||
|
|
||||||
CFLAGS = -Wall -ansi -pedantic
|
CFLAGS = -Wall -ansi -pedantic
|
||||||
#DFLAGS = -O3 -fomit-frame-pointer -mcpu=pentium
|
#DFLAGS = -O3 -fomit-frame-pointer -mcpu=pentium
|
||||||
DFLAGS = -g -DDEBUG=1
|
#DFLAGS = -g -DDEBUG=1
|
||||||
|
DFLAGS = -O2
|
||||||
INCS =
|
INCS =
|
||||||
LIBS = -lgmpxx -lgmp -lpcre
|
LIBS = -lgmpxx -lgmp -lpcre
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -144,14 +144,10 @@ static void multiply(mpz_t out, const mpz_t l, const mpz_t r)
|
||||||
amount * gmp_amount::copy() const
|
amount * gmp_amount::copy() const
|
||||||
{
|
{
|
||||||
gmp_amount * new_amt = new gmp_amount();
|
gmp_amount * new_amt = new gmp_amount();
|
||||||
#if 0
|
|
||||||
// Don't copy the price
|
|
||||||
new_amt->priced = priced;
|
|
||||||
mpz_set(new_amt->price, price);
|
|
||||||
new_amt->price_comm = price_comm;
|
|
||||||
#endif
|
|
||||||
mpz_set(new_amt->quantity, quantity);
|
mpz_set(new_amt->quantity, quantity);
|
||||||
new_amt->quantity_comm = quantity_comm;
|
new_amt->quantity_comm = quantity_comm;
|
||||||
|
|
||||||
return new_amt;
|
return new_amt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -242,6 +238,7 @@ amount * gmp_amount::street(bool get_quotes) const
|
||||||
if (amt->commdty() == old->commdty())
|
if (amt->commdty() == old->commdty())
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return amt;
|
return amt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -203,10 +203,13 @@ static void dataHandler(void *userData, const char *s, int len)
|
||||||
delete curr_value;
|
delete curr_value;
|
||||||
curr_value = NULL;
|
curr_value = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
xact->cost = create_amount(value.c_str(), curr_value);
|
xact->cost = create_amount(value.c_str(), curr_value);
|
||||||
|
|
||||||
if (curr_value)
|
if (curr_value) {
|
||||||
delete curr_value;
|
delete curr_value;
|
||||||
|
curr_value = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if (do_compute)
|
if (do_compute)
|
||||||
xact->acct->balance.credit(xact->cost);
|
xact->acct->balance.credit(xact->cost);
|
||||||
|
|
|
||||||
79
ledger.cc
79
ledger.cc
|
|
@ -7,6 +7,12 @@ namespace ledger {
|
||||||
bool use_warnings = false;
|
bool use_warnings = false;
|
||||||
book * main_ledger;
|
book * main_ledger;
|
||||||
|
|
||||||
|
commodity::~commodity()
|
||||||
|
{
|
||||||
|
if (price)
|
||||||
|
delete price;
|
||||||
|
}
|
||||||
|
|
||||||
const std::string transaction::acct_as_str() const
|
const std::string transaction::acct_as_str() const
|
||||||
{
|
{
|
||||||
char * begin = NULL;
|
char * begin = NULL;
|
||||||
|
|
@ -96,8 +102,11 @@ 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) {
|
||||||
balance.credit((*x)->cost->value());
|
amount * value = (*x)->cost->value();
|
||||||
|
balance.credit(value);
|
||||||
|
delete value;
|
||||||
|
}
|
||||||
|
|
||||||
if (show_unaccounted && ! balance.is_zero()) {
|
if (show_unaccounted && ! balance.is_zero()) {
|
||||||
std::cerr << "Unaccounted-for balances are:" << std::endl;
|
std::cerr << "Unaccounted-for balances are:" << std::endl;
|
||||||
|
|
@ -135,6 +144,23 @@ totals::~totals()
|
||||||
delete (*i).second;
|
delete (*i).second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void totals::credit(const amount * val)
|
||||||
|
{
|
||||||
|
iterator i = amounts.find(val->commdty());
|
||||||
|
if (i != amounts.end())
|
||||||
|
(*i).second->credit(val);
|
||||||
|
#ifndef DEBUG
|
||||||
|
else
|
||||||
|
amounts.insert(pair(val->commdty(), val->copy()));
|
||||||
|
#else
|
||||||
|
else {
|
||||||
|
std::pair<iterator, bool> result =
|
||||||
|
amounts.insert(pair(val->commdty(), val->copy()));
|
||||||
|
assert(result.second);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
|
@ -169,6 +195,24 @@ void totals::print(std::ostream& out, int width) const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
account::~account()
|
||||||
|
{
|
||||||
|
for (accounts_map_iterator i = children.begin();
|
||||||
|
i != children.end();
|
||||||
|
i++)
|
||||||
|
delete (*i).second;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string account::as_str() const
|
||||||
|
{
|
||||||
|
if (! parent)
|
||||||
|
return name;
|
||||||
|
else if (full_name.empty())
|
||||||
|
full_name = parent->as_str() + ":" + name;
|
||||||
|
|
||||||
|
return full_name;
|
||||||
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
|
|
||||||
|
|
@ -216,11 +260,19 @@ 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(new mask(buf));
|
regexps.push_back(mask(buf));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool mask::match(const std::string& str) const
|
||||||
|
{
|
||||||
|
static int ovec[30];
|
||||||
|
int result = pcre_exec(regexp, NULL, str.c_str(), str.length(),
|
||||||
|
0, 0, ovec, 30);
|
||||||
|
return result >= 0 && ! exclude;
|
||||||
|
}
|
||||||
|
|
||||||
bool matches(const regexps_map& regexps, const std::string& str,
|
bool matches(const regexps_map& regexps, const std::string& str,
|
||||||
bool * by_exclusion)
|
bool * by_exclusion)
|
||||||
{
|
{
|
||||||
|
|
@ -235,15 +287,15 @@ bool matches(const regexps_map& regexps, const std::string& str,
|
||||||
for (regexps_map_const_iterator r = regexps.begin();
|
for (regexps_map_const_iterator r = regexps.begin();
|
||||||
r != regexps.end();
|
r != regexps.end();
|
||||||
r++) {
|
r++) {
|
||||||
// out << " Trying: " << (*r)->pattern << std::endl;
|
// out << " Trying: " << (*r).pattern << std::endl;
|
||||||
|
|
||||||
static int ovec[30];
|
static int ovec[30];
|
||||||
int result = pcre_exec((*r)->regexp, NULL, str.c_str(), str.length(),
|
int result = pcre_exec((*r).regexp, NULL, str.c_str(), str.length(),
|
||||||
0, 0, ovec, 30);
|
0, 0, ovec, 30);
|
||||||
if (result >= 0) {
|
if (result >= 0) {
|
||||||
// out << " Definite ";
|
// out << " Definite ";
|
||||||
|
|
||||||
match = ! (*r)->exclude;
|
match = ! (*r).exclude;
|
||||||
// if (match)
|
// if (match)
|
||||||
// out << "match";
|
// out << "match";
|
||||||
// else
|
// else
|
||||||
|
|
@ -255,7 +307,7 @@ bool matches(const regexps_map& regexps, const std::string& str,
|
||||||
|
|
||||||
// out << " failure code = " << result << std::endl;
|
// out << " failure code = " << result << std::endl;
|
||||||
|
|
||||||
if ((*r)->exclude) {
|
if ((*r).exclude) {
|
||||||
if (! match) {
|
if (! match) {
|
||||||
match = ! definite;
|
match = ! definite;
|
||||||
// if (match)
|
// if (match)
|
||||||
|
|
@ -294,6 +346,19 @@ book::~book()
|
||||||
i++)
|
i++)
|
||||||
delete (*i).second;
|
delete (*i).second;
|
||||||
|
|
||||||
|
for (virtual_map_iterator i = virtual_mapping.begin();
|
||||||
|
i != virtual_mapping.end();
|
||||||
|
i++) {
|
||||||
|
delete (*i).first;
|
||||||
|
|
||||||
|
for (std::list<transaction *>::iterator j = (*i).second->begin();
|
||||||
|
j != (*i).second->end();
|
||||||
|
j++) {
|
||||||
|
delete *j;
|
||||||
|
}
|
||||||
|
delete (*i).second;
|
||||||
|
}
|
||||||
|
|
||||||
for (entries_list_iterator i = entries.begin();
|
for (entries_list_iterator i = entries.begin();
|
||||||
i != entries.end();
|
i != entries.end();
|
||||||
i++)
|
i++)
|
||||||
|
|
|
||||||
106
ledger.h
106
ledger.h
|
|
@ -1,5 +1,5 @@
|
||||||
#ifndef _LEDGER_H
|
#ifndef _LEDGER_H
|
||||||
#define _LEDGER_H "$Revision: 1.21 $"
|
#define _LEDGER_H "$Revision: 1.22 $"
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
// by John Wiegley <johnw@newartisans.com>
|
// by John Wiegley <johnw@newartisans.com>
|
||||||
//
|
//
|
||||||
// Copyright (c) 2003 New Artisans, Inc. All Rights Reserved.
|
// Copyright (c) 2003 New Artisans, Inc. All Rights Reserved.
|
||||||
|
//
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
@ -26,9 +27,12 @@
|
||||||
|
|
||||||
namespace ledger {
|
namespace ledger {
|
||||||
|
|
||||||
struct amount;
|
class amount;
|
||||||
struct commodity
|
class commodity
|
||||||
{
|
{
|
||||||
|
commodity(const commodity&);
|
||||||
|
|
||||||
|
public:
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string symbol;
|
std::string symbol;
|
||||||
|
|
||||||
|
|
@ -41,10 +45,14 @@ struct commodity
|
||||||
|
|
||||||
int precision;
|
int precision;
|
||||||
|
|
||||||
commodity() : price(NULL), prefix(false), separate(true),
|
explicit commodity() : price(NULL), prefix(false), separate(true),
|
||||||
thousands(false), european(false) {}
|
thousands(false), european(false) {}
|
||||||
commodity(const std::string& sym, bool pre = false, bool sep = true,
|
|
||||||
bool thou = true, bool euro = false, int prec = 2);
|
explicit commodity(const std::string& sym, bool pre = false,
|
||||||
|
bool sep = true, bool thou = true,
|
||||||
|
bool euro = false, int prec = 2);
|
||||||
|
|
||||||
|
~commodity();
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::map<const std::string, commodity *> commodities_map;
|
typedef std::map<const std::string, commodity *> commodities_map;
|
||||||
|
|
@ -83,22 +91,30 @@ class amount
|
||||||
extern amount * create_amount(const std::string& value,
|
extern amount * create_amount(const std::string& value,
|
||||||
const amount * cost = NULL);
|
const amount * cost = NULL);
|
||||||
|
|
||||||
struct mask
|
class mask
|
||||||
{
|
{
|
||||||
|
// jww (2003-10-08): need to correct this
|
||||||
|
//mask(const mask&);
|
||||||
|
|
||||||
|
public:
|
||||||
bool exclude;
|
bool exclude;
|
||||||
std::string pattern;
|
std::string pattern;
|
||||||
pcre * regexp;
|
pcre * regexp;
|
||||||
|
|
||||||
mask(const std::string& pattern);
|
explicit mask(const std::string& pattern);
|
||||||
|
|
||||||
|
#if 0
|
||||||
~mask() {
|
~mask() {
|
||||||
pcre_free(regexp);
|
pcre_free(regexp);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool match(const std::string& str) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::list<mask *> regexps_map;
|
typedef std::list<mask> regexps_map;
|
||||||
typedef std::list<mask *>::iterator regexps_map_iterator;
|
typedef std::list<mask>::iterator regexps_map_iterator;
|
||||||
typedef std::list<mask *>::const_iterator regexps_map_const_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);
|
||||||
|
|
@ -106,9 +122,12 @@ bool matches(const regexps_map& regexps, const std::string& str,
|
||||||
bool * by_exclusion = NULL);
|
bool * by_exclusion = NULL);
|
||||||
|
|
||||||
|
|
||||||
struct account;
|
class account;
|
||||||
struct transaction
|
class transaction
|
||||||
{
|
{
|
||||||
|
transaction(const transaction&);
|
||||||
|
|
||||||
|
public:
|
||||||
account * acct;
|
account * acct;
|
||||||
amount * cost;
|
amount * cost;
|
||||||
|
|
||||||
|
|
@ -118,7 +137,7 @@ struct transaction
|
||||||
bool must_balance;
|
bool must_balance;
|
||||||
bool specified;
|
bool specified;
|
||||||
|
|
||||||
transaction(account * _acct = NULL, amount * _cost = NULL)
|
explicit transaction(account * _acct = NULL, amount * _cost = NULL)
|
||||||
: acct(_acct), cost(_cost),
|
: acct(_acct), cost(_cost),
|
||||||
is_virtual(false), must_balance(true), specified(false) {}
|
is_virtual(false), must_balance(true), specified(false) {}
|
||||||
|
|
||||||
|
|
@ -134,8 +153,11 @@ struct transaction
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
struct entry
|
class entry
|
||||||
{
|
{
|
||||||
|
entry(const entry&);
|
||||||
|
|
||||||
|
public:
|
||||||
std::time_t date;
|
std::time_t date;
|
||||||
std::string code;
|
std::string code;
|
||||||
std::string desc;
|
std::string desc;
|
||||||
|
|
@ -144,7 +166,7 @@ struct entry
|
||||||
|
|
||||||
std::list<transaction *> xacts;
|
std::list<transaction *> xacts;
|
||||||
|
|
||||||
entry() : cleared(false) {}
|
explicit entry() : cleared(false) {}
|
||||||
|
|
||||||
// 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
|
||||||
|
|
@ -175,8 +197,11 @@ typedef entries_list::iterator entries_list_iterator;
|
||||||
typedef entries_list::const_iterator entries_list_const_iterator;
|
typedef entries_list::const_iterator entries_list_const_iterator;
|
||||||
|
|
||||||
|
|
||||||
struct totals
|
class totals
|
||||||
{
|
{
|
||||||
|
totals(const totals&);
|
||||||
|
|
||||||
|
public:
|
||||||
typedef std::map<commodity *, amount *> map;
|
typedef std::map<commodity *, amount *> map;
|
||||||
typedef map::iterator iterator;
|
typedef map::iterator iterator;
|
||||||
typedef map::const_iterator const_iterator;
|
typedef map::const_iterator const_iterator;
|
||||||
|
|
@ -184,24 +209,15 @@ struct totals
|
||||||
|
|
||||||
map amounts;
|
map amounts;
|
||||||
|
|
||||||
|
totals() {}
|
||||||
~totals();
|
~totals();
|
||||||
|
|
||||||
void credit(const amount * val) {
|
void credit(const amount * val);
|
||||||
std::pair<iterator, bool> result =
|
|
||||||
amounts.insert(pair(val->commdty(), val->copy()));
|
|
||||||
if (! result.second)
|
|
||||||
amounts[val->commdty()]->credit(val);
|
|
||||||
}
|
|
||||||
void credit(const totals& other);
|
void credit(const totals& other);
|
||||||
|
|
||||||
bool is_zero() const;
|
bool is_zero() const;
|
||||||
|
|
||||||
void print(std::ostream& out, int width) const;
|
void print(std::ostream& out, int width) const;
|
||||||
|
|
||||||
// Returns an allocated entity
|
|
||||||
amount * sum(commodity * comm) {
|
|
||||||
return amounts[comm];
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -209,8 +225,11 @@ typedef std::map<const std::string, account *> accounts_map;
|
||||||
typedef accounts_map::iterator accounts_map_iterator;
|
typedef accounts_map::iterator accounts_map_iterator;
|
||||||
typedef std::pair<const std::string, account *> accounts_map_pair;
|
typedef std::pair<const std::string, account *> accounts_map_pair;
|
||||||
|
|
||||||
struct account
|
class account
|
||||||
{
|
{
|
||||||
|
account(const account&);
|
||||||
|
|
||||||
|
public:
|
||||||
account * parent;
|
account * parent;
|
||||||
|
|
||||||
std::string name;
|
std::string name;
|
||||||
|
|
@ -223,30 +242,23 @@ struct account
|
||||||
|
|
||||||
mutable std::string full_name;
|
mutable std::string full_name;
|
||||||
|
|
||||||
account() : parent(NULL), checked(0) {}
|
explicit account() : parent(NULL), checked(0) {}
|
||||||
|
|
||||||
account(const std::string& _name, struct account * _parent = NULL)
|
explicit account(const std::string& _name,
|
||||||
|
struct account * _parent = NULL)
|
||||||
: parent(_parent), name(_name), checked(0) {}
|
: parent(_parent), name(_name), checked(0) {}
|
||||||
|
|
||||||
const std::string as_str() const {
|
~account();
|
||||||
if (! parent)
|
|
||||||
return name;
|
|
||||||
else if (full_name.empty())
|
|
||||||
full_name = parent->as_str() + ":" + name;
|
|
||||||
|
|
||||||
return full_name;
|
const std::string as_str() const;
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
struct book
|
class book
|
||||||
{
|
{
|
||||||
commodities_map commodities;
|
book(const book&);
|
||||||
accounts_map accounts;
|
|
||||||
accounts_map accounts_cache; // maps full names to accounts
|
|
||||||
entries_list entries;
|
|
||||||
int current_year;
|
|
||||||
|
|
||||||
|
public:
|
||||||
typedef std::map<regexps_map *,
|
typedef std::map<regexps_map *,
|
||||||
std::list<transaction *> *> virtual_map;
|
std::list<transaction *> *> virtual_map;
|
||||||
|
|
||||||
|
|
@ -255,8 +267,14 @@ struct book
|
||||||
|
|
||||||
typedef virtual_map::const_iterator virtual_map_iterator;
|
typedef virtual_map::const_iterator virtual_map_iterator;
|
||||||
|
|
||||||
|
commodities_map commodities;
|
||||||
|
accounts_map accounts;
|
||||||
|
accounts_map accounts_cache; // maps full names to accounts
|
||||||
virtual_map virtual_mapping;
|
virtual_map virtual_mapping;
|
||||||
|
entries_list entries;
|
||||||
|
int current_year;
|
||||||
|
|
||||||
|
book() {}
|
||||||
~book();
|
~book();
|
||||||
|
|
||||||
template<typename Compare>
|
template<typename Compare>
|
||||||
|
|
|
||||||
341
main.cc
341
main.cc
|
|
@ -1,341 +0,0 @@
|
||||||
#include "ledger.h"
|
|
||||||
|
|
||||||
#define LEDGER_VERSION "1.1"
|
|
||||||
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
extern void report_balances(std::ostream& out, regexps_map& regexps);
|
|
||||||
extern void print_register(const std::string& acct_name, std::ostream& out,
|
|
||||||
regexps_map& regexps);
|
|
||||||
extern void equity_ledger(std::ostream& out, regexps_map& regexps);
|
|
||||||
|
|
||||||
bool show_cleared;
|
|
||||||
bool show_virtual;
|
|
||||||
bool get_quotes;
|
|
||||||
bool show_children;
|
|
||||||
bool show_empty;
|
|
||||||
bool show_subtotals;
|
|
||||||
bool full_names;
|
|
||||||
|
|
||||||
std::time_t begin_date;
|
|
||||||
bool have_beginning;
|
|
||||||
std::time_t end_date;
|
|
||||||
bool have_ending;
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// main.cc ends here.
|
|
||||||
11
parse.cc
11
parse.cc
|
|
@ -242,8 +242,11 @@ entry * parse_entry(std::istream& in, book * ledger)
|
||||||
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)->cost && ! (*x)->is_virtual)
|
if ((*x)->cost && ! (*x)->is_virtual) {
|
||||||
balance.credit((*x)->cost->value());
|
amount * value = (*x)->cost->value();
|
||||||
|
balance.credit(value);
|
||||||
|
delete value;
|
||||||
|
}
|
||||||
|
|
||||||
// If one transaction is of a different commodity than the others,
|
// If one transaction is of a different commodity than the others,
|
||||||
// and it has no per-unit price, determine its price by dividing
|
// and it has no per-unit price, determine its price by dividing
|
||||||
|
|
@ -422,7 +425,7 @@ void parse_automated_transactions(std::istream& in, book * ledger)
|
||||||
|
|
||||||
if (! masks)
|
if (! masks)
|
||||||
masks = new regexps_map;
|
masks = new regexps_map;
|
||||||
masks->push_back(new mask(p));
|
masks->push_back(mask(p));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::list<transaction *> * xacts = NULL;
|
std::list<transaction *> * xacts = NULL;
|
||||||
|
|
@ -494,7 +497,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(new mask(line));
|
regexps.push_back(mask(line));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '=': // automated transactions
|
case '=': // automated transactions
|
||||||
|
|
|
||||||
43
reports.cc
43
reports.cc
|
|
@ -22,6 +22,7 @@ static bool show_cleared;
|
||||||
static bool show_virtual;
|
static bool show_virtual;
|
||||||
static bool get_quotes;
|
static bool get_quotes;
|
||||||
static bool show_children;
|
static bool show_children;
|
||||||
|
static bool show_sorted;
|
||||||
static bool show_empty;
|
static bool show_empty;
|
||||||
static bool show_subtotals;
|
static bool show_subtotals;
|
||||||
static bool full_names;
|
static bool full_names;
|
||||||
|
|
@ -117,8 +118,11 @@ void report_balances(std::ostream& out, regexps_map& regexps)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (acct->checked == 1)
|
if (acct->checked == 1) {
|
||||||
acct->balance.credit((*x)->cost->street(get_quotes));
|
amount * street = (*x)->cost->street(get_quotes);
|
||||||
|
acct->balance.credit(street);
|
||||||
|
delete street;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -163,7 +167,17 @@ static std::string truncated(const std::string& str, int width)
|
||||||
void print_register(const std::string& acct_name, std::ostream& out,
|
void print_register(const std::string& acct_name, std::ostream& out,
|
||||||
regexps_map& regexps)
|
regexps_map& regexps)
|
||||||
{
|
{
|
||||||
account * acct = main_ledger->find_account(acct_name, false);
|
account * acct = NULL;
|
||||||
|
mask acct_regex(acct_name);
|
||||||
|
|
||||||
|
for (accounts_map_iterator i = main_ledger->accounts.begin();
|
||||||
|
i != main_ledger->accounts.end();
|
||||||
|
i++)
|
||||||
|
if (acct_regex.match((*i).second->as_str())) {
|
||||||
|
acct = (*i).second;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (! acct) {
|
if (! acct) {
|
||||||
std::cerr << "Error: Unknown account name: " << acct_name
|
std::cerr << "Error: Unknown account name: " << acct_name
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
|
|
@ -385,6 +399,7 @@ int main(int argc, char * argv[])
|
||||||
show_cleared = false;
|
show_cleared = false;
|
||||||
show_virtual = true;
|
show_virtual = true;
|
||||||
show_children = false;
|
show_children = false;
|
||||||
|
show_sorted = false;
|
||||||
show_empty = false;
|
show_empty = false;
|
||||||
show_subtotals = true;
|
show_subtotals = true;
|
||||||
full_names = false;
|
full_names = false;
|
||||||
|
|
@ -392,7 +407,7 @@ int main(int argc, char * argv[])
|
||||||
// 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:PvsSnF"))) {
|
while (-1 != (c = getopt(argc, argv, "+b:e:d:cChRV:wf:i:p:PvsSEnF"))) {
|
||||||
switch (char(c)) {
|
switch (char(c)) {
|
||||||
case 'b':
|
case 'b':
|
||||||
case 'e': {
|
case 'e': {
|
||||||
|
|
@ -474,7 +489,8 @@ int main(int argc, char * argv[])
|
||||||
case 'R': show_virtual = false; break;
|
case 'R': show_virtual = false; break;
|
||||||
case 'w': use_warnings = true; break;
|
case 'w': use_warnings = true; break;
|
||||||
case 's': show_children = true; break;
|
case 's': show_children = true; break;
|
||||||
case 'S': show_empty = true; break;
|
case 'S': show_sorted = true; break;
|
||||||
|
case 'E': show_empty = true; break;
|
||||||
case 'n': show_subtotals = false; break;
|
case 'n': show_subtotals = false; break;
|
||||||
case 'F': full_names = true; break;
|
case 'F': full_names = true; break;
|
||||||
|
|
||||||
|
|
@ -565,7 +581,7 @@ int main(int argc, char * argv[])
|
||||||
// specified after the command, or using the '-i FILE' option
|
// specified after the command, or using the '-i FILE' option
|
||||||
|
|
||||||
for (; index < argc; index++)
|
for (; index < argc; index++)
|
||||||
regexps.push_back(new mask(argv[index]));
|
regexps.push_back(mask(argv[index]));
|
||||||
|
|
||||||
// Parse the ledger
|
// Parse the ledger
|
||||||
|
|
||||||
|
|
@ -607,9 +623,12 @@ int main(int argc, char * argv[])
|
||||||
report_balances(std::cout, regexps);
|
report_balances(std::cout, regexps);
|
||||||
}
|
}
|
||||||
else if (command == "register") {
|
else if (command == "register") {
|
||||||
|
if (show_sorted)
|
||||||
|
main_ledger->sort(cmp_entry_date());
|
||||||
print_register(argv[name_index], std::cout, regexps);
|
print_register(argv[name_index], std::cout, regexps);
|
||||||
}
|
}
|
||||||
else if (command == "print") {
|
else if (command == "print") {
|
||||||
|
if (show_sorted)
|
||||||
main_ledger->sort(cmp_entry_date());
|
main_ledger->sort(cmp_entry_date());
|
||||||
main_ledger->print(std::cout, regexps, true);
|
main_ledger->print(std::cout, regexps, true);
|
||||||
}
|
}
|
||||||
|
|
@ -618,18 +637,10 @@ int main(int argc, char * argv[])
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
// Ordinarily, deleting the main ledger just isn't necessary at
|
// Ordinarily, deleting the main ledger isn't necessary, since the
|
||||||
// this point.
|
// process is about to give back its heap to the OS.
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue