rewrote the way registers are computed -- again
This commit is contained in:
parent
f570e6644f
commit
1741c80fe4
26 changed files with 1305 additions and 1154 deletions
44
Makefile
44
Makefile
|
|
@ -1,17 +1,34 @@
|
|||
CODE = amount.cc balance.cc account.cc ledger.cc \
|
||||
item.cc expr.cc format.cc textual.cc binary.cc
|
||||
CODE = account.cc \
|
||||
amount.cc \
|
||||
autoxact.cc \
|
||||
balance.cc \
|
||||
binary.cc \
|
||||
datetime.cc \
|
||||
error.cc \
|
||||
format.cc \
|
||||
ledger.cc \
|
||||
textual.cc \
|
||||
valexpr.cc \
|
||||
walk.cc
|
||||
|
||||
OBJS = $(patsubst %.cc,%.o,$(CODE))
|
||||
|
||||
#CXX = cc
|
||||
CXX = g++
|
||||
|
||||
CFLAGS = -Wall -ansi -pedantic
|
||||
#DFLAGS = -O3 -fomit-frame-pointer
|
||||
DFLAGS = -g -DDEBUG=1
|
||||
#DFLAGS = -g -pg
|
||||
INCS = -I/sw/include -I/usr/include/gcc/darwin/3.3/c++ -I/usr/include/gcc/darwin/3.3/c++/ppc-darwin
|
||||
|
||||
INCS = -I/sw/include \
|
||||
-I/usr/include/gcc/darwin/3.3/c++ \
|
||||
-I/usr/include/gcc/darwin/3.3/c++/ppc-darwin
|
||||
LIBS = -L/sw/lib -lgmpxx -lgmp -lpcre
|
||||
|
||||
ifdef GNUCASH
|
||||
CODE := $(CODE) gnucash.cc
|
||||
OBJS := $(OBJS) gnucash.o
|
||||
CFLAGS := $(CFLAGS) -DREAD_GNUCASH=1
|
||||
INCS := $(INCS) -I/usr/include/httpd/xml
|
||||
LIBS := $(LIBS) -L/sw/lib -lxmlparse
|
||||
|
|
@ -34,8 +51,8 @@ libledger.a: $(OBJS)
|
|||
ledger: libledger.a main.o
|
||||
$(CXX) $(CFLAGS) $(INCS) $(DFLAGS) -o $@ main.o -L. -lledger $(LIBS)
|
||||
|
||||
report: libledger.a report.cc
|
||||
$(CXX) $(CFLAGS) $(INCS) $(DFLAGS) -DTEST -o $@ report.cc \
|
||||
valexpr: libledger.a valexpr.cc
|
||||
$(CXX) $(CFLAGS) $(INCS) $(DFLAGS) -DTEST -o $@ valexpr.cc \
|
||||
-L. -lledger $(LIBS)
|
||||
|
||||
ledger.info: ledger.texi
|
||||
|
|
@ -48,9 +65,9 @@ ledger.pdf: ledger.texi
|
|||
$(CXX) $(CFLAGS) $(INCS) $(DFLAGS) -c -o $@ $<
|
||||
|
||||
clean:
|
||||
rm -f ledger report libledger.a *.o *.elc *~ .\#*
|
||||
rm -f ledger valexpr libledger.a *.o *.elc *~ .\#*
|
||||
rm -f *.aux *.cp *.fn *.ky *.log *.pg *.toc *.tp *.vr
|
||||
rm -f .gdb_history
|
||||
rm -f .gdb_history gmon.out out
|
||||
|
||||
distclean fullclean: clean
|
||||
rm -f *.texi *.info *.html *.pdf *.elc make.deps TAGS
|
||||
|
|
@ -60,7 +77,7 @@ rebuild: clean deps all
|
|||
deps: make.deps
|
||||
|
||||
make.deps: Makefile
|
||||
cc -M $(INCS) $(CODE) > $@
|
||||
cc -M $(INCS) $(CODE) main.cc > $@
|
||||
|
||||
include make.deps
|
||||
|
||||
|
|
@ -95,11 +112,6 @@ dist:
|
|||
mv t Makefile && \
|
||||
perl -ne 'print if 1 .. /^include make.deps/;' Makefile > t && \
|
||||
mv t Makefile && \
|
||||
cd $(HOME)/Public && \
|
||||
tar cvzf ledger-$(VERSION).tar.gz /tmp/ledger-$(VERSION))
|
||||
|
||||
publish: dist
|
||||
(cd $(HOME)/Public && \
|
||||
ln -sf ledger-$(VERSION).tar.gz ledger.tar.gz && \
|
||||
rm -fr /tmp/ledger-$(VERSION) && \
|
||||
upload)
|
||||
cd /tmp && \
|
||||
tar cvzf $(HOME)/Sites/ledger/ledger-$(VERSION).tar.gz \
|
||||
ledger-$(VERSION))
|
||||
|
|
|
|||
2
README
2
README
|
|
@ -201,7 +201,7 @@ amount, if it is the same as the first line:
|
|||
For this entry, Ledger will figure out that $-23.00 must come from
|
||||
"Assets:Checking" in order to balance the entry.
|
||||
|
||||
** Transactions: Additions and Subtractions
|
||||
** Stating where money goes
|
||||
|
||||
Accountants will talk of `credits' and `debits', but their meaning is
|
||||
often different from the layman's definitions. To avoid this semantic
|
||||
|
|
|
|||
24
account.cc
24
account.cc
|
|
@ -15,30 +15,26 @@ account_t::~account_t()
|
|||
delete (*i).second;
|
||||
}
|
||||
|
||||
account_t * account_t::find_account(const std::string& ident,
|
||||
account_t * account_t::find_account(const std::string& name,
|
||||
const bool auto_create)
|
||||
{
|
||||
accounts_map::const_iterator c = accounts_cache.find(ident);
|
||||
if (c != accounts_cache.end())
|
||||
return (*c).second;
|
||||
|
||||
accounts_map::const_iterator i = accounts.find(ident);
|
||||
accounts_map::const_iterator i = accounts.find(name);
|
||||
if (i != accounts.end())
|
||||
return (*i).second;
|
||||
|
||||
static char buf[256];
|
||||
|
||||
std::string::size_type sep = ident.find(':');
|
||||
std::string::size_type sep = name.find(':');
|
||||
const char * first, * rest;
|
||||
if (sep == std::string::npos) {
|
||||
first = ident.c_str();
|
||||
first = name.c_str();
|
||||
rest = NULL;
|
||||
} else {
|
||||
std::strncpy(buf, ident.c_str(), sep);
|
||||
std::strncpy(buf, name.c_str(), sep);
|
||||
buf[sep] = '\0';
|
||||
|
||||
first = buf;
|
||||
rest = ident.c_str() + sep + 1;
|
||||
rest = name.c_str() + sep + 1;
|
||||
}
|
||||
|
||||
account_t * account;
|
||||
|
|
@ -56,8 +52,6 @@ account_t * account_t::find_account(const std::string& ident,
|
|||
if (rest)
|
||||
account = account->find_account(rest, auto_create);
|
||||
|
||||
accounts_cache.insert(accounts_pair(ident, account));
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
|
|
@ -76,6 +70,9 @@ bool account_t::remove_transaction(transaction_t * xact)
|
|||
|
||||
std::string account_t::fullname() const
|
||||
{
|
||||
if (! _fullname.empty()) {
|
||||
return _fullname;
|
||||
} else {
|
||||
const account_t * first = this;
|
||||
std::string fullname = name;
|
||||
|
||||
|
|
@ -85,7 +82,10 @@ std::string account_t::fullname() const
|
|||
fullname = first->name + ":" + fullname;
|
||||
}
|
||||
|
||||
_fullname = fullname;
|
||||
|
||||
return fullname;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ledger
|
||||
|
|
|
|||
112
amount.cc
112
amount.cc
|
|
@ -10,6 +10,24 @@
|
|||
|
||||
namespace ledger {
|
||||
|
||||
static mpz_t full_divisor;
|
||||
static mpz_t true_value;
|
||||
|
||||
static class init_amounts
|
||||
{
|
||||
public:
|
||||
init_amounts() {
|
||||
mpz_init(full_divisor);
|
||||
mpz_init(true_value);
|
||||
mpz_ui_pow_ui(full_divisor, 10, MAX_PRECISION);
|
||||
mpz_mul_ui(true_value, full_divisor, 1);
|
||||
}
|
||||
~init_amounts() {
|
||||
mpz_clear(full_divisor);
|
||||
mpz_clear(true_value);
|
||||
}
|
||||
} initializer;
|
||||
|
||||
static void mpz_round(mpz_t out, mpz_t value, int precision)
|
||||
{
|
||||
mpz_t divisor;
|
||||
|
|
@ -52,7 +70,49 @@ static void mpz_round(mpz_t out, mpz_t value, int precision)
|
|||
mpz_clear(remainder);
|
||||
}
|
||||
|
||||
// destructor
|
||||
amount_t::amount_t(const bool value)
|
||||
: quantity(NULL), commodity(NULL)
|
||||
{
|
||||
if (value) {
|
||||
commodity = commodity_t::null_commodity;
|
||||
quantity = new MP_INT;
|
||||
mpz_init_set(MPZ(quantity), true_value);
|
||||
}
|
||||
}
|
||||
|
||||
amount_t::amount_t(const int value)
|
||||
: quantity(NULL), commodity(NULL)
|
||||
{
|
||||
if (value != 0) {
|
||||
_init();
|
||||
commodity = commodity_t::null_commodity;
|
||||
mpz_set_si(MPZ(quantity), value);
|
||||
mpz_mul(MPZ(quantity), MPZ(quantity), full_divisor);
|
||||
}
|
||||
}
|
||||
|
||||
amount_t::amount_t(const unsigned int value)
|
||||
: quantity(NULL), commodity(NULL)
|
||||
{
|
||||
if (value != 0) {
|
||||
_init();
|
||||
commodity = commodity_t::null_commodity;
|
||||
mpz_set_ui(MPZ(quantity), value);
|
||||
mpz_mul(MPZ(quantity), MPZ(quantity), full_divisor);
|
||||
}
|
||||
}
|
||||
|
||||
amount_t::amount_t(const double value)
|
||||
: quantity(NULL), commodity(NULL)
|
||||
{
|
||||
if (value != 0.0) {
|
||||
_init();
|
||||
commodity = commodity_t::null_commodity;
|
||||
mpz_set_d(MPZ(quantity), value);
|
||||
mpz_mul(MPZ(quantity), MPZ(quantity), full_divisor);
|
||||
}
|
||||
}
|
||||
|
||||
void amount_t::_clear()
|
||||
{
|
||||
mpz_clear(MPZ(quantity));
|
||||
|
|
@ -92,6 +152,22 @@ amount_t& amount_t::operator=(const amount_t& amt)
|
|||
return *this;
|
||||
}
|
||||
|
||||
amount_t& amount_t::operator=(const bool value)
|
||||
{
|
||||
if (! value) {
|
||||
if (quantity) {
|
||||
_clear();
|
||||
quantity = NULL;
|
||||
commodity = NULL;
|
||||
}
|
||||
} else {
|
||||
commodity = commodity_t::null_commodity;
|
||||
quantity = new MP_INT;
|
||||
mpz_init_set(MPZ(quantity), true_value);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
amount_t& amount_t::operator=(const int value)
|
||||
{
|
||||
if (value == 0) {
|
||||
|
|
@ -101,10 +177,9 @@ amount_t& amount_t::operator=(const int value)
|
|||
commodity = NULL;
|
||||
}
|
||||
} else {
|
||||
std::string str;
|
||||
std::ostringstream strstr(str);
|
||||
strstr << value;
|
||||
parse(strstr.str());
|
||||
commodity = commodity_t::null_commodity;
|
||||
mpz_set_si(MPZ(quantity), value);
|
||||
mpz_mul(MPZ(quantity), MPZ(quantity), full_divisor);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
|
@ -118,10 +193,9 @@ amount_t& amount_t::operator=(const unsigned int value)
|
|||
commodity = NULL;
|
||||
}
|
||||
} else {
|
||||
std::string str;
|
||||
std::ostringstream strstr(str);
|
||||
strstr << value;
|
||||
parse(strstr.str());
|
||||
commodity = commodity_t::null_commodity;
|
||||
mpz_set_ui(MPZ(quantity), value);
|
||||
mpz_mul(MPZ(quantity), MPZ(quantity), full_divisor);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
|
@ -135,10 +209,9 @@ amount_t& amount_t::operator=(const double value)
|
|||
commodity = NULL;
|
||||
}
|
||||
} else {
|
||||
std::string str;
|
||||
std::ostringstream strstr(str);
|
||||
strstr << value;
|
||||
parse(strstr.str());
|
||||
commodity = commodity_t::null_commodity;
|
||||
mpz_set_d(MPZ(quantity), value);
|
||||
mpz_mul(MPZ(quantity), MPZ(quantity), full_divisor);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
|
@ -279,7 +352,16 @@ amount_t::operator bool() const
|
|||
{
|
||||
if (quantity) {
|
||||
assert(commodity);
|
||||
return mpz_sgn(MPZ(round().quantity)) != 0;
|
||||
mpz_t temp;
|
||||
mpz_t divisor;
|
||||
mpz_init_set(temp, MPZ(quantity));
|
||||
mpz_init(divisor);
|
||||
mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - commodity->precision);
|
||||
mpz_tdiv_q(temp, temp, divisor);
|
||||
bool zero = mpz_sgn(temp) == 0;
|
||||
mpz_clear(divisor);
|
||||
mpz_clear(temp);
|
||||
return ! zero;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -682,6 +764,8 @@ void (*commodity_t::updater)(commodity_t * commodity,
|
|||
const std::time_t moment) = NULL;
|
||||
|
||||
commodities_map commodity_t::commodities;
|
||||
commodity_t * commodity_t::null_commodity =
|
||||
commodity_t::find_commodity("", true);
|
||||
|
||||
struct cleanup_commodities
|
||||
{
|
||||
|
|
|
|||
244
amount.h
Normal file
244
amount.h
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
#ifndef _AMOUNT_H
|
||||
#define _AMOUNT_H
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <ctime>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace ledger {
|
||||
|
||||
class commodity_t;
|
||||
|
||||
class amount_t
|
||||
{
|
||||
typedef void * base_type;
|
||||
|
||||
void _init();
|
||||
void _copy(const amount_t& amt);
|
||||
void _clear();
|
||||
|
||||
public:
|
||||
base_type quantity; // amount, to MAX_PRECISION
|
||||
commodity_t * commodity;
|
||||
|
||||
bool valid() const {
|
||||
if (quantity)
|
||||
return commodity != NULL;
|
||||
else
|
||||
return commodity == NULL;
|
||||
}
|
||||
|
||||
// constructors
|
||||
amount_t(commodity_t * _commodity = NULL)
|
||||
: quantity(NULL), commodity(_commodity) {}
|
||||
|
||||
amount_t(const amount_t& amt) : quantity(NULL) {
|
||||
if (amt.quantity)
|
||||
_copy(amt);
|
||||
else
|
||||
commodity = amt.commodity;
|
||||
}
|
||||
amount_t(const std::string& value) {
|
||||
_init();
|
||||
std::istringstream str(value);
|
||||
str >> *this;
|
||||
}
|
||||
amount_t(const bool value);
|
||||
amount_t(const int value);
|
||||
amount_t(const unsigned int value);
|
||||
amount_t(const double value);
|
||||
|
||||
// destructor
|
||||
~amount_t() {
|
||||
if (quantity)
|
||||
_clear();
|
||||
}
|
||||
|
||||
// assignment operator
|
||||
amount_t& operator=(const amount_t& amt);
|
||||
amount_t& operator=(const std::string& value);
|
||||
amount_t& operator=(const bool value);
|
||||
amount_t& operator=(const int value);
|
||||
amount_t& operator=(const unsigned int value);
|
||||
amount_t& operator=(const double value);
|
||||
|
||||
// general methods
|
||||
amount_t round(int precision = -1) const;
|
||||
|
||||
// in-place arithmetic
|
||||
amount_t& operator*=(const amount_t& amt);
|
||||
amount_t& operator/=(const amount_t& amt);
|
||||
amount_t& operator%=(const amount_t& amt);
|
||||
amount_t& operator+=(const amount_t& amt);
|
||||
amount_t& operator-=(const amount_t& amt);
|
||||
|
||||
// simple arithmetic
|
||||
amount_t operator*(const amount_t& amt) const {
|
||||
amount_t temp = *this;
|
||||
temp *= amt;
|
||||
return temp;
|
||||
}
|
||||
amount_t operator/(const amount_t& amt) const {
|
||||
amount_t temp = *this;
|
||||
temp /= amt;
|
||||
return temp;
|
||||
}
|
||||
amount_t operator%(const amount_t& amt) const {
|
||||
amount_t temp = *this;
|
||||
temp %= amt;
|
||||
return temp;
|
||||
}
|
||||
amount_t operator+(const amount_t& amt) const {
|
||||
amount_t temp = *this;
|
||||
temp += amt;
|
||||
return temp;
|
||||
}
|
||||
amount_t operator-(const amount_t& amt) const {
|
||||
amount_t temp = *this;
|
||||
temp -= amt;
|
||||
return temp;
|
||||
}
|
||||
|
||||
// unary negation
|
||||
amount_t& negate();
|
||||
amount_t negated() const {
|
||||
amount_t temp = *this;
|
||||
temp.negate();
|
||||
return temp;
|
||||
}
|
||||
amount_t operator-() const {
|
||||
return negated();
|
||||
}
|
||||
|
||||
// test for non-zero (use ! for zero)
|
||||
operator bool() const;
|
||||
|
||||
// comparisons to zero
|
||||
bool operator<(const int num) const;
|
||||
bool operator<=(const int num) const;
|
||||
bool operator>(const int num) const;
|
||||
bool operator>=(const int num) const;
|
||||
|
||||
// comparisons between amounts
|
||||
bool operator<(const amount_t& amt) const;
|
||||
bool operator<=(const amount_t& amt) const;
|
||||
bool operator>(const amount_t& amt) const;
|
||||
bool operator>=(const amount_t& amt) const;
|
||||
bool operator==(const amount_t& amt) const;
|
||||
bool operator!=(const amount_t& amt) const {
|
||||
if (commodity != amt.commodity)
|
||||
return true;
|
||||
return ! (*this == amt);
|
||||
}
|
||||
|
||||
amount_t value(const std::time_t moment) const;
|
||||
|
||||
operator std::string() const;
|
||||
|
||||
void parse(std::istream& in);
|
||||
void parse(const std::string& str) {
|
||||
std::istringstream stream(str);
|
||||
parse(stream);
|
||||
}
|
||||
|
||||
void write_quantity(std::ostream& out) const;
|
||||
void read_quantity(std::istream& in);
|
||||
|
||||
friend std::istream& operator>>(std::istream& in, amount_t& amt);
|
||||
};
|
||||
|
||||
void parse_quantity(std::istream& in, std::string& value);
|
||||
void parse_commodity(std::istream& in, std::string& symbol);
|
||||
|
||||
inline amount_t abs(const amount_t& amt) {
|
||||
return amt < 0 ? amt.negated() : amt;
|
||||
}
|
||||
|
||||
inline std::istream& operator>>(std::istream& in, amount_t& amt) {
|
||||
amt.parse(in);
|
||||
return in;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const amount_t& amt);
|
||||
|
||||
|
||||
#define COMMODITY_STYLE_DEFAULTS 0x00
|
||||
#define COMMODITY_STYLE_SUFFIXED 0x01
|
||||
#define COMMODITY_STYLE_SEPARATED 0x02
|
||||
#define COMMODITY_STYLE_EUROPEAN 0x04
|
||||
#define COMMODITY_STYLE_THOUSANDS 0x08
|
||||
#define COMMODITY_STYLE_CONSULTED 0x10
|
||||
#define COMMODITY_STYLE_NOMARKET 0x20
|
||||
|
||||
typedef std::map<const std::time_t, amount_t> history_map;
|
||||
typedef std::pair<const std::time_t, amount_t> history_pair;
|
||||
|
||||
typedef std::map<const std::string, commodity_t *> commodities_map;
|
||||
typedef std::pair<const std::string, commodity_t *> commodities_pair;
|
||||
|
||||
class commodity_t
|
||||
{
|
||||
public:
|
||||
std::string symbol;
|
||||
std::string name;
|
||||
std::string note;
|
||||
unsigned int precision;
|
||||
unsigned int flags;
|
||||
history_map history;
|
||||
amount_t conversion;
|
||||
unsigned long ident;
|
||||
|
||||
// If set, this global function pointer is called to determine
|
||||
// whether prices have been updated in the meanwhile.
|
||||
|
||||
static void (*updater)(commodity_t * commodity,
|
||||
const std::time_t date,
|
||||
const amount_t& price,
|
||||
const std::time_t moment);
|
||||
|
||||
// This map remembers all commodities that have been
|
||||
// defined thus far.
|
||||
|
||||
static commodities_map commodities;
|
||||
static commodity_t * null_commodity;
|
||||
|
||||
static void add_commodity(commodity_t * commodity,
|
||||
const std::string symbol = "") {
|
||||
commodities.insert(commodities_pair((symbol.empty() ?
|
||||
commodity->symbol : symbol),
|
||||
commodity));
|
||||
}
|
||||
static bool remove_commodity(commodity_t * commodity) {
|
||||
commodities_map::size_type n = commodities.erase(commodity->symbol);
|
||||
return n > 0;
|
||||
}
|
||||
static commodity_t * find_commodity(const std::string& symbol,
|
||||
bool auto_create = false);
|
||||
|
||||
// Now the per-object constructor and methods
|
||||
|
||||
commodity_t(const std::string& _symbol = "",
|
||||
unsigned int _precision = 2,
|
||||
unsigned int _flags = COMMODITY_STYLE_DEFAULTS)
|
||||
: symbol(_symbol), precision(_precision), flags(_flags) {}
|
||||
|
||||
void add_price(const std::time_t date, const amount_t& price) {
|
||||
history.insert(history_pair(date, price));
|
||||
}
|
||||
bool remove_price(const std::time_t date) {
|
||||
history_map::size_type n = history.erase(date);
|
||||
return n > 0;
|
||||
}
|
||||
|
||||
void set_conversion(const amount_t& price) {
|
||||
conversion = price;
|
||||
}
|
||||
|
||||
amount_t value(const std::time_t moment = std::time(NULL));
|
||||
};
|
||||
|
||||
} // namespace ledger
|
||||
|
||||
#endif // _AMOUNT_H
|
||||
28
autoxact.cc
Normal file
28
autoxact.cc
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#include "autoxact.h"
|
||||
|
||||
namespace ledger {
|
||||
|
||||
void automated_transaction_t::extend_entry(entry_t * entry)
|
||||
{
|
||||
for (transactions_list::iterator i = entry->transactions.begin();
|
||||
i != entry->transactions.end();
|
||||
i++)
|
||||
if (matches(masks, *((*i)->account))) {
|
||||
for (transactions_list::iterator t = transactions.begin();
|
||||
t != transactions.end();
|
||||
t++) {
|
||||
amount_t amt;
|
||||
if ((*t)->amount.commodity->symbol.empty())
|
||||
amt = (*i)->amount * (*t)->amount;
|
||||
else
|
||||
amt = (*t)->amount;
|
||||
|
||||
transaction_t * xact
|
||||
= new transaction_t(entry, (*t)->account, amt, amt,
|
||||
(*t)->flags | TRANSACTION_AUTO);
|
||||
entry->add_transaction(xact);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ledger
|
||||
79
autoxact.h
Normal file
79
autoxact.h
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
#ifndef _AUTOXACT_H
|
||||
#define _AUTOXACT_H
|
||||
|
||||
#include "ledger.h"
|
||||
#include "valexpr.h"
|
||||
|
||||
#include <deque>
|
||||
|
||||
namespace ledger {
|
||||
|
||||
class automated_transaction_t
|
||||
{
|
||||
public:
|
||||
masks_list masks;
|
||||
transactions_list transactions;
|
||||
|
||||
automated_transaction_t(masks_list& _masks,
|
||||
transactions_list& _transactions) {
|
||||
masks.insert(masks.begin(), _masks.begin(), _masks.end());
|
||||
transactions.insert(transactions.begin(),
|
||||
_transactions.begin(), _transactions.end());
|
||||
// Take over ownership of the pointers
|
||||
_transactions.clear();
|
||||
}
|
||||
|
||||
~automated_transaction_t() {
|
||||
for (transactions_list::iterator i = transactions.begin();
|
||||
i != transactions.end();
|
||||
i++)
|
||||
delete *i;
|
||||
}
|
||||
|
||||
void extend_entry(entry_t * entry);
|
||||
};
|
||||
|
||||
|
||||
typedef std::deque<automated_transaction_t *> automated_transactions_deque;
|
||||
|
||||
class automated_transactions_t
|
||||
{
|
||||
public:
|
||||
automated_transactions_deque automated_transactions;
|
||||
|
||||
~automated_transactions_t() {
|
||||
for (automated_transactions_deque::iterator i
|
||||
= automated_transactions.begin();
|
||||
i != automated_transactions.end();
|
||||
i++)
|
||||
delete *i;
|
||||
}
|
||||
|
||||
void extend_entry(entry_t * entry) {
|
||||
for (automated_transactions_deque::iterator i
|
||||
= automated_transactions.begin();
|
||||
i != automated_transactions.end();
|
||||
i++)
|
||||
(*i)->extend_entry(entry);
|
||||
}
|
||||
|
||||
void add_automated_transaction(automated_transaction_t * auto_xact) {
|
||||
automated_transactions.push_back(auto_xact);
|
||||
}
|
||||
bool remove_automated_transaction(automated_transaction_t * auto_xact) {
|
||||
for (automated_transactions_deque::iterator i
|
||||
= automated_transactions.begin();
|
||||
i != automated_transactions.end();
|
||||
i++) {
|
||||
if (*i == auto_xact) {
|
||||
automated_transactions.erase(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ledger
|
||||
|
||||
#endif // _AUTOXACT_H
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
#include "ledger.h"
|
||||
#include "balance.h"
|
||||
|
||||
namespace ledger {
|
||||
|
||||
|
|
@ -86,7 +85,6 @@ void balance_t::write(std::ostream& out,
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
balance_pair_t::balance_pair_t(const transaction_t& xact)
|
||||
: quantity(xact.amount), cost(xact.cost) {}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
#ifndef _BALANCE_H
|
||||
#define _BALANCE_H
|
||||
|
||||
#include "ledger.h"
|
||||
#include <map>
|
||||
#include <ctime>
|
||||
#include <iostream>
|
||||
|
||||
#include "amount.h"
|
||||
|
||||
namespace ledger {
|
||||
|
||||
|
|
@ -349,6 +353,7 @@ inline std::ostream& operator<<(std::ostream& out, const balance_t& bal) {
|
|||
}
|
||||
#endif
|
||||
|
||||
class transaction_t;
|
||||
|
||||
class balance_pair_t
|
||||
{
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
namespace ledger {
|
||||
|
||||
unsigned long magic_number = 0xFFEED765;
|
||||
unsigned long binary_magic_number = 0xFFEED765;
|
||||
static unsigned long format_version = 0x00020009;
|
||||
|
||||
static char buf[4096];
|
||||
|
|
@ -292,7 +292,7 @@ unsigned int read_binary_ledger(std::istream& in,
|
|||
|
||||
unsigned long magic;
|
||||
in.read((char *)&magic, sizeof(magic));
|
||||
if (magic != magic_number)
|
||||
if (magic != binary_magic_number)
|
||||
return 0;
|
||||
|
||||
#ifdef DEBUG
|
||||
|
|
@ -560,7 +560,7 @@ void write_binary_account(std::ostream& out, account_t * account)
|
|||
void write_binary_ledger(std::ostream& out, ledger_t * ledger,
|
||||
const std::string& leader)
|
||||
{
|
||||
out.write((char *)&magic_number, sizeof(magic_number));
|
||||
out.write((char *)&binary_magic_number, sizeof(binary_magic_number));
|
||||
|
||||
#ifdef DEBUG
|
||||
{
|
||||
|
|
|
|||
2
binary.h
2
binary.h
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
namespace ledger {
|
||||
|
||||
extern unsigned long magic_number;
|
||||
extern unsigned long binary_magic_number;
|
||||
|
||||
extern unsigned int read_binary_ledger(std::istream& in,
|
||||
const std::string& leader,
|
||||
|
|
|
|||
125
datetime.cc
Normal file
125
datetime.cc
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
#include "datetime.h"
|
||||
|
||||
#include <ctime>
|
||||
|
||||
namespace ledger {
|
||||
|
||||
static std::time_t now = std::time(NULL);
|
||||
struct std::tm * now_tm = std::localtime(&now);
|
||||
|
||||
static std::time_t base = -1;
|
||||
static int base_year = -1;
|
||||
|
||||
static const int month_days[12] = {
|
||||
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
|
||||
};
|
||||
|
||||
static const char * formats[] = {
|
||||
"%Y/%m/%d",
|
||||
"%m/%d",
|
||||
"%Y.%m.%d",
|
||||
"%m.%d",
|
||||
"%Y-%m-%d",
|
||||
"%m-%d",
|
||||
"%a",
|
||||
"%A",
|
||||
"%b",
|
||||
"%B",
|
||||
"%Y",
|
||||
NULL
|
||||
};
|
||||
|
||||
bool parse_date_mask(const char * date_str, struct std::tm * result)
|
||||
{
|
||||
for (const char ** f = formats; *f; f++) {
|
||||
memset(result, INT_MAX, sizeof(struct std::tm));
|
||||
if (strptime(date_str, *f, result))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool parse_date(const char * date_str, std::time_t * result, const int year)
|
||||
{
|
||||
struct std::tm when;
|
||||
|
||||
if (! parse_date_mask(date_str, &when))
|
||||
return false;
|
||||
|
||||
when.tm_hour = 0;
|
||||
when.tm_min = 0;
|
||||
when.tm_sec = 0;
|
||||
|
||||
if (when.tm_year == -1)
|
||||
when.tm_year = ((year == -1) ? now_tm->tm_year : (year - 1900));
|
||||
|
||||
if (when.tm_mon == -1)
|
||||
when.tm_mon = 0;
|
||||
|
||||
if (when.tm_mday == -1)
|
||||
when.tm_mday = 1;
|
||||
|
||||
*result = std::mktime(&when);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool quick_parse_date(char * date_str, std::time_t * result)
|
||||
{
|
||||
int year = -1, month = -1, day, num = 0;
|
||||
|
||||
for (char * p = date_str; *p; p++) {
|
||||
if (*p == '/' || *p == '-' || *p == '.') {
|
||||
if (year == -1)
|
||||
year = num;
|
||||
else
|
||||
month = num;
|
||||
num = 0;
|
||||
}
|
||||
else if (*p < '0' || *p > '9') {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
num *= 10;
|
||||
num += *p - '0';
|
||||
}
|
||||
}
|
||||
|
||||
day = num;
|
||||
|
||||
if (month == -1) {
|
||||
month = year;
|
||||
year = -1;
|
||||
}
|
||||
|
||||
if (base == -1 || year != base_year) {
|
||||
struct std::tm when;
|
||||
|
||||
when.tm_hour = 0;
|
||||
when.tm_min = 0;
|
||||
when.tm_sec = 0;
|
||||
|
||||
base_year = year == -1 ? now_tm->tm_year + 1900 : year;
|
||||
when.tm_year = year == -1 ? now_tm->tm_year : year - 1900;
|
||||
when.tm_mon = 0;
|
||||
when.tm_mday = 1;
|
||||
|
||||
base = std::mktime(&when);
|
||||
}
|
||||
|
||||
*result = base;
|
||||
|
||||
--month;
|
||||
while (--month >= 0) {
|
||||
*result += month_days[month] * 24 * 60 * 60;
|
||||
if (month == 1 && year % 4 == 0 && year != 2000) // february in leap years
|
||||
*result += 24 * 60 * 60;
|
||||
}
|
||||
|
||||
if (--day)
|
||||
*result += day * 24 * 60 * 60;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ledger
|
||||
19
datetime.h
Normal file
19
datetime.h
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#ifndef _DATETIME_H
|
||||
#define _DATETIME_H
|
||||
|
||||
#include "ledger.h"
|
||||
|
||||
namespace ledger {
|
||||
|
||||
extern bool parse_date_mask(const char * date_str, struct std::tm * result);
|
||||
|
||||
extern bool parse_date(const char * date_str, std::time_t * result,
|
||||
const int year = -1);
|
||||
|
||||
extern bool quick_parse_date(char * date_str, std::time_t * result);
|
||||
|
||||
extern struct std::tm * now_tm;
|
||||
|
||||
} // namespace ledger
|
||||
|
||||
#endif // _DATETIME_H
|
||||
21
error.h
21
error.h
|
|
@ -1,8 +1,19 @@
|
|||
#ifndef _ERROR_H
|
||||
#define _ERROR_H
|
||||
|
||||
#include "ledger.h"
|
||||
|
||||
#include <exception>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#ifdef DEBUG
|
||||
#include <cassert>
|
||||
#else
|
||||
#ifdef assert
|
||||
#undef assert
|
||||
#endif
|
||||
#define assert(x)
|
||||
#endif
|
||||
|
||||
namespace ledger {
|
||||
|
||||
|
|
@ -49,13 +60,9 @@ class parse_error : public error
|
|||
: error(reason), line(_line), file(_file) {}
|
||||
virtual ~parse_error() throw() {}
|
||||
|
||||
virtual const char* what() const throw() {
|
||||
static std::ostringstream msg;
|
||||
msg << file << ", line " << line << ": " << error::what();
|
||||
return msg.str().c_str();
|
||||
}
|
||||
virtual const char* what() const throw();
|
||||
};
|
||||
|
||||
} // namespace ledger
|
||||
|
||||
#endif // _CONSTRAINT_H
|
||||
#endif // _ERROR_H
|
||||
|
|
|
|||
134
format.cc
134
format.cc
|
|
@ -14,13 +14,18 @@ std::string truncated(const std::string& str, unsigned int width)
|
|||
return buf;
|
||||
}
|
||||
|
||||
std::string maximal_account_name(const item_t * item, const item_t * parent)
|
||||
std::string partial_account_name(const account_t * account,
|
||||
const unsigned int start_depth)
|
||||
{
|
||||
std::string name = item->account->name;
|
||||
for (const item_t * i = item->parent;
|
||||
i && i->account && i != parent;
|
||||
i = i->parent)
|
||||
name = i->account->name + ":" + name;
|
||||
std::string name = account->name;
|
||||
const account_t * acct = account->parent;
|
||||
|
||||
for (int i = account->depth - start_depth - 1;
|
||||
--i >= 0 && acct->parent; ) {
|
||||
assert(acct);
|
||||
name = acct->name + ":" + name;
|
||||
acct = acct->parent;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
|
|
@ -139,14 +144,10 @@ element_t * format_t::parse_elements(const std::string& fmt)
|
|||
return result;
|
||||
}
|
||||
|
||||
void format_t::format_elements(std::ostream& out, const item_t * item,
|
||||
const item_t * displayed_parent) const
|
||||
void format_t::format_elements(std::ostream& out,
|
||||
const details_t& details) const
|
||||
{
|
||||
std::string result;
|
||||
|
||||
for (const element_t * elem = elements;
|
||||
elem;
|
||||
elem = elem->next) {
|
||||
for (const element_t * elem = elements.get(); elem; elem = elem->next) {
|
||||
if (elem->align_left)
|
||||
out << std::left;
|
||||
else
|
||||
|
|
@ -161,16 +162,18 @@ void format_t::format_elements(std::ostream& out, const item_t * item,
|
|||
break;
|
||||
|
||||
case element_t::VALUE_EXPR: {
|
||||
balance_t value = elem->val_expr->compute(item);
|
||||
value.write(out, elem->min_width,
|
||||
elem->max_width > 0 ? elem->max_width : elem->min_width);
|
||||
balance_t value;
|
||||
elem->val_expr->compute(value, details);
|
||||
value.write(out, elem->min_width, (elem->max_width > 0 ?
|
||||
elem->max_width : elem->min_width));
|
||||
break;
|
||||
}
|
||||
|
||||
case element_t::DATE_STRING:
|
||||
if (item->date != -1) {
|
||||
if (details.entry && details.entry->date != -1) {
|
||||
char buf[256];
|
||||
std::strftime(buf, 255, elem->chars.c_str(), std::gmtime(&item->date));
|
||||
std::strftime(buf, 255, elem->chars.c_str(),
|
||||
std::gmtime(&details.entry->date));
|
||||
out << (elem->max_width == 0 ? buf : truncated(buf, elem->max_width));
|
||||
} else {
|
||||
out << " ";
|
||||
|
|
@ -178,35 +181,38 @@ void format_t::format_elements(std::ostream& out, const item_t * item,
|
|||
break;
|
||||
|
||||
case element_t::CLEARED:
|
||||
if (item->state == entry_t::CLEARED)
|
||||
if (details.entry && details.entry->state == entry_t::CLEARED)
|
||||
out << "* ";
|
||||
else
|
||||
out << "";
|
||||
break;
|
||||
|
||||
case element_t::CODE:
|
||||
if (! item->code.empty())
|
||||
out << "(" << item->code << ") ";
|
||||
if (details.entry && ! details.entry->code.empty())
|
||||
out << "(" << details.entry->code << ") ";
|
||||
else
|
||||
out << "";
|
||||
break;
|
||||
|
||||
case element_t::PAYEE:
|
||||
if (details.entry)
|
||||
out << (elem->max_width == 0 ?
|
||||
item->payee : truncated(item->payee, elem->max_width));
|
||||
details.entry->payee : truncated(details.entry->payee,
|
||||
elem->max_width));
|
||||
break;
|
||||
|
||||
case element_t::ACCOUNT_NAME:
|
||||
case element_t::ACCOUNT_FULLNAME:
|
||||
if (item->account) {
|
||||
if (details.account) {
|
||||
std::string name = (elem->type == element_t::ACCOUNT_FULLNAME ?
|
||||
item->account->fullname() :
|
||||
maximal_account_name(item, displayed_parent));
|
||||
details.account->fullname() :
|
||||
partial_account_name(details.account,
|
||||
details.depth));
|
||||
if (elem->max_width > 0)
|
||||
name = truncated(name, elem->max_width);
|
||||
|
||||
if (item->flags & TRANSACTION_VIRTUAL) {
|
||||
if (item->flags & TRANSACTION_BALANCE)
|
||||
if (details.xact && details.xact->flags & TRANSACTION_VIRTUAL) {
|
||||
if (details.xact->flags & TRANSACTION_BALANCE)
|
||||
name = "[" + name + "]";
|
||||
else
|
||||
name = "(" + name + ")";
|
||||
|
|
@ -218,81 +224,67 @@ void format_t::format_elements(std::ostream& out, const item_t * item,
|
|||
break;
|
||||
|
||||
case element_t::OPT_AMOUNT: {
|
||||
if (! details.entry || ! details.xact)
|
||||
break;
|
||||
|
||||
std::string disp;
|
||||
bool use_disp = false;
|
||||
|
||||
if (std::find(displayed_parent->subitems.begin(),
|
||||
displayed_parent->subitems.end(), item) !=
|
||||
displayed_parent->subitems.end()) {
|
||||
if (displayed_parent->subitems.size() == 2 &&
|
||||
item == displayed_parent->subitems.back() &&
|
||||
(displayed_parent->subitems.front()->value.quantity ==
|
||||
displayed_parent->subitems.front()->value.cost) &&
|
||||
(displayed_parent->subitems.front()->value ==
|
||||
- displayed_parent->subitems.back()->value)) {
|
||||
if (std::find(details.entry->transactions.begin(),
|
||||
details.entry->transactions.end(), details.xact) !=
|
||||
details.entry->transactions.end()) {
|
||||
if (details.entry->transactions.size() == 2 &&
|
||||
details.xact == details.entry->transactions.back() &&
|
||||
(details.entry->transactions.front()->amount ==
|
||||
details.entry->transactions.front()->cost) &&
|
||||
(details.entry->transactions.front()->amount ==
|
||||
- details.entry->transactions.back()->amount)) {
|
||||
use_disp = true;
|
||||
}
|
||||
else if (displayed_parent->subitems.size() != 2 &&
|
||||
item->value.quantity != item->value.cost &&
|
||||
item->value.quantity.amounts.size() == 1 &&
|
||||
item->value.cost.amounts.size() == 1 &&
|
||||
((*item->value.quantity.amounts.begin()).first !=
|
||||
(*item->value.cost.amounts.begin()).first)) {
|
||||
amount_t unit_cost
|
||||
= ((*item->value.cost.amounts.begin()).second /
|
||||
(*item->value.quantity.amounts.begin()).second);
|
||||
else if (details.entry->transactions.size() != 2 &&
|
||||
details.xact->amount != details.xact->cost) {
|
||||
amount_t unit_cost = details.xact->cost / details.xact->amount;
|
||||
std::ostringstream stream;
|
||||
stream << item->value.quantity << " @ " << unit_cost;
|
||||
stream << details.xact->amount << " @ " << unit_cost;
|
||||
disp = stream.str();
|
||||
use_disp = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (use_disp)
|
||||
if (! use_disp)
|
||||
disp = std::string(details.xact->amount);
|
||||
out << disp;
|
||||
else
|
||||
item->value.quantity.write(out, elem->min_width,
|
||||
elem->max_width > 0 ?
|
||||
elem->max_width : elem->min_width);
|
||||
|
||||
// jww (2004-07-31): this should be handled differently
|
||||
if (! item->note.empty())
|
||||
out << " ; " << item->note;
|
||||
if (! details.xact->note.empty())
|
||||
out << " ; " << details.xact->note;
|
||||
break;
|
||||
}
|
||||
|
||||
case element_t::VALUE: {
|
||||
balance_t value = compute_value(item);
|
||||
value.write(out, elem->min_width,
|
||||
elem->max_width > 0 ? elem->max_width : elem->min_width);
|
||||
balance_t value;
|
||||
compute_value(value, details);
|
||||
value.write(out, elem->min_width, (elem->max_width > 0 ?
|
||||
elem->max_width : elem->min_width));
|
||||
break;
|
||||
}
|
||||
|
||||
case element_t::TOTAL: {
|
||||
balance_t value = compute_total(item);
|
||||
value.write(out, elem->min_width,
|
||||
elem->max_width > 0 ? elem->max_width : elem->min_width);
|
||||
balance_t value;
|
||||
compute_total(value, details);
|
||||
value.write(out, elem->min_width, (elem->max_width > 0 ?
|
||||
elem->max_width : elem->min_width));
|
||||
break;
|
||||
}
|
||||
|
||||
case element_t::SPACER: {
|
||||
int depth = 0;
|
||||
for (const item_t * i = item; i->parent; i = i->parent)
|
||||
depth++;
|
||||
|
||||
for (const item_t * i = item->parent;
|
||||
i && i->account && i != displayed_parent;
|
||||
i = i->parent)
|
||||
depth--;
|
||||
|
||||
while (--depth >= 0) {
|
||||
case element_t::SPACER:
|
||||
for (unsigned int i = 0; i < details.depth; i++) {
|
||||
if (elem->min_width > 0 || elem->max_width > 0)
|
||||
out.width(elem->min_width > elem->max_width ?
|
||||
elem->min_width : elem->max_width);
|
||||
out << " ";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
|
|
|
|||
28
format.h
28
format.h
|
|
@ -2,13 +2,14 @@
|
|||
#define _FORMAT_H
|
||||
|
||||
#include "ledger.h"
|
||||
#include "balance.h"
|
||||
#include "expr.h"
|
||||
#include "valexpr.h"
|
||||
|
||||
namespace ledger {
|
||||
|
||||
std::string truncated(const std::string& str, unsigned int width);
|
||||
std::string maximal_account_name(const item_t * item, const item_t * parent);
|
||||
|
||||
std::string partial_account_name(const account_t * account,
|
||||
const unsigned int start_depth);
|
||||
|
||||
struct element_t
|
||||
{
|
||||
|
|
@ -48,28 +49,27 @@ struct element_t
|
|||
|
||||
struct format_t
|
||||
{
|
||||
element_t * elements;
|
||||
std::auto_ptr<element_t> elements;
|
||||
|
||||
static std::auto_ptr<node_t> value_expr;
|
||||
static std::auto_ptr<node_t> total_expr;
|
||||
|
||||
format_t(const std::string& _format) {
|
||||
elements = parse_elements(_format);
|
||||
}
|
||||
~format_t() {
|
||||
if (elements) delete elements;
|
||||
elements.reset(parse_elements(_format));
|
||||
}
|
||||
|
||||
static element_t * parse_elements(const std::string& fmt);
|
||||
|
||||
void format_elements(std::ostream& out, const item_t * item,
|
||||
const item_t * displayed_parent = NULL) const;
|
||||
void format_elements(std::ostream& out, const details_t& details) const;
|
||||
|
||||
static balance_t compute_value(const item_t * item) {
|
||||
return value_expr.get() ? value_expr->compute(item) : balance_t();
|
||||
static void compute_value(balance_t& result, const details_t& details) {
|
||||
if (value_expr.get())
|
||||
value_expr->compute(result, details);
|
||||
}
|
||||
static balance_t compute_total(const item_t * item) {
|
||||
return total_expr.get() ? total_expr->compute(item) : balance_t();
|
||||
|
||||
static void compute_total(balance_t& result, const details_t& details) {
|
||||
if (total_expr.get())
|
||||
total_expr->compute(result, details);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
196
item.cc
196
item.cc
|
|
@ -1,196 +0,0 @@
|
|||
#include "item.h"
|
||||
#include "expr.h"
|
||||
|
||||
namespace ledger {
|
||||
|
||||
static inline void sum_items(const item_t * top,
|
||||
const bool show_subtotals,
|
||||
item_t * item)
|
||||
{
|
||||
if (top->account == item->account) {
|
||||
item->value += top->value;
|
||||
if (show_subtotals)
|
||||
item->total += top->value;
|
||||
}
|
||||
|
||||
for (items_deque::const_iterator i = top->subitems.begin();
|
||||
i != top->subitems.end();
|
||||
i++)
|
||||
sum_items(*i, show_subtotals, item);
|
||||
}
|
||||
|
||||
item_t * walk_accounts(const item_t * top,
|
||||
account_t * account,
|
||||
const node_t * predicate,
|
||||
const bool show_subtotals,
|
||||
const bool show_flattened)
|
||||
{
|
||||
item_t * item = new item_t;
|
||||
item->account = account;
|
||||
|
||||
if (top) {
|
||||
sum_items(top, show_subtotals, item);
|
||||
} else {
|
||||
std::time_t latest = 0;
|
||||
for (transactions_list::iterator i
|
||||
= std::find_if(account->transactions.begin(),
|
||||
account->transactions.end(),
|
||||
value_predicate(predicate));
|
||||
i != account->transactions.end();
|
||||
i = std::find_if(++i, account->transactions.end(),
|
||||
value_predicate(predicate))) {
|
||||
if (std::difftime(latest, (*i)->entry->date) < 0)
|
||||
latest = (*i)->entry->date;
|
||||
|
||||
item->value += *(*i);
|
||||
if (show_subtotals)
|
||||
item->total += *(*i);
|
||||
}
|
||||
item->date = latest;
|
||||
}
|
||||
|
||||
for (accounts_map::iterator i = account->accounts.begin();
|
||||
i != account->accounts.end();
|
||||
i++) {
|
||||
std::auto_ptr<item_t>
|
||||
subitem(walk_accounts(top, (*i).second, predicate, show_subtotals,
|
||||
show_flattened));
|
||||
subitem->parent = item;
|
||||
|
||||
if (std::difftime(item->date, subitem->date) < 0)
|
||||
item->date = subitem->date;
|
||||
|
||||
if (show_flattened) {
|
||||
item_t * ptr = item;
|
||||
balance_pair_t total;
|
||||
|
||||
for (items_deque::const_iterator i = subitem->subitems.begin();
|
||||
i != subitem->subitems.end();
|
||||
i++)
|
||||
if (show_subtotals ? (*i)->total : (*i)->value) {
|
||||
if (! account->parent) {
|
||||
if (! total) {
|
||||
item_t * temp = new item_t;
|
||||
temp->date = top ? top->date : item->date;
|
||||
temp->payee = "Opening balance";
|
||||
item->subitems.push_back(temp);
|
||||
ptr = temp;
|
||||
}
|
||||
total += show_subtotals ? (*i)->total : (*i)->value;
|
||||
}
|
||||
|
||||
ptr->subitems.push_back(new item_t(*i));
|
||||
ptr->subitems.back()->date = ptr->date;
|
||||
ptr->subitems.back()->payee = ptr->payee;
|
||||
}
|
||||
|
||||
if (total) {
|
||||
item_t * temp = new item_t;
|
||||
temp->date = ptr->date;
|
||||
temp->payee = ptr->payee;
|
||||
temp->account = account->find_account("Equity:Opening Balances");
|
||||
temp->value = total;
|
||||
temp->value.negate();
|
||||
ptr->subitems.push_back(temp);
|
||||
}
|
||||
}
|
||||
|
||||
if (show_subtotals)
|
||||
item->total += subitem->total;
|
||||
|
||||
if ((! show_flattened || account->parent) &&
|
||||
show_subtotals ? subitem->total : subitem->value)
|
||||
item->subitems.push_back(subitem.release());
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
item_t * walk_entries(entries_list::const_iterator begin,
|
||||
entries_list::const_iterator end,
|
||||
const node_t * predicate,
|
||||
const bool show_related,
|
||||
const bool show_inverted)
|
||||
{
|
||||
unsigned int count = 0;
|
||||
item_t * result = NULL;
|
||||
value_predicate pred_obj(predicate);
|
||||
|
||||
for (entries_list::const_iterator i = std::find_if(begin, end, pred_obj);
|
||||
i != end;
|
||||
i = std::find_if(++i, end, pred_obj)) {
|
||||
transactions_list reckoned;
|
||||
item_t * item = NULL;
|
||||
|
||||
for (transactions_list::const_iterator j
|
||||
= std::find_if((*i)->transactions.begin(),
|
||||
(*i)->transactions.end(), pred_obj);
|
||||
j != (*i)->transactions.end();
|
||||
j = std::find_if(++j,
|
||||
transactions_list::const_iterator((*i)->transactions.end()),
|
||||
pred_obj)) {
|
||||
assert(*i == (*j)->entry);
|
||||
|
||||
if (! item) {
|
||||
item = new item_t(*i);
|
||||
item->index = count++;
|
||||
}
|
||||
|
||||
// If show_inverted is true, it implies show_related.
|
||||
if (! show_inverted &&
|
||||
std::find(reckoned.begin(),
|
||||
reckoned.end(), *j) == reckoned.end()) {
|
||||
item->add_item(new item_t(*j));
|
||||
reckoned.push_back(*j);
|
||||
}
|
||||
|
||||
if (show_related)
|
||||
for (transactions_list::iterator k = (*i)->transactions.begin();
|
||||
k != (*i)->transactions.end();
|
||||
k++) {
|
||||
if (*k == *j || ((*k)->flags & TRANSACTION_AUTO) ||
|
||||
std::find(reckoned.begin(),
|
||||
reckoned.end(), *k) != reckoned.end())
|
||||
continue;
|
||||
|
||||
item->add_item(new item_t(*k));
|
||||
if (show_inverted)
|
||||
item->subitems.back()->value.negate();
|
||||
reckoned.push_back(*k);
|
||||
}
|
||||
}
|
||||
|
||||
if (item) {
|
||||
if (! result)
|
||||
result = new item_t;
|
||||
item->parent = result;
|
||||
result->subitems.push_back(item);
|
||||
|
||||
if (std::difftime(result->date, item->date) < 0)
|
||||
result->date = item->date;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
struct cmp_items {
|
||||
const node_t * sort_order;
|
||||
|
||||
cmp_items(const node_t * _sort_order) : sort_order(_sort_order) {
|
||||
assert(sort_order);
|
||||
}
|
||||
|
||||
bool operator()(const item_t * left, const item_t * right) const {
|
||||
assert(left);
|
||||
assert(right);
|
||||
return sort_order->compute(left) < sort_order->compute(right);
|
||||
}
|
||||
};
|
||||
|
||||
void item_t::sort(const node_t * sort_order)
|
||||
{
|
||||
std::stable_sort(subitems.begin(), subitems.end(), cmp_items(sort_order));
|
||||
}
|
||||
|
||||
} // namespace ledger
|
||||
82
item.h
82
item.h
|
|
@ -1,82 +0,0 @@
|
|||
#ifndef _ITEM_H
|
||||
#define _ITEM_H
|
||||
|
||||
#include "ledger.h"
|
||||
#include "balance.h"
|
||||
|
||||
#include <deque>
|
||||
|
||||
namespace ledger {
|
||||
|
||||
struct node_t;
|
||||
struct item_t;
|
||||
typedef std::deque<item_t *> items_deque;
|
||||
|
||||
struct item_t
|
||||
{
|
||||
struct item_t * parent;
|
||||
items_deque subitems;
|
||||
|
||||
unsigned int index;
|
||||
std::time_t date;
|
||||
entry_t::entry_state_t state;
|
||||
std::string code;
|
||||
std::string payee;
|
||||
unsigned int flags;
|
||||
const account_t * account;
|
||||
balance_pair_t value;
|
||||
balance_pair_t total;
|
||||
std::string note;
|
||||
|
||||
item_t() : parent(NULL), index(0), date(-1),
|
||||
state(entry_t::UNCLEARED), flags(0), account(NULL) {}
|
||||
|
||||
item_t(const item_t * item)
|
||||
: parent(NULL), index(0), date(item->date), state(item->state),
|
||||
code(item->code), payee(item->payee), flags(item->flags),
|
||||
account(item->account), value(item->value), total(item->total),
|
||||
note(item->note) {}
|
||||
|
||||
item_t(const entry_t * entry)
|
||||
: parent(NULL), index(0), date(entry->date), state(entry->state),
|
||||
code(entry->code), payee(entry->payee) {}
|
||||
|
||||
item_t(const transaction_t * xact)
|
||||
: parent(NULL), index(0), date(xact->entry->date),
|
||||
state(xact->entry->state), code(xact->entry->code),
|
||||
payee(xact->entry->payee), flags(xact->flags),
|
||||
account(xact->account), value(*xact), note(xact->note) {}
|
||||
|
||||
~item_t() {
|
||||
for (items_deque::iterator i = subitems.begin();
|
||||
i != subitems.end();
|
||||
i++)
|
||||
delete *i;
|
||||
}
|
||||
|
||||
void add_item(item_t * item) {
|
||||
item->parent = this;
|
||||
value += item->value;
|
||||
subitems.push_back(item);
|
||||
}
|
||||
|
||||
void sort(const node_t * sort_order);
|
||||
};
|
||||
|
||||
struct node_t;
|
||||
|
||||
item_t * walk_accounts(const item_t * top,
|
||||
account_t * account,
|
||||
const node_t * predicate = NULL,
|
||||
const bool show_subtotals = true,
|
||||
const bool show_flattened = false);
|
||||
|
||||
item_t * walk_entries(entries_list::const_iterator begin,
|
||||
entries_list::const_iterator end,
|
||||
const node_t * predicate = NULL,
|
||||
const bool show_related = false,
|
||||
const bool show_inverted = false);
|
||||
|
||||
} // namespace report
|
||||
|
||||
#endif // _REPORT_H
|
||||
28
ledger.cc
28
ledger.cc
|
|
@ -1,5 +1,6 @@
|
|||
#include "ledger.h"
|
||||
#include "expr.h"
|
||||
#include "valexpr.h"
|
||||
#include "datetime.h"
|
||||
#include "textual.h"
|
||||
#include "binary.h"
|
||||
|
||||
|
|
@ -9,6 +10,29 @@ namespace ledger {
|
|||
|
||||
const std::string version = "2.0b";
|
||||
|
||||
#if 0
|
||||
|
||||
struct cmp_items {
|
||||
const node_t * sort_order;
|
||||
|
||||
cmp_items(const node_t * _sort_order) : sort_order(_sort_order) {
|
||||
assert(sort_order);
|
||||
}
|
||||
|
||||
bool operator()(const item_t * left, const item_t * right) const {
|
||||
assert(left);
|
||||
assert(right);
|
||||
return sort_order->compute(left) < sort_order->compute(right);
|
||||
}
|
||||
};
|
||||
|
||||
void item_t::sort(const node_t * sort_order)
|
||||
{
|
||||
std::stable_sort(subitems.begin(), subitems.end(), cmp_items(sort_order));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
ledger_t::~ledger_t()
|
||||
{
|
||||
delete master;
|
||||
|
|
@ -200,7 +224,7 @@ int parse_ledger_file(char * p, ledger_t * journal)
|
|||
stream.read((char *)&magic, sizeof(magic));
|
||||
stream.seekg(start);
|
||||
|
||||
if (magic == magic_number)
|
||||
if (magic == binary_magic_number)
|
||||
return read_binary_ledger(stream, "", journal, master);
|
||||
else
|
||||
return parse_textual_ledger(stream, journal, master);
|
||||
|
|
|
|||
307
ledger.h
307
ledger.h
|
|
@ -14,281 +14,22 @@
|
|||
#include <list>
|
||||
#include <string>
|
||||
#include <ctime>
|
||||
#include <cctype>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#ifdef DEBUG
|
||||
#include <cassert>
|
||||
#else
|
||||
#ifdef assert
|
||||
#undef assert
|
||||
#endif
|
||||
#define assert(x)
|
||||
#endif
|
||||
#include "amount.h"
|
||||
#include "balance.h"
|
||||
|
||||
namespace ledger {
|
||||
|
||||
extern const std::string version;
|
||||
#define TRANSACTION_NORMAL 0x00
|
||||
#define TRANSACTION_VIRTUAL 0x01
|
||||
#define TRANSACTION_BALANCE 0x02
|
||||
#define TRANSACTION_AUTO 0x04
|
||||
#define TRANSACTION_HANDLED 0x08
|
||||
#define TRANSACTION_DISPLAYED 0x10
|
||||
|
||||
class commodity_t;
|
||||
class amount_t;
|
||||
class transaction_t;
|
||||
class entry_t;
|
||||
class account_t;
|
||||
class ledger_t;
|
||||
|
||||
class amount_t
|
||||
{
|
||||
typedef void * base_type;
|
||||
|
||||
void _init();
|
||||
void _copy(const amount_t& amt);
|
||||
void _clear();
|
||||
|
||||
public:
|
||||
base_type quantity; // amount, to MAX_PRECISION
|
||||
commodity_t * commodity;
|
||||
|
||||
bool valid() const {
|
||||
if (quantity)
|
||||
return commodity != NULL;
|
||||
else
|
||||
return commodity == NULL;
|
||||
}
|
||||
|
||||
// constructors
|
||||
amount_t(commodity_t * _commodity = NULL)
|
||||
: quantity(NULL), commodity(_commodity) {}
|
||||
|
||||
amount_t(const amount_t& amt) : quantity(NULL) {
|
||||
if (amt.quantity)
|
||||
_copy(amt);
|
||||
else
|
||||
commodity = amt.commodity;
|
||||
}
|
||||
amount_t(const std::string& value) {
|
||||
_init();
|
||||
std::istringstream str(value);
|
||||
str >> *this;
|
||||
}
|
||||
amount_t(const int value) : quantity(NULL), commodity(NULL) {
|
||||
if (value != 0) {
|
||||
std::string str;
|
||||
std::ostringstream strstr(str);
|
||||
strstr << value;
|
||||
parse(strstr.str());
|
||||
}
|
||||
}
|
||||
amount_t(const unsigned int value) : quantity(NULL), commodity(NULL) {
|
||||
if (value != 0) {
|
||||
std::string str;
|
||||
std::ostringstream strstr(str);
|
||||
strstr << value;
|
||||
parse(strstr.str());
|
||||
}
|
||||
}
|
||||
amount_t(const double value) : quantity(NULL), commodity(NULL) {
|
||||
if (value != 0.0) {
|
||||
std::string str;
|
||||
std::ostringstream strstr(str);
|
||||
strstr << value;
|
||||
parse(strstr.str());
|
||||
}
|
||||
}
|
||||
|
||||
// destructor
|
||||
~amount_t() {
|
||||
if (quantity)
|
||||
_clear();
|
||||
}
|
||||
|
||||
// assignment operator
|
||||
amount_t& operator=(const amount_t& amt);
|
||||
amount_t& operator=(const std::string& value);
|
||||
amount_t& operator=(const int value);
|
||||
amount_t& operator=(const unsigned int value);
|
||||
amount_t& operator=(const double value);
|
||||
|
||||
// general methods
|
||||
amount_t round(int precision = -1) const;
|
||||
|
||||
// in-place arithmetic
|
||||
amount_t& operator*=(const amount_t& amt);
|
||||
amount_t& operator/=(const amount_t& amt);
|
||||
amount_t& operator%=(const amount_t& amt);
|
||||
amount_t& operator+=(const amount_t& amt);
|
||||
amount_t& operator-=(const amount_t& amt);
|
||||
|
||||
// simple arithmetic
|
||||
amount_t operator*(const amount_t& amt) const {
|
||||
amount_t temp = *this;
|
||||
temp *= amt;
|
||||
return temp;
|
||||
}
|
||||
amount_t operator/(const amount_t& amt) const {
|
||||
amount_t temp = *this;
|
||||
temp /= amt;
|
||||
return temp;
|
||||
}
|
||||
amount_t operator%(const amount_t& amt) const {
|
||||
amount_t temp = *this;
|
||||
temp %= amt;
|
||||
return temp;
|
||||
}
|
||||
amount_t operator+(const amount_t& amt) const {
|
||||
amount_t temp = *this;
|
||||
temp += amt;
|
||||
return temp;
|
||||
}
|
||||
amount_t operator-(const amount_t& amt) const {
|
||||
amount_t temp = *this;
|
||||
temp -= amt;
|
||||
return temp;
|
||||
}
|
||||
|
||||
// unary negation
|
||||
amount_t& negate();
|
||||
amount_t negated() const {
|
||||
amount_t temp = *this;
|
||||
temp.negate();
|
||||
return temp;
|
||||
}
|
||||
amount_t operator-() const {
|
||||
return negated();
|
||||
}
|
||||
|
||||
// test for non-zero (use ! for zero)
|
||||
operator bool() const;
|
||||
|
||||
// comparisons to zero
|
||||
bool operator<(const int num) const;
|
||||
bool operator<=(const int num) const;
|
||||
bool operator>(const int num) const;
|
||||
bool operator>=(const int num) const;
|
||||
|
||||
// comparisons between amounts
|
||||
bool operator<(const amount_t& amt) const;
|
||||
bool operator<=(const amount_t& amt) const;
|
||||
bool operator>(const amount_t& amt) const;
|
||||
bool operator>=(const amount_t& amt) const;
|
||||
bool operator==(const amount_t& amt) const;
|
||||
bool operator!=(const amount_t& amt) const {
|
||||
if (commodity != amt.commodity)
|
||||
return true;
|
||||
return ! (*this == amt);
|
||||
}
|
||||
|
||||
amount_t value(const std::time_t moment) const;
|
||||
|
||||
operator std::string() const;
|
||||
|
||||
void parse(std::istream& in);
|
||||
void parse(const std::string& str) {
|
||||
std::istringstream stream(str);
|
||||
parse(stream);
|
||||
}
|
||||
|
||||
void write_quantity(std::ostream& out) const;
|
||||
void read_quantity(std::istream& in);
|
||||
|
||||
friend std::istream& operator>>(std::istream& in, amount_t& amt);
|
||||
};
|
||||
|
||||
void parse_quantity(std::istream& in, std::string& value);
|
||||
void parse_commodity(std::istream& in, std::string& symbol);
|
||||
|
||||
inline amount_t abs(const amount_t& amt) {
|
||||
return amt < 0 ? amt.negated() : amt;
|
||||
}
|
||||
|
||||
inline std::istream& operator>>(std::istream& in, amount_t& amt) {
|
||||
amt.parse(in);
|
||||
return in;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const amount_t& amt);
|
||||
|
||||
|
||||
#define COMMODITY_STYLE_DEFAULTS 0x00
|
||||
#define COMMODITY_STYLE_SUFFIXED 0x01
|
||||
#define COMMODITY_STYLE_SEPARATED 0x02
|
||||
#define COMMODITY_STYLE_EUROPEAN 0x04
|
||||
#define COMMODITY_STYLE_THOUSANDS 0x08
|
||||
#define COMMODITY_STYLE_CONSULTED 0x10
|
||||
#define COMMODITY_STYLE_NOMARKET 0x20
|
||||
|
||||
typedef std::map<const std::time_t, amount_t> history_map;
|
||||
typedef std::pair<const std::time_t, amount_t> history_pair;
|
||||
|
||||
typedef std::map<const std::string, commodity_t *> commodities_map;
|
||||
typedef std::pair<const std::string, commodity_t *> commodities_pair;
|
||||
|
||||
class commodity_t
|
||||
{
|
||||
public:
|
||||
std::string symbol;
|
||||
std::string name;
|
||||
std::string note;
|
||||
unsigned int precision;
|
||||
unsigned int flags;
|
||||
history_map history;
|
||||
amount_t conversion;
|
||||
unsigned long ident;
|
||||
|
||||
// If set, this global function pointer is called to determine
|
||||
// whether prices have been updated in the meanwhile.
|
||||
|
||||
static void (*updater)(commodity_t * commodity,
|
||||
const std::time_t date,
|
||||
const amount_t& price,
|
||||
const std::time_t moment);
|
||||
|
||||
// This map remembers all commodities that have been
|
||||
// defined thus far.
|
||||
|
||||
static commodities_map commodities;
|
||||
|
||||
static void add_commodity(commodity_t * commodity,
|
||||
const std::string symbol = "") {
|
||||
commodities.insert(commodities_pair((symbol.empty() ?
|
||||
commodity->symbol : symbol),
|
||||
commodity));
|
||||
}
|
||||
static bool remove_commodity(commodity_t * commodity) {
|
||||
commodities_map::size_type n = commodities.erase(commodity->symbol);
|
||||
return n > 0;
|
||||
}
|
||||
static commodity_t * find_commodity(const std::string& symbol,
|
||||
bool auto_create = false);
|
||||
|
||||
// Now the per-object constructor and methods
|
||||
|
||||
commodity_t(const std::string& _symbol = "",
|
||||
unsigned int _precision = 2,
|
||||
unsigned int _flags = COMMODITY_STYLE_DEFAULTS)
|
||||
: symbol(_symbol), precision(_precision), flags(_flags) {}
|
||||
|
||||
void add_price(const std::time_t date, const amount_t& price) {
|
||||
history.insert(history_pair(date, price));
|
||||
}
|
||||
bool remove_price(const std::time_t date) {
|
||||
history_map::size_type n = history.erase(date);
|
||||
return n > 0;
|
||||
}
|
||||
|
||||
void set_conversion(const amount_t& price) {
|
||||
conversion = price;
|
||||
}
|
||||
|
||||
amount_t value(const std::time_t moment = std::time(NULL));
|
||||
};
|
||||
|
||||
|
||||
#define TRANSACTION_NORMAL 0x0
|
||||
#define TRANSACTION_VIRTUAL 0x1
|
||||
#define TRANSACTION_BALANCE 0x2
|
||||
#define TRANSACTION_AUTO 0x4
|
||||
|
||||
class transaction_t
|
||||
{
|
||||
|
|
@ -351,23 +92,27 @@ class entry_t
|
|||
typedef std::map<const std::string, account_t *> accounts_map;
|
||||
typedef std::pair<const std::string, account_t *> accounts_pair;
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& out, const account_t& acct);
|
||||
|
||||
class account_t
|
||||
{
|
||||
public:
|
||||
const account_t * parent;
|
||||
account_t * parent;
|
||||
std::string name;
|
||||
std::string note;
|
||||
accounts_map accounts;
|
||||
mutable accounts_map accounts_cache;
|
||||
transactions_list transactions;
|
||||
balance_pair_t value;
|
||||
balance_pair_t total;
|
||||
unsigned long depth;
|
||||
unsigned long ident;
|
||||
|
||||
mutable std::string _fullname;
|
||||
static unsigned long next_ident;
|
||||
|
||||
account_t(const account_t * _parent, const std::string& _name = "",
|
||||
account_t(account_t * _parent,
|
||||
const std::string& _name = "",
|
||||
const std::string& _note = "")
|
||||
: parent(_parent), name(_name), note(_note) {}
|
||||
: parent(_parent), name(_name), note(_note),
|
||||
depth(parent ? parent->depth + 1 : 0) {}
|
||||
|
||||
~account_t();
|
||||
|
||||
|
|
@ -411,7 +156,7 @@ class ledger_t
|
|||
public:
|
||||
account_t * master;
|
||||
entries_list entries;
|
||||
|
||||
mutable accounts_map accounts_cache;
|
||||
std::list<std::string> sources;
|
||||
|
||||
ledger_t() {
|
||||
|
|
@ -430,10 +175,18 @@ class ledger_t
|
|||
}
|
||||
|
||||
account_t * find_account(const std::string& name, bool auto_create = true) {
|
||||
return master->find_account(name, auto_create);
|
||||
accounts_map::iterator c = accounts_cache.find(name);
|
||||
if (c != accounts_cache.end())
|
||||
return (*c).second;
|
||||
|
||||
account_t * account = master->find_account(name, auto_create);
|
||||
accounts_cache.insert(accounts_pair(name, account));
|
||||
return account;
|
||||
}
|
||||
account_t * find_account(const std::string& name) const {
|
||||
return master->find_account(name, false);
|
||||
// With auto_create false, the other `find_account' will not
|
||||
// change the object.
|
||||
return const_cast<ledger_t *>(this)->find_account(name, false);
|
||||
}
|
||||
|
||||
bool add_entry(entry_t * entry);
|
||||
|
|
@ -444,6 +197,8 @@ class ledger_t
|
|||
|
||||
int parse_ledger_file(char * p, ledger_t * journal);
|
||||
|
||||
extern const std::string version;
|
||||
|
||||
} // namespace ledger
|
||||
|
||||
#endif // _LEDGER_H
|
||||
|
|
|
|||
88
main.cc
88
main.cc
|
|
@ -1,11 +1,10 @@
|
|||
#include "ledger.h"
|
||||
#include "balance.h"
|
||||
#include "error.h"
|
||||
#include "textual.h"
|
||||
#include "binary.h"
|
||||
#include "item.h"
|
||||
#include "expr.h"
|
||||
#include "valexpr.h"
|
||||
#include "format.h"
|
||||
#include "walk.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
|
|
@ -20,7 +19,9 @@ namespace ledger {
|
|||
// The command-line balance report
|
||||
//
|
||||
|
||||
static const std::string bal_fmt = "%20T%2_%-n\n";
|
||||
static const std::string bal_fmt = "%20T %2_%-n\n";
|
||||
|
||||
#if 0
|
||||
|
||||
unsigned int show_balances(std::ostream& out,
|
||||
items_deque& items,
|
||||
|
|
@ -80,6 +81,7 @@ void balance_report(std::ostream& out,
|
|||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
|
|
@ -96,6 +98,8 @@ static const std::string print_fmt
|
|||
static bool show_commodities_revalued = false;
|
||||
static bool show_commodities_revalued_only = false;
|
||||
|
||||
#if 0
|
||||
|
||||
static void report_value_change(std::ostream& out,
|
||||
const std::time_t date,
|
||||
const balance_pair_t& balance,
|
||||
|
|
@ -218,6 +222,7 @@ void register_report(std::ostream& out,
|
|||
first_line_format, next_lines_format);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void set_price_conversion(const std::string& setting)
|
||||
{
|
||||
|
|
@ -362,6 +367,7 @@ int main(int argc, char * argv[])
|
|||
bool show_expanded = false;
|
||||
bool show_related = false;
|
||||
bool show_inverted = false;
|
||||
bool show_empty = false;
|
||||
|
||||
#ifdef DEBUG
|
||||
bool debug = false;
|
||||
|
|
@ -492,11 +498,9 @@ int main(int argc, char * argv[])
|
|||
format_string = optarg;
|
||||
break;
|
||||
|
||||
#if 0
|
||||
case 'E':
|
||||
show_empty = true;
|
||||
break;
|
||||
#endif
|
||||
|
||||
case 'n':
|
||||
show_subtotals = false;
|
||||
|
|
@ -708,7 +712,11 @@ int main(int argc, char * argv[])
|
|||
else
|
||||
predicate_string += "(";
|
||||
first = false;
|
||||
} else {
|
||||
}
|
||||
else if (argv[index][0] == '-') {
|
||||
predicate_string += "&";
|
||||
}
|
||||
else {
|
||||
predicate_string += "|";
|
||||
}
|
||||
|
||||
|
|
@ -736,6 +744,8 @@ int main(int argc, char * argv[])
|
|||
|
||||
if (first)
|
||||
first = false;
|
||||
else if (argv[index][0] == '-')
|
||||
predicate_string += "&";
|
||||
else
|
||||
predicate_string += "|";
|
||||
|
||||
|
|
@ -753,7 +763,7 @@ int main(int argc, char * argv[])
|
|||
predicate_string += ")";
|
||||
}
|
||||
|
||||
// Compile the predicate
|
||||
// Compile the predicates
|
||||
|
||||
if (! predicate_string.empty()) {
|
||||
#ifdef DEBUG
|
||||
|
|
@ -763,6 +773,9 @@ int main(int argc, char * argv[])
|
|||
predicate.reset(ledger::parse_expr(predicate_string));
|
||||
}
|
||||
|
||||
if (display_predicate_string.empty() && command == "b" && ! show_empty)
|
||||
display_predicate_string = "T";
|
||||
|
||||
if (! display_predicate_string.empty()) {
|
||||
#ifdef DEBUG
|
||||
if (debug)
|
||||
|
|
@ -795,39 +808,6 @@ int main(int argc, char * argv[])
|
|||
show_inverted = true;
|
||||
}
|
||||
|
||||
std::auto_ptr<ledger::item_t> top;
|
||||
std::auto_ptr<ledger::item_t> list;
|
||||
|
||||
if (command == "e") {
|
||||
top.reset(new ledger::item_t);
|
||||
ledger::item_t * item = new ledger::item_t(new_entry.get());
|
||||
for (ledger::transactions_list::const_iterator i
|
||||
= new_entry->transactions.begin();
|
||||
i != new_entry->transactions.end();
|
||||
i++)
|
||||
item->add_item(new ledger::item_t(*i));
|
||||
top->add_item(item);
|
||||
}
|
||||
else if ((! show_related || ! predicate.get()) &&
|
||||
(command == "b" || command == "E")) {
|
||||
top.reset(ledger::walk_accounts(NULL, journal->master, predicate.get(),
|
||||
command != "E" && show_subtotals,
|
||||
command == "E"));
|
||||
}
|
||||
else {
|
||||
top.reset(ledger::walk_entries(journal->entries.begin(),
|
||||
journal->entries.end(), predicate.get(),
|
||||
show_related, show_inverted));
|
||||
if (top.get() && command == "b" || command == "E") {
|
||||
list.reset(top.release());
|
||||
top.reset(ledger::walk_accounts(list.get(), journal->master,
|
||||
predicate.get(),
|
||||
command != "E" && show_subtotals,
|
||||
command == "E"));
|
||||
}
|
||||
}
|
||||
|
||||
if (top.get()) {
|
||||
const char * f;
|
||||
if (! format_string.empty())
|
||||
f = format_string.c_str();
|
||||
|
|
@ -840,9 +820,19 @@ int main(int argc, char * argv[])
|
|||
|
||||
if (command == "b") {
|
||||
std::auto_ptr<ledger::format_t> format(new ledger::format_t(f));
|
||||
ledger::balance_report(std::cout, top.get(), display_predicate.get(),
|
||||
sort_order.get(), *format, show_expanded,
|
||||
show_subtotals);
|
||||
|
||||
ledger::walk_accounts(journal->master,
|
||||
ledger::format_account(std::cout, *format.get()),
|
||||
predicate.get(), show_related, show_inverted,
|
||||
show_subtotals, display_predicate.get());
|
||||
|
||||
if (! display_predicate.get() ||
|
||||
ledger::item_predicate(display_predicate.get())(journal->master)) {
|
||||
std::string end_format = "--------------------\n";
|
||||
end_format += f;
|
||||
format.get()->elements.reset(ledger::format_t::parse_elements(end_format));
|
||||
ledger::format_account(std::cout, *format.get())(journal->master, true);
|
||||
}
|
||||
} else {
|
||||
std::string first_line_format;
|
||||
std::string next_lines_format;
|
||||
|
|
@ -859,10 +849,12 @@ int main(int argc, char * argv[])
|
|||
std::auto_ptr<ledger::format_t>
|
||||
nformat(new ledger::format_t(next_lines_format));
|
||||
|
||||
ledger::register_report(std::cout, top.get(), display_predicate.get(),
|
||||
sort_order.get(), *format, *nformat,
|
||||
show_expanded);
|
||||
}
|
||||
ledger::walk_entries(journal->entries.begin(), journal->entries.end(),
|
||||
ledger::format_transaction(std::cout,
|
||||
first_line_format,
|
||||
next_lines_format),
|
||||
predicate.get(), show_related, show_inverted,
|
||||
display_predicate.get());
|
||||
}
|
||||
|
||||
// Save the cache, if need be
|
||||
|
|
|
|||
252
textual.cc
252
textual.cc
|
|
@ -1,8 +1,9 @@
|
|||
#include "textual.h"
|
||||
#include "datetime.h"
|
||||
#include "autoxact.h"
|
||||
#include "valexpr.h"
|
||||
#include "error.h"
|
||||
#include "expr.h"
|
||||
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <cstring>
|
||||
|
|
@ -13,11 +14,6 @@
|
|||
|
||||
namespace ledger {
|
||||
|
||||
#if 0
|
||||
static const std::string entry1_fmt = "%10d %p";
|
||||
static const std::string entryn_fmt = " %-30a %15t";
|
||||
#endif
|
||||
|
||||
#define MAX_LINE 1024
|
||||
|
||||
std::string path;
|
||||
|
|
@ -29,31 +25,6 @@ static account_t * last_account;
|
|||
static std::string last_desc;
|
||||
#endif
|
||||
|
||||
static std::time_t now = std::time(NULL);
|
||||
static struct std::tm * now_tm = std::localtime(&now);
|
||||
|
||||
static std::time_t base = -1;
|
||||
static int base_year = -1;
|
||||
|
||||
static const int month_days[12] = {
|
||||
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
|
||||
};
|
||||
|
||||
static const char * formats[] = {
|
||||
"%Y/%m/%d",
|
||||
"%m/%d",
|
||||
"%Y.%m.%d",
|
||||
"%m.%d",
|
||||
"%Y-%m-%d",
|
||||
"%m-%d",
|
||||
"%a",
|
||||
"%A",
|
||||
"%b",
|
||||
"%B",
|
||||
"%Y",
|
||||
NULL
|
||||
};
|
||||
|
||||
inline char * skip_ws(char * ptr)
|
||||
{
|
||||
while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n')
|
||||
|
|
@ -61,6 +32,16 @@ inline char * skip_ws(char * ptr)
|
|||
return ptr;
|
||||
}
|
||||
|
||||
inline char peek_next_nonws(std::istream& in)
|
||||
{
|
||||
char c = in.peek();
|
||||
while (! in.eof() && std::isspace(c) && c != '\n') {
|
||||
in.get(c);
|
||||
c = in.peek();
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
inline char * next_element(char * buf, bool variable = false)
|
||||
{
|
||||
for (char * p = buf; *p; p++) {
|
||||
|
|
@ -83,109 +64,6 @@ inline char * next_element(char * buf, bool variable = false)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
bool parse_date_mask(const char * date_str, struct std::tm * result)
|
||||
{
|
||||
for (const char ** f = formats; *f; f++) {
|
||||
memset(result, INT_MAX, sizeof(struct std::tm));
|
||||
if (strptime(date_str, *f, result))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool parse_date(const char * date_str, std::time_t * result, const int year)
|
||||
{
|
||||
struct std::tm when;
|
||||
|
||||
if (! parse_date_mask(date_str, &when))
|
||||
return false;
|
||||
|
||||
when.tm_hour = 0;
|
||||
when.tm_min = 0;
|
||||
when.tm_sec = 0;
|
||||
|
||||
if (when.tm_year == -1)
|
||||
when.tm_year = ((year == -1) ? now_tm->tm_year : (year - 1900));
|
||||
|
||||
if (when.tm_mon == -1)
|
||||
when.tm_mon = 0;
|
||||
|
||||
if (when.tm_mday == -1)
|
||||
when.tm_mday = 1;
|
||||
|
||||
*result = std::mktime(&when);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool quick_parse_date(char * date_str, std::time_t * result)
|
||||
{
|
||||
int year = -1, month = -1, day, num = 0;
|
||||
|
||||
for (char * p = date_str; *p; p++) {
|
||||
if (*p == '/' || *p == '-' || *p == '.') {
|
||||
if (year == -1)
|
||||
year = num;
|
||||
else
|
||||
month = num;
|
||||
num = 0;
|
||||
}
|
||||
else if (*p < '0' || *p > '9') {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
num *= 10;
|
||||
num += *p - '0';
|
||||
}
|
||||
}
|
||||
|
||||
day = num;
|
||||
|
||||
if (month == -1) {
|
||||
month = year;
|
||||
year = -1;
|
||||
}
|
||||
|
||||
if (base == -1 || year != base_year) {
|
||||
struct std::tm when;
|
||||
|
||||
when.tm_hour = 0;
|
||||
when.tm_min = 0;
|
||||
when.tm_sec = 0;
|
||||
|
||||
base_year = year == -1 ? now_tm->tm_year + 1900 : year;
|
||||
when.tm_year = year == -1 ? now_tm->tm_year : year - 1900;
|
||||
when.tm_mon = 0;
|
||||
when.tm_mday = 1;
|
||||
|
||||
base = std::mktime(&when);
|
||||
}
|
||||
|
||||
*result = base;
|
||||
|
||||
--month;
|
||||
while (--month >= 0) {
|
||||
*result += month_days[month] * 24 * 60 * 60;
|
||||
if (month == 1 && year % 4 == 0 && year != 2000) // february in leap years
|
||||
*result += 24 * 60 * 60;
|
||||
}
|
||||
|
||||
if (--day)
|
||||
*result += day * 24 * 60 * 60;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline char peek_next_nonws(std::istream& in)
|
||||
{
|
||||
char c = in.peek();
|
||||
while (! in.eof() && std::isspace(c) && c != '\n') {
|
||||
in.get(c);
|
||||
c = in.peek();
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
transaction_t * parse_transaction_text(char * line, account_t * account,
|
||||
entry_t * entry)
|
||||
{
|
||||
|
|
@ -233,9 +111,9 @@ transaction_t * parse_transaction_text(char * line, account_t * account,
|
|||
xact->account = account->find_account(p);
|
||||
|
||||
if (! xact->amount.commodity)
|
||||
xact->amount.commodity = commodity_t::find_commodity("", true);
|
||||
xact->amount.commodity = commodity_t::null_commodity;
|
||||
if (! xact->cost.commodity)
|
||||
xact->cost.commodity = commodity_t::find_commodity("", true);
|
||||
xact->cost.commodity = commodity_t::null_commodity;
|
||||
|
||||
return xact;
|
||||
}
|
||||
|
|
@ -250,95 +128,6 @@ transaction_t * parse_transaction(std::istream& in, account_t * account,
|
|||
return parse_transaction_text(line, account, entry);
|
||||
}
|
||||
|
||||
class automated_transaction_t
|
||||
{
|
||||
public:
|
||||
masks_list masks;
|
||||
transactions_list transactions;
|
||||
|
||||
automated_transaction_t(masks_list& _masks,
|
||||
transactions_list& _transactions) {
|
||||
masks.insert(masks.begin(), _masks.begin(), _masks.end());
|
||||
transactions.insert(transactions.begin(),
|
||||
_transactions.begin(), _transactions.end());
|
||||
// Take over ownership of the pointers
|
||||
_transactions.clear();
|
||||
}
|
||||
|
||||
~automated_transaction_t() {
|
||||
for (transactions_list::iterator i = transactions.begin();
|
||||
i != transactions.end();
|
||||
i++)
|
||||
delete *i;
|
||||
}
|
||||
|
||||
void extend_entry(entry_t * entry);
|
||||
};
|
||||
|
||||
typedef std::vector<automated_transaction_t *>
|
||||
automated_transactions_vector;
|
||||
|
||||
void automated_transaction_t::extend_entry(entry_t * entry)
|
||||
{
|
||||
for (transactions_list::iterator i = entry->transactions.begin();
|
||||
i != entry->transactions.end();
|
||||
i++)
|
||||
if (matches(masks, *((*i)->account))) {
|
||||
for (transactions_list::iterator t = transactions.begin();
|
||||
t != transactions.end();
|
||||
t++) {
|
||||
amount_t amt;
|
||||
if ((*t)->amount.commodity->symbol.empty())
|
||||
amt = (*i)->amount * (*t)->amount;
|
||||
else
|
||||
amt = (*t)->amount;
|
||||
|
||||
transaction_t * xact
|
||||
= new transaction_t(entry, (*t)->account, amt, amt,
|
||||
(*t)->flags | TRANSACTION_AUTO);
|
||||
entry->add_transaction(xact);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class automated_transactions_t
|
||||
{
|
||||
public:
|
||||
automated_transactions_vector automated_transactions;
|
||||
|
||||
~automated_transactions_t() {
|
||||
for (automated_transactions_vector::iterator i
|
||||
= automated_transactions.begin();
|
||||
i != automated_transactions.end();
|
||||
i++)
|
||||
delete *i;
|
||||
}
|
||||
|
||||
void extend_entry(entry_t * entry) {
|
||||
for (automated_transactions_vector::iterator i
|
||||
= automated_transactions.begin();
|
||||
i != automated_transactions.end();
|
||||
i++)
|
||||
(*i)->extend_entry(entry);
|
||||
}
|
||||
|
||||
void add_automated_transaction(automated_transaction_t * auto_xact) {
|
||||
automated_transactions.push_back(auto_xact);
|
||||
}
|
||||
bool remove_automated_transaction(automated_transaction_t * auto_xact) {
|
||||
for (automated_transactions_vector::iterator i
|
||||
= automated_transactions.begin();
|
||||
i != automated_transactions.end();
|
||||
i++) {
|
||||
if (*i == auto_xact) {
|
||||
automated_transactions.erase(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
void parse_automated_transactions(std::istream& in, account_t * account,
|
||||
automated_transactions_t& auto_xacts)
|
||||
{
|
||||
|
|
@ -498,11 +287,6 @@ entry_t * parse_entry(std::istream& in, account_t * master)
|
|||
return curr;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Textual ledger parser
|
||||
//
|
||||
|
||||
unsigned int parse_textual_ledger(std::istream& in, ledger_t * journal,
|
||||
account_t * master)
|
||||
{
|
||||
|
|
@ -676,7 +460,8 @@ unsigned int parse_textual_ledger(std::istream& in, ledger_t * journal,
|
|||
amount_t price;
|
||||
|
||||
parse_commodity(in, symbol);
|
||||
in >> line; // the price
|
||||
in.getline(line, MAX_LINE);
|
||||
linenum++;
|
||||
price.parse(line);
|
||||
|
||||
commodity_t * commodity = commodity_t::find_commodity(symbol, true);
|
||||
|
|
@ -733,7 +518,8 @@ unsigned int parse_textual_ledger(std::istream& in, ledger_t * journal,
|
|||
unsigned int curr_linenum = linenum;
|
||||
std::string curr_path = path;
|
||||
|
||||
count += parse_textual_ledger(stream, journal, account_stack.front());
|
||||
count += parse_textual_ledger(stream, journal,
|
||||
account_stack.front());
|
||||
|
||||
linenum = curr_linenum;
|
||||
path = curr_path;
|
||||
|
|
|
|||
|
|
@ -8,11 +8,6 @@ namespace ledger {
|
|||
extern unsigned int parse_textual_ledger(std::istream& in, ledger_t * ledger,
|
||||
account_t * master = NULL);
|
||||
|
||||
extern bool parse_date_mask(const char * date_str, struct std::tm * result);
|
||||
|
||||
extern bool parse_date(const char * date_str, std::time_t * result,
|
||||
const int year = -1);
|
||||
|
||||
} // namespace ledger
|
||||
|
||||
#endif // _TEXTUAL_H
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "expr.h"
|
||||
#include "valexpr.h"
|
||||
#include "error.h"
|
||||
#include "datetime.h"
|
||||
#include "textual.h"
|
||||
|
||||
#include <pcre.h>
|
||||
|
|
@ -90,138 +91,199 @@ bool matches(const masks_list& regexps, const std::string& str,
|
|||
|
||||
#endif
|
||||
|
||||
balance_t node_t::compute(const item_t * item) const
|
||||
void node_t::compute(balance_t& result, const details_t& details) const
|
||||
{
|
||||
balance_t temp;
|
||||
|
||||
switch (type) {
|
||||
case CONSTANT_A:
|
||||
temp = constant_a;
|
||||
result = constant_a;
|
||||
break;
|
||||
|
||||
case CONSTANT_T:
|
||||
temp = amount_t((unsigned int) constant_t);
|
||||
result = (unsigned int) constant_t;
|
||||
break;
|
||||
|
||||
case AMOUNT:
|
||||
temp = item->value.quantity;
|
||||
if (details.xact)
|
||||
result = details.xact->amount;
|
||||
else if (details.account)
|
||||
result = details.account->value.quantity;
|
||||
break;
|
||||
|
||||
case COST:
|
||||
temp = item->value.cost;
|
||||
if (details.xact)
|
||||
result = details.xact->cost;
|
||||
else if (details.account)
|
||||
result = details.account->value.cost;
|
||||
break;
|
||||
|
||||
case BALANCE:
|
||||
temp = item->total.quantity - item->value.quantity;
|
||||
if (details.balance) {
|
||||
result = details.balance->quantity;
|
||||
if (details.xact)
|
||||
result -= details.xact->amount;
|
||||
else if (details.account)
|
||||
result -= details.account->value.quantity;
|
||||
}
|
||||
break;
|
||||
|
||||
case COST_BALANCE:
|
||||
temp = item->total.cost - item->value.cost;
|
||||
if (details.balance) {
|
||||
result = details.balance->cost;
|
||||
if (details.xact)
|
||||
result -= details.xact->cost;
|
||||
else if (details.account)
|
||||
result -= details.account->value.cost;
|
||||
}
|
||||
break;
|
||||
|
||||
case TOTAL:
|
||||
temp = item->total.quantity;
|
||||
if (details.balance)
|
||||
result = details.balance->quantity;
|
||||
else if (details.account)
|
||||
result = details.account->total.quantity;
|
||||
break;
|
||||
case COST_TOTAL:
|
||||
temp = item->total.cost;
|
||||
if (details.balance)
|
||||
result = details.balance->cost;
|
||||
else if (details.account)
|
||||
result = details.account->total.cost;
|
||||
break;
|
||||
|
||||
case DATE:
|
||||
temp = amount_t((unsigned int) item->date);
|
||||
if (details.entry)
|
||||
result = (unsigned int) details.entry->date;
|
||||
break;
|
||||
|
||||
case CLEARED:
|
||||
temp = amount_t(item->state == entry_t::CLEARED ? 1 : 0);
|
||||
if (details.entry)
|
||||
result = details.entry->state == entry_t::CLEARED;
|
||||
break;
|
||||
|
||||
case REAL:
|
||||
temp = amount_t(item->flags & TRANSACTION_VIRTUAL ? 0 : 1);
|
||||
if (details.xact)
|
||||
result = bool(details.xact->flags & TRANSACTION_VIRTUAL);
|
||||
break;
|
||||
|
||||
case INDEX:
|
||||
temp = amount_t(item->index + 1);
|
||||
if (details.index)
|
||||
result = *details.index + 1;
|
||||
break;
|
||||
|
||||
case F_ARITH_MEAN:
|
||||
if (details.index) {
|
||||
assert(left);
|
||||
temp = left->compute(item);
|
||||
temp /= amount_t(item->index + 1);
|
||||
left->compute(result, details);
|
||||
result /= amount_t(*details.index + 1);
|
||||
}
|
||||
break;
|
||||
|
||||
case F_NEG:
|
||||
assert(left);
|
||||
temp = left->compute(item).negated();
|
||||
left->compute(result, details);
|
||||
result.negate();
|
||||
break;
|
||||
|
||||
case F_ABS:
|
||||
assert(left);
|
||||
temp = abs(left->compute(item));
|
||||
left->compute(result, details);
|
||||
result = abs(result);
|
||||
break;
|
||||
|
||||
case F_PAYEE_MASK:
|
||||
assert(mask);
|
||||
temp = (mask->match(item->payee) || mask->match(item->note)) ? 1 : 0;
|
||||
if (details.entry)
|
||||
result = mask->match(details.entry->payee);
|
||||
break;
|
||||
|
||||
case F_ACCOUNT_MASK:
|
||||
assert(mask);
|
||||
temp = (item->account &&
|
||||
mask->match(item->account->fullname())) ? 1 : 0;
|
||||
if (details.account)
|
||||
result = mask->match(details.account->fullname());
|
||||
break;
|
||||
|
||||
case F_VALUE: {
|
||||
assert(left);
|
||||
temp = left->compute(item);
|
||||
left->compute(result, details);
|
||||
|
||||
std::time_t moment = -1;
|
||||
if (right) {
|
||||
if (right && details.entry) {
|
||||
switch (right->type) {
|
||||
case DATE: moment = item->date; break;
|
||||
case DATE: moment = details.entry->date; break;
|
||||
default:
|
||||
throw compute_error("Invalid date passed to P(v,d)");
|
||||
}
|
||||
}
|
||||
temp = temp.value(moment);
|
||||
result = result.value(moment);
|
||||
break;
|
||||
}
|
||||
|
||||
case O_NOT:
|
||||
temp = left->compute(item) ? 0 : 1;
|
||||
left->compute(result, details);
|
||||
result = result ? false : true;
|
||||
break;
|
||||
|
||||
case O_QUES:
|
||||
temp = left->compute(item);
|
||||
if (temp)
|
||||
temp = right->left->compute(item);
|
||||
assert(left);
|
||||
assert(right);
|
||||
assert(right->type == O_COL);
|
||||
left->compute(result, details);
|
||||
if (result)
|
||||
right->left->compute(result, details);
|
||||
else
|
||||
temp = right->right->compute(item);
|
||||
right->right->compute(result, details);
|
||||
break;
|
||||
|
||||
case O_AND:
|
||||
assert(left);
|
||||
assert(right);
|
||||
left->compute(result, details);
|
||||
if (result)
|
||||
right->compute(result, details);
|
||||
break;
|
||||
|
||||
case O_OR:
|
||||
assert(left);
|
||||
assert(right);
|
||||
left->compute(result, details);
|
||||
if (! result)
|
||||
right->compute(result, details);
|
||||
break;
|
||||
|
||||
case O_EQ:
|
||||
case O_LT:
|
||||
case O_LTE:
|
||||
case O_GT:
|
||||
case O_GTE:
|
||||
case O_GTE: {
|
||||
assert(left);
|
||||
assert(right);
|
||||
left->compute(result, details);
|
||||
balance_t temp = result;
|
||||
right->compute(result, details);
|
||||
switch (type) {
|
||||
case O_EQ: result = temp == result; break;
|
||||
case O_LT: result = temp < result; break;
|
||||
case O_LTE: result = temp <= result; break;
|
||||
case O_GT: result = temp > result; break;
|
||||
case O_GTE: result = temp >= result; break;
|
||||
default: assert(0); break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case O_ADD:
|
||||
case O_SUB:
|
||||
case O_MUL:
|
||||
case O_DIV: {
|
||||
assert(left);
|
||||
assert(right);
|
||||
balance_t left_bal = left->compute(item);
|
||||
balance_t right_bal = right->compute(item);
|
||||
right->compute(result, details);
|
||||
balance_t temp = result;
|
||||
left->compute(result, details);
|
||||
switch (type) {
|
||||
case O_AND: temp = (left_bal && right_bal) ? 1 : 0; break;
|
||||
case O_OR: temp = (left_bal || right_bal) ? 1 : 0; break;
|
||||
case O_EQ: temp = (left_bal == right_bal) ? 1 : 0; break;
|
||||
case O_LT: temp = (left_bal < right_bal) ? 1 : 0; break;
|
||||
case O_LTE: temp = (left_bal <= right_bal) ? 1 : 0; break;
|
||||
case O_GT: temp = (left_bal > right_bal) ? 1 : 0; break;
|
||||
case O_GTE: temp = (left_bal >= right_bal) ? 1 : 0; break;
|
||||
case O_ADD: temp = left_bal + right_bal; break;
|
||||
case O_SUB: temp = left_bal - right_bal; break;
|
||||
case O_MUL: temp = left_bal * right_bal; break;
|
||||
case O_DIV: temp = left_bal / right_bal; break;
|
||||
case O_ADD: result += temp; break;
|
||||
case O_SUB: result -= temp; break;
|
||||
case O_MUL: result *= temp; break;
|
||||
case O_DIV: result /= temp; break;
|
||||
default: assert(0); break;
|
||||
}
|
||||
break;
|
||||
|
|
@ -232,8 +294,6 @@ balance_t node_t::compute(const item_t * item) const
|
|||
assert(0);
|
||||
break;
|
||||
}
|
||||
|
||||
return temp;
|
||||
}
|
||||
|
||||
node_t * parse_term(std::istream& in);
|
||||
|
|
@ -608,8 +668,13 @@ namespace ledger {
|
|||
static void dump_tree(std::ostream& out, node_t * node)
|
||||
{
|
||||
switch (node->type) {
|
||||
case node_t::CONSTANT_A: out << "CONST[" << node->constant_a << "]"; break;
|
||||
case node_t::CONSTANT_T: out << "DATE/TIME[" << node->constant_t << "]"; break;
|
||||
case node_t::CONSTANT_A:
|
||||
out << "CONST[" << node->constant_a << "]";
|
||||
break;
|
||||
case node_t::CONSTANT_T:
|
||||
out << "DATE/TIME[" << node->constant_t << "]";
|
||||
break;
|
||||
|
||||
case node_t::AMOUNT: out << "AMOUNT"; break;
|
||||
case node_t::COST: out << "COST"; break;
|
||||
case node_t::DATE: out << "DATE"; break;
|
||||
|
|
@ -674,11 +739,36 @@ static void dump_tree(std::ostream& out, node_t * node)
|
|||
|
||||
case node_t::O_AND:
|
||||
case node_t::O_OR:
|
||||
out << "(";
|
||||
dump_tree(out, node->left);
|
||||
switch (node->type) {
|
||||
case node_t::O_AND: out << " & "; break;
|
||||
case node_t::O_OR: out << " | "; break;
|
||||
default: assert(0); break;
|
||||
}
|
||||
dump_tree(out, node->right);
|
||||
out << ")";
|
||||
break;
|
||||
|
||||
case node_t::O_EQ:
|
||||
case node_t::O_LT:
|
||||
case node_t::O_LTE:
|
||||
case node_t::O_GT:
|
||||
case node_t::O_GTE:
|
||||
out << "(";
|
||||
dump_tree(out, node->left);
|
||||
switch (node->type) {
|
||||
case node_t::O_EQ: out << "="; break;
|
||||
case node_t::O_LT: out << "<"; break;
|
||||
case node_t::O_LTE: out << "<="; break;
|
||||
case node_t::O_GT: out << ">"; break;
|
||||
case node_t::O_GTE: out << ">="; break;
|
||||
default: assert(0); break;
|
||||
}
|
||||
dump_tree(out, node->right);
|
||||
out << ")";
|
||||
break;
|
||||
|
||||
case node_t::O_ADD:
|
||||
case node_t::O_SUB:
|
||||
case node_t::O_MUL:
|
||||
|
|
@ -686,13 +776,6 @@ static void dump_tree(std::ostream& out, node_t * node)
|
|||
out << "(";
|
||||
dump_tree(out, node->left);
|
||||
switch (node->type) {
|
||||
case node_t::O_AND: out << " & "; break;
|
||||
case node_t::O_OR: out << " | "; break;
|
||||
case node_t::O_EQ: out << "="; break;
|
||||
case node_t::O_LT: out << "<"; break;
|
||||
case node_t::O_LTE: out << "<="; break;
|
||||
case node_t::O_GT: out << ">"; break;
|
||||
case node_t::O_GTE: out << ">="; break;
|
||||
case node_t::O_ADD: out << "+"; break;
|
||||
case node_t::O_SUB: out << "-"; break;
|
||||
case node_t::O_MUL: out << "*"; break;
|
||||
|
|
@ -714,7 +797,7 @@ static void dump_tree(std::ostream& out, node_t * node)
|
|||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
ledger::dump_tree(std::cout, ledger::parse_expr(argv[1], NULL));
|
||||
ledger::dump_tree(std::cout, ledger::parse_expr(argv[1]));
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
|
|
@ -2,8 +2,6 @@
|
|||
#define _EXPR_H
|
||||
|
||||
#include "ledger.h"
|
||||
#include "balance.h"
|
||||
#include "item.h"
|
||||
|
||||
namespace ledger {
|
||||
|
||||
|
|
@ -30,6 +28,33 @@ bool matches(const masks_list& regexps, const std::string& str,
|
|||
#endif
|
||||
|
||||
|
||||
struct details_t
|
||||
{
|
||||
const entry_t * entry;
|
||||
const transaction_t * xact;
|
||||
const account_t * account;
|
||||
const balance_pair_t * balance;
|
||||
const unsigned int * index;
|
||||
const unsigned int depth;
|
||||
|
||||
details_t(const entry_t * _entry,
|
||||
const balance_pair_t * _balance = NULL,
|
||||
const unsigned int * _index = NULL)
|
||||
: entry(_entry), xact(NULL), account(NULL),
|
||||
balance(_balance), index(_index), depth(0) {}
|
||||
|
||||
details_t(const transaction_t * _xact,
|
||||
const balance_pair_t * _balance = NULL,
|
||||
const unsigned int * _index = NULL)
|
||||
: entry(_xact->entry), xact(_xact), account(_xact->account),
|
||||
balance(_balance), index(_index), depth(0) {}
|
||||
|
||||
details_t(const account_t * _account,
|
||||
const unsigned int _depth = 0)
|
||||
: entry(NULL), xact(NULL), account(_account),
|
||||
balance(NULL), index(NULL), depth(_depth) {}
|
||||
};
|
||||
|
||||
struct node_t
|
||||
{
|
||||
enum kind_t {
|
||||
|
|
@ -95,7 +120,7 @@ struct node_t
|
|||
if (right) delete right;
|
||||
}
|
||||
|
||||
balance_t compute(const item_t * item) const;
|
||||
void compute(balance_t& result, const details_t& details) const;
|
||||
};
|
||||
|
||||
node_t * parse_expr(std::istream& in);
|
||||
|
|
@ -120,64 +145,6 @@ inline node_t * find_node(node_t * node, node_t::kind_t type) {
|
|||
return result;
|
||||
}
|
||||
|
||||
void dump_tree(std::ostream& out, node_t * node);
|
||||
|
||||
class value_predicate
|
||||
{
|
||||
public:
|
||||
const node_t * predicate;
|
||||
|
||||
explicit value_predicate(const node_t * _predicate)
|
||||
: predicate(_predicate) {}
|
||||
|
||||
bool operator ()(const transaction_t * xact) const {
|
||||
if (! predicate) {
|
||||
return true;
|
||||
} else {
|
||||
item_t temp;
|
||||
temp.date = xact->entry->date;
|
||||
temp.state = xact->entry->state;
|
||||
temp.code = xact->entry->code;
|
||||
temp.payee = xact->entry->payee;
|
||||
temp.flags = xact->flags;
|
||||
temp.account = xact->account;
|
||||
return predicate->compute(&temp);
|
||||
}
|
||||
}
|
||||
|
||||
bool operator ()(const entry_t * entry) const {
|
||||
if (! predicate) {
|
||||
return true;
|
||||
} else {
|
||||
item_t temp;
|
||||
temp.date = entry->date;
|
||||
temp.payee = entry->payee;
|
||||
temp.state = entry->state;
|
||||
temp.code = entry->code;
|
||||
|
||||
// Although there may be conflicting account masks for the whole
|
||||
// set of transactions -- for example, /rent/&!/expenses/, which
|
||||
// might match one by not another transactions -- we let the
|
||||
// entry through if at least one of the transactions meets the
|
||||
// criterion
|
||||
|
||||
for (transactions_list::const_iterator i = entry->transactions.begin();
|
||||
i != entry->transactions.end();
|
||||
i++) {
|
||||
temp.flags = (*i)->flags;
|
||||
temp.account = (*i)->account;
|
||||
if (predicate->compute(&temp))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool operator ()(const item_t * item) const {
|
||||
return ! predicate || predicate->compute(item);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace report
|
||||
|
||||
#endif // _REPORT_H
|
||||
234
walk.h
Normal file
234
walk.h
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
#ifndef _WALK_H
|
||||
#define _WALK_H
|
||||
|
||||
#include "ledger.h"
|
||||
#include "balance.h"
|
||||
#include "format.h"
|
||||
#include "valexpr.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace ledger {
|
||||
|
||||
class item_predicate
|
||||
{
|
||||
const node_t * predicate;
|
||||
balance_pair_t * balance;
|
||||
unsigned int * index;
|
||||
|
||||
public:
|
||||
item_predicate(const node_t * _predicate,
|
||||
balance_pair_t * _balance = NULL,
|
||||
unsigned int * _index = NULL)
|
||||
: predicate(_predicate), balance(_balance), index(_index) {}
|
||||
|
||||
bool operator()(const entry_t * entry) const {
|
||||
if (predicate) {
|
||||
balance_t result;
|
||||
predicate->compute(result, details_t(entry, balance, index));
|
||||
return result;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool operator()(const transaction_t * xact) const {
|
||||
if (predicate) {
|
||||
balance_t result;
|
||||
predicate->compute(result, details_t(xact, balance, index));
|
||||
return result;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool operator()(const account_t * account) const {
|
||||
if (predicate) {
|
||||
balance_t result;
|
||||
predicate->compute(result, details_t(account));
|
||||
return result;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
inline void add_to_balance_pair(balance_pair_t& balance,
|
||||
transaction_t * xact,
|
||||
const bool inverted = false)
|
||||
{
|
||||
if (inverted) {
|
||||
balance.quantity += - xact->amount;
|
||||
balance.cost += - xact->cost;
|
||||
} else {
|
||||
balance += *xact;
|
||||
}
|
||||
}
|
||||
|
||||
class format_transaction
|
||||
{
|
||||
std::ostream& output_stream;
|
||||
const format_t& first_line_format;
|
||||
const format_t& next_lines_format;
|
||||
mutable entry_t * last_entry;
|
||||
|
||||
public:
|
||||
format_transaction(std::ostream& _output_stream,
|
||||
const format_t& _first_line_format,
|
||||
const format_t& _next_lines_format)
|
||||
: output_stream(_output_stream),
|
||||
first_line_format(_first_line_format),
|
||||
next_lines_format(_next_lines_format),
|
||||
last_entry(NULL) {}
|
||||
|
||||
void operator()(transaction_t * xact,
|
||||
balance_pair_t * balance,
|
||||
unsigned int * index,
|
||||
const bool inverted) const;
|
||||
};
|
||||
|
||||
class ignore_transaction
|
||||
{
|
||||
public:
|
||||
void operator()(transaction_t * xact,
|
||||
balance_pair_t * balance,
|
||||
unsigned int * index,
|
||||
const bool inverted) const {}
|
||||
};
|
||||
|
||||
template <typename Function>
|
||||
void handle_transaction(transaction_t * xact,
|
||||
Function functor,
|
||||
item_predicate& pred_functor,
|
||||
const bool related,
|
||||
const bool inverted,
|
||||
balance_pair_t * balance = NULL,
|
||||
unsigned int * index = NULL)
|
||||
{
|
||||
// If inverted is true, it implies related.
|
||||
if (! inverted && ! (xact->flags & TRANSACTION_HANDLED)) {
|
||||
xact->flags |= TRANSACTION_HANDLED;
|
||||
if (pred_functor(xact)) {
|
||||
xact->flags |= TRANSACTION_DISPLAYED;
|
||||
functor(xact, balance, index, inverted);
|
||||
}
|
||||
}
|
||||
|
||||
if (related)
|
||||
for (transactions_list::iterator i = xact->entry->transactions.begin();
|
||||
i != xact->entry->transactions.end();
|
||||
i++) {
|
||||
if (*i == xact || ((*i)->flags & (TRANSACTION_AUTO |
|
||||
TRANSACTION_HANDLED)))
|
||||
continue;
|
||||
|
||||
(*i)->flags |= TRANSACTION_HANDLED;
|
||||
if (pred_functor(xact)) {
|
||||
xact->flags |= TRANSACTION_DISPLAYED;
|
||||
functor(*i, balance, index, inverted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
void walk_entries(entries_list::iterator begin,
|
||||
entries_list::iterator end,
|
||||
Function functor,
|
||||
const node_t * predicate,
|
||||
const bool related,
|
||||
const bool inverted,
|
||||
const node_t * display_predicate = NULL)
|
||||
{
|
||||
balance_pair_t balance;
|
||||
unsigned int index;
|
||||
item_predicate pred_functor(predicate, &balance, &index);
|
||||
item_predicate disp_pred_functor(display_predicate, &balance, &index);
|
||||
|
||||
for (entries_list::iterator i = begin; i != end; i++)
|
||||
for (transactions_list::iterator j = (*i)->transactions.begin();
|
||||
j != (*i)->transactions.end();
|
||||
j++)
|
||||
if (pred_functor(*j))
|
||||
handle_transaction(*j, functor, disp_pred_functor,
|
||||
related, inverted, &balance, &index);
|
||||
}
|
||||
|
||||
class format_account
|
||||
{
|
||||
std::ostream& output_stream;
|
||||
const format_t& format;
|
||||
|
||||
mutable const account_t * last_account;
|
||||
|
||||
public:
|
||||
format_account(std::ostream& _output_stream, const format_t& _format)
|
||||
: output_stream(_output_stream), format(_format) {}
|
||||
|
||||
void operator()(const account_t * account, bool report_top = false);
|
||||
};
|
||||
|
||||
void calc__accounts(account_t * account,
|
||||
const node_t * predicate,
|
||||
const bool related,
|
||||
const bool inverted,
|
||||
const bool calc_subtotals);
|
||||
|
||||
template <typename Function>
|
||||
void walk__accounts(const account_t * account,
|
||||
Function functor,
|
||||
const node_t * display_predicate)
|
||||
{
|
||||
if (! display_predicate || item_predicate(display_predicate)(account))
|
||||
functor(account);
|
||||
|
||||
for (accounts_map::const_iterator i = account->accounts.begin();
|
||||
i != account->accounts.end();
|
||||
i++)
|
||||
walk__accounts((*i).second, functor, display_predicate);
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
void walk_accounts(account_t * account,
|
||||
Function functor,
|
||||
const node_t * predicate,
|
||||
const bool related,
|
||||
const bool inverted,
|
||||
const bool calc_subtotals,
|
||||
const node_t * display_predicate = NULL)
|
||||
{
|
||||
calc__accounts(account, predicate, related, inverted, calc_subtotals);
|
||||
walk__accounts<Function>(account, functor, display_predicate);
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
void sum_entries(entries_list& entries,
|
||||
account_t * account,
|
||||
const bool show_subtotals)
|
||||
{
|
||||
for (entries_list::const_iterator i = entries.begin();
|
||||
i != entries.end();
|
||||
i++)
|
||||
for (transactions_list::const_iterator j = (*i)->transactions.begin();
|
||||
j != (*i)->transactions.end();
|
||||
j++)
|
||||
if ((*j)->account == account) {
|
||||
account->value += *(*j);
|
||||
if (show_subtotals)
|
||||
for (account_t * a = account;
|
||||
a;
|
||||
a = a->parent)
|
||||
a->total += *(*j);
|
||||
}
|
||||
|
||||
for (accounts_map::iterator i = account->accounts.begin();
|
||||
i != account->accounts.end();
|
||||
i++)
|
||||
sum_items(entries, *i, show_subtotals);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace ledger
|
||||
|
||||
#endif // _WALK_H
|
||||
Loading…
Add table
Reference in a new issue