predicate are now used instead of constraints
This commit is contained in:
parent
94e76ae87e
commit
493694f848
10 changed files with 343 additions and 446 deletions
3
Makefile
3
Makefile
|
|
@ -1,6 +1,5 @@
|
|||
CODE = amount.cc balance.cc account.cc ledger.cc \
|
||||
constraint.cc item.cc expr.cc format.cc \
|
||||
textual.cc binary.cc
|
||||
item.cc expr.cc format.cc textual.cc binary.cc
|
||||
OBJS = $(patsubst %.cc,%.o,$(CODE))
|
||||
#CXX = cc
|
||||
CXX = g++
|
||||
|
|
|
|||
2
NEWS
2
NEWS
|
|
@ -70,7 +70,7 @@
|
|||
i index (within the report)
|
||||
o item age = date - report begin date
|
||||
w item newness = report end date - date
|
||||
|
||||
|
||||
b report begin date
|
||||
e report end date
|
||||
|
||||
|
|
|
|||
229
constraint.cc
229
constraint.cc
|
|
@ -1,229 +0,0 @@
|
|||
#include "constraint.h"
|
||||
#include "expr.h"
|
||||
|
||||
#include <pcre.h>
|
||||
|
||||
namespace ledger {
|
||||
|
||||
constraints_t::~constraints_t()
|
||||
{
|
||||
if (predicate) delete predicate;
|
||||
if (sort_order) delete sort_order;
|
||||
}
|
||||
|
||||
mask_t::mask_t(const std::string& pat) : exclude(false)
|
||||
{
|
||||
const char * p = pat.c_str();
|
||||
if (*p == '-') {
|
||||
exclude = true;
|
||||
p++;
|
||||
while (std::isspace(*p))
|
||||
p++;
|
||||
}
|
||||
else if (*p == '+') {
|
||||
p++;
|
||||
while (std::isspace(*p))
|
||||
p++;
|
||||
}
|
||||
pattern = p;
|
||||
|
||||
const char *error;
|
||||
int erroffset;
|
||||
regexp = pcre_compile(pattern.c_str(), PCRE_CASELESS,
|
||||
&error, &erroffset, NULL);
|
||||
if (! regexp)
|
||||
std::cerr << "Warning: Failed to compile regexp: " << pattern
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
mask_t::mask_t(const mask_t& m) : exclude(m.exclude), pattern(m.pattern)
|
||||
{
|
||||
const char *error;
|
||||
int erroffset;
|
||||
regexp = pcre_compile(pattern.c_str(), PCRE_CASELESS,
|
||||
&error, &erroffset, NULL);
|
||||
assert(regexp);
|
||||
}
|
||||
|
||||
bool mask_t::match(const std::string& str) const
|
||||
{
|
||||
static int ovec[30];
|
||||
int result = pcre_exec((pcre *)regexp, NULL,
|
||||
str.c_str(), str.length(), 0, 0, ovec, 30);
|
||||
return result >= 0 && ! exclude;
|
||||
}
|
||||
|
||||
mask_t::~mask_t() {
|
||||
pcre_free((pcre *)regexp);
|
||||
}
|
||||
|
||||
bool matches(const masks_list& regexps, const std::string& str,
|
||||
bool * by_exclusion)
|
||||
{
|
||||
if (regexps.empty())
|
||||
return false;
|
||||
|
||||
bool match = false;
|
||||
bool definite = false;
|
||||
|
||||
for (masks_list::const_iterator r = regexps.begin();
|
||||
r != regexps.end();
|
||||
r++) {
|
||||
static int ovec[30];
|
||||
int result = pcre_exec((pcre *)(*r).regexp, NULL,
|
||||
str.c_str(), str.length(), 0, 0, ovec, 30);
|
||||
if (result >= 0) {
|
||||
match = ! (*r).exclude;
|
||||
definite = true;
|
||||
}
|
||||
else if ((*r).exclude) {
|
||||
if (! match)
|
||||
match = ! definite;
|
||||
}
|
||||
else {
|
||||
definite = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (by_exclusion)
|
||||
*by_exclusion = match && ! definite && by_exclusion;
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
bool constraints_t::matches_date_range(const std::time_t date) const
|
||||
{
|
||||
if (begin_date != -1 && difftime(date, begin_date) < 0)
|
||||
return false;
|
||||
|
||||
if (end_date != -1 && difftime(date, end_date) >= 0)
|
||||
return false;
|
||||
|
||||
if (have_date_mask) {
|
||||
struct std::tm * then = std::gmtime(&date);
|
||||
|
||||
if (date_mask.tm_mon != -1 &&
|
||||
date_mask.tm_mon != then->tm_mon)
|
||||
return false;
|
||||
|
||||
if (date_mask.tm_mday != -1 &&
|
||||
date_mask.tm_mday != then->tm_mday)
|
||||
return false;
|
||||
|
||||
#if 0
|
||||
// jww (2003-10-10): This causes only certain days of the week to
|
||||
// print, even when it was not included in the mask.
|
||||
if (date_mask.tm_wday != -1 &&
|
||||
date_mask.tm_wday != then->tm_wday)
|
||||
return false;
|
||||
#endif
|
||||
|
||||
if (date_mask.tm_year != -1 &&
|
||||
date_mask.tm_year != then->tm_year)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool constraints_t::operator ()(const transaction_t * xact) const
|
||||
{
|
||||
if ((cleared_only && xact->entry->state != entry_t::CLEARED) ||
|
||||
(uncleared_only && xact->entry->state == entry_t::CLEARED) ||
|
||||
! matches_date_range(xact->entry->date))
|
||||
return false;
|
||||
|
||||
if (! payee_masks.empty() &&
|
||||
(! (matches(payee_masks, xact->entry->payee)
|
||||
//|| matches(payee_masks, xact->entry->code))
|
||||
)))
|
||||
return false;
|
||||
|
||||
if (real_only && xact->flags & TRANSACTION_VIRTUAL)
|
||||
return false;
|
||||
|
||||
if (! account_masks.empty() &&
|
||||
! (matches(account_masks, std::string(*(xact->account)))
|
||||
//|| matches(account_masks, (*i)->note)
|
||||
))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool constraints_t::operator ()(const entry_t * entry) const
|
||||
{
|
||||
if ((cleared_only && entry->state != entry_t::CLEARED) ||
|
||||
(uncleared_only && entry->state == entry_t::CLEARED) ||
|
||||
! matches_date_range(entry->date))
|
||||
return false;
|
||||
|
||||
if (! payee_masks.empty() &&
|
||||
(! (matches(payee_masks, entry->payee)
|
||||
//|| matches(payee_masks, entry->code)
|
||||
)))
|
||||
return false;
|
||||
|
||||
if (! account_masks.empty()) {
|
||||
bool match = false;
|
||||
|
||||
for (transactions_list::const_iterator i = entry->transactions.begin();
|
||||
i != entry->transactions.end();
|
||||
i++) {
|
||||
if (real_only && (*i)->flags & TRANSACTION_VIRTUAL)
|
||||
continue;
|
||||
|
||||
if (matches(account_masks, std::string(*((*i)->account)))
|
||||
//|| matches(account_masks, (*i)->note)
|
||||
) {
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! match)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool constraints_t::operator ()(const item_t * item) const
|
||||
{
|
||||
if (predicate && ! predicate->compute(item, begin(), end()))
|
||||
return false;
|
||||
|
||||
if (! matches_date_range(item->date))
|
||||
return false;
|
||||
|
||||
if (! payee_masks.empty() && ! matches(payee_masks, item->payee))
|
||||
return false;
|
||||
|
||||
#if 0
|
||||
// jww (2004-07-26): It shouldn't be necessary to check against the
|
||||
// account here, since this is always done during initial compiling
|
||||
// of the item_t tree.
|
||||
|
||||
if (! account_masks.empty()) {
|
||||
bool match = false;
|
||||
|
||||
for (amounts_map::const_iterator i = item->value.quantity.amounts.begin();
|
||||
i != item->value.quantity.amounts.end();
|
||||
i++) {
|
||||
if (matches(account_masks, std::string(*((*i)->account)))
|
||||
//|| matches(account_masks, (*i)->note)
|
||||
) {
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! match)
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ledger
|
||||
106
constraint.h
106
constraint.h
|
|
@ -2,6 +2,7 @@
|
|||
#define _CONSTRAINT_H
|
||||
|
||||
#include "ledger.h"
|
||||
#include "expr.h"
|
||||
#include "item.h"
|
||||
|
||||
template <typename ForwardIterator, typename ValueType, typename Constraint>
|
||||
|
|
@ -60,96 +61,71 @@ class constrained_iterator
|
|||
|
||||
namespace ledger {
|
||||
|
||||
class mask_t
|
||||
{
|
||||
public:
|
||||
bool exclude;
|
||||
std::string pattern;
|
||||
void * regexp;
|
||||
|
||||
explicit mask_t(const std::string& pattern);
|
||||
mask_t(const mask_t&);
|
||||
|
||||
~mask_t();
|
||||
|
||||
bool match(const std::string& str) const;
|
||||
};
|
||||
|
||||
typedef std::list<mask_t> masks_list;
|
||||
|
||||
bool matches(const masks_list& regexps, const std::string& str,
|
||||
bool * by_exclusion = NULL);
|
||||
|
||||
|
||||
struct node_t;
|
||||
|
||||
enum periodicity_t {
|
||||
PERIOD_NONE,
|
||||
PERIOD_MONTHLY,
|
||||
PERIOD_WEEKLY_SUN,
|
||||
PERIOD_WEEKLY_MON
|
||||
};
|
||||
|
||||
class constraints_t
|
||||
{
|
||||
public:
|
||||
bool real_only;
|
||||
bool cleared_only;
|
||||
bool uncleared_only;
|
||||
|
||||
bool show_expanded;
|
||||
bool show_related;
|
||||
bool show_inverted;
|
||||
bool show_subtotals;
|
||||
bool show_empty;
|
||||
|
||||
std::time_t begin_date;
|
||||
std::time_t end_date;
|
||||
struct std::tm date_mask;
|
||||
bool have_date_mask;
|
||||
|
||||
masks_list payee_masks;
|
||||
masks_list account_masks;
|
||||
|
||||
periodicity_t period;
|
||||
node_t * predicate;
|
||||
node_t * sort_order;
|
||||
node_t * predicate;
|
||||
|
||||
explicit constraints_t() {
|
||||
real_only = false;
|
||||
cleared_only = false;
|
||||
uncleared_only = false;
|
||||
|
||||
show_expanded = false;
|
||||
show_related = false;
|
||||
show_inverted = false;
|
||||
show_subtotals = true;
|
||||
show_empty = false;
|
||||
|
||||
begin_date = -1;
|
||||
end_date = -1;
|
||||
have_date_mask = false;
|
||||
|
||||
period = PERIOD_NONE;
|
||||
predicate = NULL;
|
||||
sort_order = NULL;
|
||||
}
|
||||
|
||||
~constraints_t();
|
||||
|
||||
std::time_t begin() const {
|
||||
return begin_date == -1 ? 0 : begin_date;
|
||||
~constraints_t() {
|
||||
if (predicate) delete predicate;
|
||||
}
|
||||
|
||||
std::time_t end() const {
|
||||
return end_date == -1 ? std::time(NULL) : end_date;
|
||||
bool operator ()(const transaction_t * xact) const {
|
||||
if (! predicate) {
|
||||
return true;
|
||||
} else {
|
||||
item_t temp;
|
||||
temp.date = xact->entry->date;
|
||||
temp.payee = xact->entry->payee;
|
||||
temp.account = xact->account;
|
||||
return predicate->compute(&temp);
|
||||
}
|
||||
}
|
||||
|
||||
bool matches_date_range(const std::time_t date) const;
|
||||
bool operator ()(const entry_t * entry) const {
|
||||
if (! predicate) {
|
||||
return true;
|
||||
} else {
|
||||
item_t temp;
|
||||
temp.date = entry->date;
|
||||
temp.payee = entry->payee;
|
||||
|
||||
bool operator ()(const transaction_t * xact) const;
|
||||
bool operator ()(const entry_t * entry) const;
|
||||
bool operator ()(const item_t * item) const;
|
||||
// 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.account = (*i)->account;
|
||||
if (predicate->compute(&temp))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool operator ()(const item_t * item) const {
|
||||
return ! predicate || predicate->compute(item);
|
||||
}
|
||||
};
|
||||
|
||||
typedef constrained_iterator<transactions_list::const_iterator, transaction_t *,
|
||||
|
|
|
|||
169
expr.cc
169
expr.cc
|
|
@ -2,11 +2,95 @@
|
|||
#include "error.h"
|
||||
#include "textual.h"
|
||||
|
||||
#include <pcre.h>
|
||||
|
||||
namespace ledger {
|
||||
|
||||
balance_t node_t::compute(const item_t * item,
|
||||
const std::time_t begin,
|
||||
const std::time_t end) const
|
||||
mask_t::mask_t(const std::string& pat) : exclude(false)
|
||||
{
|
||||
const char * p = pat.c_str();
|
||||
if (*p == '-') {
|
||||
exclude = true;
|
||||
p++;
|
||||
while (std::isspace(*p))
|
||||
p++;
|
||||
}
|
||||
else if (*p == '+') {
|
||||
p++;
|
||||
while (std::isspace(*p))
|
||||
p++;
|
||||
}
|
||||
pattern = p;
|
||||
|
||||
const char *error;
|
||||
int erroffset;
|
||||
regexp = pcre_compile(pattern.c_str(), PCRE_CASELESS,
|
||||
&error, &erroffset, NULL);
|
||||
if (! regexp)
|
||||
std::cerr << "Warning: Failed to compile regexp: " << pattern
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
mask_t::mask_t(const mask_t& m) : exclude(m.exclude), pattern(m.pattern)
|
||||
{
|
||||
const char *error;
|
||||
int erroffset;
|
||||
regexp = pcre_compile(pattern.c_str(), PCRE_CASELESS,
|
||||
&error, &erroffset, NULL);
|
||||
assert(regexp);
|
||||
}
|
||||
|
||||
bool mask_t::match(const std::string& str) const
|
||||
{
|
||||
static int ovec[30];
|
||||
int result = pcre_exec((pcre *)regexp, NULL,
|
||||
str.c_str(), str.length(), 0, 0, ovec, 30);
|
||||
return result >= 0 && ! exclude;
|
||||
}
|
||||
|
||||
mask_t::~mask_t() {
|
||||
pcre_free((pcre *)regexp);
|
||||
}
|
||||
|
||||
#if 1
|
||||
|
||||
bool matches(const masks_list& regexps, const std::string& str,
|
||||
bool * by_exclusion)
|
||||
{
|
||||
if (regexps.empty())
|
||||
return false;
|
||||
|
||||
bool match = false;
|
||||
bool definite = false;
|
||||
|
||||
for (masks_list::const_iterator r = regexps.begin();
|
||||
r != regexps.end();
|
||||
r++) {
|
||||
static int ovec[30];
|
||||
int result = pcre_exec((pcre *)(*r).regexp, NULL,
|
||||
str.c_str(), str.length(), 0, 0, ovec, 30);
|
||||
if (result >= 0) {
|
||||
match = ! (*r).exclude;
|
||||
definite = true;
|
||||
}
|
||||
else if ((*r).exclude) {
|
||||
if (! match)
|
||||
match = ! definite;
|
||||
}
|
||||
else {
|
||||
definite = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (by_exclusion)
|
||||
*by_exclusion = match && ! definite && by_exclusion;
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
balance_t node_t::compute(const item_t * item) const
|
||||
{
|
||||
balance_t temp;
|
||||
|
||||
|
|
@ -44,35 +128,44 @@ balance_t node_t::compute(const item_t * item,
|
|||
temp = amount_t((unsigned int) item->date);
|
||||
break;
|
||||
|
||||
case CLEARED:
|
||||
#if 0
|
||||
temp = amount_t(item->state == CLEARED ? 1 : 0);
|
||||
#endif
|
||||
break;
|
||||
|
||||
case REAL:
|
||||
#if 0
|
||||
temp = amount_t(item->flags & TRANSACTION_VIRTUAL ? 0 : 1);
|
||||
#endif
|
||||
break;
|
||||
|
||||
case INDEX:
|
||||
temp = amount_t(item->index + 1);
|
||||
break;
|
||||
|
||||
case BEGIN_DATE:
|
||||
temp = amount_t((unsigned int) begin);
|
||||
break;
|
||||
|
||||
case END_DATE:
|
||||
temp = amount_t((unsigned int) end);
|
||||
break;
|
||||
|
||||
case F_ARITH_MEAN:
|
||||
assert(left);
|
||||
temp = left->compute(item, begin, end);
|
||||
temp = left->compute(item);
|
||||
temp /= amount_t(item->index + 1);
|
||||
break;
|
||||
|
||||
case F_NEG:
|
||||
assert(left);
|
||||
temp = left->compute(item, begin, end).negated();
|
||||
temp = left->compute(item).negated();
|
||||
break;
|
||||
|
||||
case F_ABS:
|
||||
assert(left);
|
||||
temp = abs(left->compute(item, begin, end));
|
||||
temp = abs(left->compute(item));
|
||||
break;
|
||||
|
||||
case F_REGEXP:
|
||||
case F_PAYEE_MASK:
|
||||
assert(mask);
|
||||
temp = mask->match(item->payee);
|
||||
break;
|
||||
|
||||
case F_ACCOUNT_MASK:
|
||||
assert(mask);
|
||||
temp = (item->account &&
|
||||
mask->match(item->account->fullname())) ? 1 : 0;
|
||||
|
|
@ -80,14 +173,12 @@ balance_t node_t::compute(const item_t * item,
|
|||
|
||||
case F_VALUE: {
|
||||
assert(left);
|
||||
temp = left->compute(item, begin, end);
|
||||
temp = left->compute(item);
|
||||
|
||||
std::time_t moment = -1;
|
||||
if (right) {
|
||||
switch (right->type) {
|
||||
case DATE: moment = item->date; break;
|
||||
case BEGIN_DATE: moment = begin; break;
|
||||
case END_DATE: moment = end; break;
|
||||
case DATE: moment = item->date; break;
|
||||
default:
|
||||
throw compute_error("Invalid date passed to P(v,d)");
|
||||
}
|
||||
|
|
@ -97,15 +188,15 @@ balance_t node_t::compute(const item_t * item,
|
|||
}
|
||||
|
||||
case O_NOT:
|
||||
temp = left->compute(item, begin, end) ? 0 : 1;
|
||||
temp = left->compute(item) ? 0 : 1;
|
||||
break;
|
||||
|
||||
case O_QUES:
|
||||
temp = left->compute(item, begin, end);
|
||||
temp = left->compute(item);
|
||||
if (temp)
|
||||
temp = right->left->compute(item, begin, end);
|
||||
temp = right->left->compute(item);
|
||||
else
|
||||
temp = right->right->compute(item, begin, end);
|
||||
temp = right->right->compute(item);
|
||||
break;
|
||||
|
||||
case O_AND:
|
||||
|
|
@ -121,8 +212,8 @@ balance_t node_t::compute(const item_t * item,
|
|||
case O_DIV: {
|
||||
assert(left);
|
||||
assert(right);
|
||||
balance_t left_bal = left->compute(item, begin, end);
|
||||
balance_t right_bal = right->compute(item, begin, end);
|
||||
balance_t left_bal = left->compute(item);
|
||||
balance_t right_bal = right->compute(item);
|
||||
switch (type) {
|
||||
case O_AND: temp = (left_bal && right_bal) ? 1 : 0; break;
|
||||
case O_OR: temp = (left_bal || right_bal) ? 1 : 0; break;
|
||||
|
|
@ -197,8 +288,8 @@ node_t * parse_term(std::istream& in)
|
|||
case 'a': node = new node_t(node_t::AMOUNT); break;
|
||||
case 'c': node = new node_t(node_t::COST); break;
|
||||
case 'd': node = new node_t(node_t::DATE); break;
|
||||
case 'b': node = new node_t(node_t::BEGIN_DATE); break;
|
||||
case 'e': node = new node_t(node_t::END_DATE); break;
|
||||
case 'X': node = new node_t(node_t::CLEARED); break;
|
||||
case 'R': node = new node_t(node_t::REAL); break;
|
||||
case 'i': node = new node_t(node_t::INDEX); break;
|
||||
case 'B': node = new node_t(node_t::BALANCE); break;
|
||||
case 'T': node = new node_t(node_t::TOTAL); break;
|
||||
|
|
@ -256,8 +347,15 @@ node_t * parse_term(std::istream& in)
|
|||
// Other
|
||||
case '/': {
|
||||
std::string ident;
|
||||
bool payee_mask = false;
|
||||
|
||||
c = in.peek();
|
||||
if (c == '/') {
|
||||
payee_mask = true;
|
||||
in.get(c);
|
||||
c = in.peek();
|
||||
}
|
||||
|
||||
while (! in.eof() && c != '/') {
|
||||
in.get(c);
|
||||
if (c == '\\')
|
||||
|
|
@ -265,9 +363,11 @@ node_t * parse_term(std::istream& in)
|
|||
ident += c;
|
||||
c = in.peek();
|
||||
}
|
||||
|
||||
if (c == '/') {
|
||||
in.get(c);
|
||||
node = new node_t(node_t::F_REGEXP);
|
||||
node = new node_t(payee_mask ?
|
||||
node_t::F_PAYEE_MASK : node_t::F_ACCOUNT_MASK);
|
||||
node->mask = new mask_t(ident);
|
||||
} else {
|
||||
throw expr_error("Missing closing '/'");
|
||||
|
|
@ -517,13 +617,13 @@ static void dump_tree(std::ostream& out, node_t * node)
|
|||
case node_t::AMOUNT: out << "AMOUNT"; break;
|
||||
case node_t::COST: out << "COST"; break;
|
||||
case node_t::DATE: out << "DATE"; break;
|
||||
case node_t::CLEARED: out << "CLEARED"; break;
|
||||
case node_t::REAL: out << "REAL"; break;
|
||||
case node_t::INDEX: out << "INDEX"; break;
|
||||
case node_t::BALANCE: out << "BALANCE"; break;
|
||||
case node_t::COST_BALANCE: out << "COST_BALANCE"; break;
|
||||
case node_t::TOTAL: out << "TOTAL"; break;
|
||||
case node_t::COST_TOTAL: out << "COST_TOTAL"; break;
|
||||
case node_t::BEGIN_DATE: out << "BEGIN"; break;
|
||||
case node_t::END_DATE: out << "END"; break;
|
||||
|
||||
case node_t::F_ARITH_MEAN:
|
||||
out << "MEAN(";
|
||||
|
|
@ -543,9 +643,14 @@ static void dump_tree(std::ostream& out, node_t * node)
|
|||
out << ")";
|
||||
break;
|
||||
|
||||
case node_t::F_REGEXP:
|
||||
case node_t::F_PAYEE_MASK:
|
||||
assert(node->mask);
|
||||
out << "RE(" << node->mask->pattern << ")";
|
||||
out << "P_MASK(" << node->mask->pattern << ")";
|
||||
break;
|
||||
|
||||
case node_t::F_ACCOUNT_MASK:
|
||||
assert(node->mask);
|
||||
out << "A_MASK(" << node->mask->pattern << ")";
|
||||
break;
|
||||
|
||||
case node_t::F_VALUE:
|
||||
|
|
|
|||
38
expr.h
38
expr.h
|
|
@ -3,10 +3,33 @@
|
|||
|
||||
#include "ledger.h"
|
||||
#include "balance.h"
|
||||
#include "constraint.h"
|
||||
#include "item.h"
|
||||
|
||||
namespace ledger {
|
||||
|
||||
class mask_t
|
||||
{
|
||||
public:
|
||||
bool exclude;
|
||||
std::string pattern;
|
||||
void * regexp;
|
||||
|
||||
explicit mask_t(const std::string& pattern);
|
||||
mask_t(const mask_t&);
|
||||
|
||||
~mask_t();
|
||||
|
||||
bool match(const std::string& str) const;
|
||||
};
|
||||
|
||||
#if 1
|
||||
typedef std::list<mask_t> masks_list;
|
||||
|
||||
bool matches(const masks_list& regexps, const std::string& str,
|
||||
bool * by_exclusion = NULL);
|
||||
#endif
|
||||
|
||||
|
||||
struct node_t
|
||||
{
|
||||
enum kind_t {
|
||||
|
|
@ -18,6 +41,8 @@ struct node_t
|
|||
AMOUNT,
|
||||
COST,
|
||||
DATE,
|
||||
CLEARED,
|
||||
REAL,
|
||||
INDEX,
|
||||
|
||||
// Item totals
|
||||
|
|
@ -26,16 +51,13 @@ struct node_t
|
|||
TOTAL,
|
||||
COST_TOTAL,
|
||||
|
||||
// Constraint details
|
||||
BEGIN_DATE,
|
||||
END_DATE,
|
||||
|
||||
// Functions
|
||||
F_ARITH_MEAN,
|
||||
F_VALUE,
|
||||
F_NEG,
|
||||
F_ABS,
|
||||
F_REGEXP,
|
||||
F_PAYEE_MASK,
|
||||
F_ACCOUNT_MASK,
|
||||
|
||||
// Binary operators
|
||||
O_ADD,
|
||||
|
|
@ -73,9 +95,7 @@ struct node_t
|
|||
if (right) delete right;
|
||||
}
|
||||
|
||||
balance_t compute(const item_t * item,
|
||||
const std::time_t begin = -1,
|
||||
const std::time_t end = -1) const;
|
||||
balance_t compute(const item_t * item) const;
|
||||
};
|
||||
|
||||
node_t * parse_expr(std::istream& in);
|
||||
|
|
|
|||
|
|
@ -14,8 +14,7 @@ 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 maximal_account_name(const item_t * item, const item_t * parent)
|
||||
{
|
||||
std::string name = item->account->name;
|
||||
for (const item_t * i = item->parent;
|
||||
|
|
|
|||
29
format.h
29
format.h
|
|
@ -63,37 +63,12 @@ struct format_t
|
|||
void format_elements(std::ostream& out, const item_t * item,
|
||||
const item_t * displayed_parent = NULL) const;
|
||||
|
||||
#if 1
|
||||
static balance_t compute_value(const item_t * item) {
|
||||
if (value_expr)
|
||||
return value_expr->compute(item);
|
||||
else
|
||||
return balance_t();
|
||||
return value_expr ? value_expr->compute(item) : balance_t();
|
||||
}
|
||||
|
||||
static balance_t compute_total(const item_t * item) {
|
||||
if (total_expr)
|
||||
return total_expr->compute(item);
|
||||
else
|
||||
return balance_t();
|
||||
return total_expr ? total_expr->compute(item) : balance_t();
|
||||
}
|
||||
#else
|
||||
static balance_t compute_value(const item_t * item,
|
||||
const constraints_t& constraints) {
|
||||
if (value_expr)
|
||||
return value_expr->compute(item, constraints.begin(), constraints.end());
|
||||
else
|
||||
return balance_t();
|
||||
}
|
||||
|
||||
static balance_t compute_total(const item_t * item,
|
||||
const constraints_t& constraints) {
|
||||
if (total_expr)
|
||||
return total_expr->compute(item, constraints.begin(), constraints.end());
|
||||
else
|
||||
return balance_t();
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace ledger
|
||||
|
|
|
|||
1
item.cc
1
item.cc
|
|
@ -12,7 +12,6 @@ item_t * walk_accounts(const account_t * account,
|
|||
{
|
||||
item_t * item = new item_t;
|
||||
item->account = account;
|
||||
item->date = constraints.end() - 1;
|
||||
|
||||
for (constrained_transactions_list_const_iterator
|
||||
i(account->transactions.begin(),
|
||||
|
|
|
|||
209
main.cc
209
main.cc
|
|
@ -23,53 +23,54 @@ namespace ledger {
|
|||
|
||||
static const std::string bal_fmt = "%20T%2_%-n\n";
|
||||
|
||||
void show_balances(std::ostream& out,
|
||||
items_deque& items,
|
||||
const constraints_t& constraints,
|
||||
const format_t& format,
|
||||
const item_t * displayed_parent)
|
||||
unsigned int show_balances(std::ostream& out,
|
||||
items_deque& items,
|
||||
const constraints_t& constraints,
|
||||
const node_t * sort_order,
|
||||
const format_t& format,
|
||||
const item_t * displayed_parent)
|
||||
{
|
||||
unsigned int headlines = 0;
|
||||
|
||||
for (items_deque::const_iterator i = items.begin();
|
||||
i != items.end();
|
||||
i++) {
|
||||
const item_t * parent = displayed_parent;
|
||||
|
||||
bool by_exclusion = false;
|
||||
std::string name = maximal_account_name(*i, parent);
|
||||
const bool match = (constraints.show_expanded ||
|
||||
(! constraints.account_masks.empty() &&
|
||||
matches(constraints.account_masks, name,
|
||||
&by_exclusion) &&
|
||||
(! by_exclusion ||
|
||||
displayed_parent->parent == NULL)) ||
|
||||
(constraints.account_masks.empty() &&
|
||||
displayed_parent->parent == NULL));
|
||||
|
||||
if (match && constraints(*i) &&
|
||||
if (constraints(*i) &&
|
||||
((*i)->subitems.size() != 1 ||
|
||||
(*i)->total != (*i)->subitems[0]->total)) {
|
||||
format.format_elements(out, *i, parent);
|
||||
parent = *i;
|
||||
|
||||
if (! displayed_parent->parent)
|
||||
headlines++;
|
||||
}
|
||||
|
||||
if (constraints.sort_order)
|
||||
(*i)->sort(constraints.sort_order);
|
||||
if (sort_order)
|
||||
(*i)->sort(sort_order);
|
||||
|
||||
show_balances(out, (*i)->subitems, constraints, format, parent);
|
||||
if (constraints.show_expanded)
|
||||
headlines += show_balances(out, (*i)->subitems, constraints,
|
||||
sort_order, format, parent);
|
||||
}
|
||||
|
||||
return headlines;
|
||||
}
|
||||
|
||||
void balance_report(std::ostream& out,
|
||||
item_t * top,
|
||||
const constraints_t& constraints,
|
||||
const format_t& format)
|
||||
void balance_report(std::ostream& out,
|
||||
item_t * top,
|
||||
const constraints_t& constraints,
|
||||
const node_t * sort_order,
|
||||
const format_t& format)
|
||||
{
|
||||
if (constraints.sort_order)
|
||||
top->sort(constraints.sort_order);
|
||||
if (sort_order)
|
||||
top->sort(sort_order);
|
||||
|
||||
show_balances(out, top->subitems, constraints, format, top);
|
||||
unsigned int headlines = show_balances(out, top->subitems, constraints,
|
||||
sort_order, format, top);
|
||||
|
||||
if (constraints.show_subtotals && top->subitems.size() > 1 && top->total) {
|
||||
if (constraints.show_subtotals && headlines > 1 && top->total) {
|
||||
std::cout << "--------------------\n";
|
||||
format.format_elements(std::cout, top);
|
||||
}
|
||||
|
|
@ -127,11 +128,12 @@ static void report_value_change(std::ostream& out,
|
|||
void register_report(std::ostream& out,
|
||||
item_t * top,
|
||||
const constraints_t& constraints,
|
||||
const node_t * sort_order,
|
||||
const format_t& first_line_format,
|
||||
const format_t& next_lines_format)
|
||||
{
|
||||
if (constraints.sort_order)
|
||||
top->sort(constraints.sort_order);
|
||||
if (sort_order)
|
||||
top->sort(sort_order);
|
||||
|
||||
balance_pair_t balance;
|
||||
balance_pair_t last_reported;
|
||||
|
|
@ -203,8 +205,8 @@ void register_report(std::ostream& out,
|
|||
}
|
||||
|
||||
if (show_commodities_revalued)
|
||||
report_value_change(out, constraints.end(), balance, last_reported,
|
||||
constraints, first_line_format, next_lines_format);
|
||||
report_value_change(out, -1, balance, last_reported, constraints,
|
||||
first_line_format, next_lines_format);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -471,11 +473,17 @@ int main(int argc, char * argv[])
|
|||
std::list<std::string> files;
|
||||
ledger::ledger_t * journal = new ledger::ledger_t;
|
||||
ledger::constraints_t constraints;
|
||||
std::string predicate;
|
||||
std::string format_string;
|
||||
std::string sort_order;
|
||||
std::string sort_str;
|
||||
ledger::node_t * sort_order = NULL;
|
||||
std::string value_expr = "a";
|
||||
std::string total_expr = "T";
|
||||
|
||||
#ifdef DEBUG
|
||||
bool debug = false;
|
||||
#endif
|
||||
|
||||
// Initialize some variables based on environment variable settings
|
||||
|
||||
if (char * p = std::getenv("PRICE_HIST"))
|
||||
|
|
@ -518,13 +526,19 @@ int main(int argc, char * argv[])
|
|||
int c, index;
|
||||
while (-1 !=
|
||||
(c = getopt(argc, argv,
|
||||
"+a:ABb:Ccd:DEe:F:f:Ghi:L:l:MN:noOP:p:QRS:st:T:UVvWXZ"))) {
|
||||
"+ABb:Ccd:DEe:F:f:Ghi:L:l:MnoOP:p:QRS:st:T:UVvWXZz"))) {
|
||||
switch (char(c)) {
|
||||
// Basic options
|
||||
case 'h':
|
||||
show_help(std::cout);
|
||||
break;
|
||||
|
||||
#ifdef DEBUG
|
||||
case 'z':
|
||||
debug = 1;
|
||||
break;
|
||||
#endif
|
||||
|
||||
case 'v':
|
||||
std::cout
|
||||
<< "Ledger " << ledger::version
|
||||
|
|
@ -547,47 +561,49 @@ int main(int argc, char * argv[])
|
|||
ledger::set_price_conversion(optarg);
|
||||
break;
|
||||
|
||||
// Constraint options
|
||||
case 'a':
|
||||
constraints.account_masks.push_back(ledger::mask_t(optarg));
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
if (! ledger::parse_date(optarg, &constraints.begin_date)) {
|
||||
std::cerr << "Error: Bad begin date: " << optarg << std::endl;
|
||||
return 1;
|
||||
}
|
||||
if (! predicate.empty())
|
||||
predicate += "&";
|
||||
predicate += "(d>=[";
|
||||
predicate += optarg;
|
||||
predicate += "])";
|
||||
break;
|
||||
|
||||
case 'e':
|
||||
if (! ledger::parse_date(optarg, &constraints.end_date)) {
|
||||
std::cerr << "Error: Bad end date: " << optarg << std::endl;
|
||||
return 1;
|
||||
}
|
||||
if (! predicate.empty())
|
||||
predicate += "&";
|
||||
predicate += "(d<[";
|
||||
predicate += optarg;
|
||||
predicate += "])";
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
constraints.end_date = std::time(NULL);
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
constraints.have_date_mask = true;
|
||||
if (! ledger::parse_date_mask(optarg, &constraints.date_mask)) {
|
||||
std::cerr << "Error: Bad date mask: " << optarg << std::endl;
|
||||
return 1;
|
||||
}
|
||||
case 'c': {
|
||||
if (! predicate.empty())
|
||||
predicate += "&";
|
||||
predicate += "(d<";
|
||||
std::ostringstream now;
|
||||
now << std::time(NULL);
|
||||
predicate += now.str();
|
||||
predicate += ")";
|
||||
break;
|
||||
}
|
||||
|
||||
case 'C':
|
||||
constraints.cleared_only = true;
|
||||
if (! predicate.empty())
|
||||
predicate += "&";
|
||||
predicate += "X";
|
||||
break;
|
||||
|
||||
case 'U':
|
||||
constraints.uncleared_only = true;
|
||||
if (! predicate.empty())
|
||||
predicate += "&";
|
||||
predicate += "!X";
|
||||
break;
|
||||
|
||||
case 'R':
|
||||
constraints.real_only = true;
|
||||
if (! predicate.empty())
|
||||
predicate += "&";
|
||||
predicate += "R";
|
||||
break;
|
||||
|
||||
// Customizing output
|
||||
|
|
@ -595,10 +611,6 @@ int main(int argc, char * argv[])
|
|||
format_string = optarg;
|
||||
break;
|
||||
|
||||
case 'M':
|
||||
constraints.period = ledger::PERIOD_MONTHLY;
|
||||
break;
|
||||
|
||||
case 'E':
|
||||
constraints.show_empty = true;
|
||||
break;
|
||||
|
|
@ -612,7 +624,7 @@ int main(int argc, char * argv[])
|
|||
break;
|
||||
|
||||
case 'S':
|
||||
sort_order = optarg;
|
||||
sort_str = optarg;
|
||||
break;
|
||||
|
||||
case 'o':
|
||||
|
|
@ -620,7 +632,11 @@ int main(int argc, char * argv[])
|
|||
break;
|
||||
|
||||
case 'l':
|
||||
constraints.predicate = ledger::parse_expr(optarg);
|
||||
if (! predicate.empty())
|
||||
predicate += "&";
|
||||
predicate += "(";
|
||||
predicate += optarg;
|
||||
predicate += ")";
|
||||
break;
|
||||
|
||||
// Commodity reporting
|
||||
|
|
@ -761,17 +777,51 @@ int main(int argc, char * argv[])
|
|||
index++;
|
||||
break;
|
||||
}
|
||||
constraints.account_masks.push_back(ledger::mask_t(argv[index]));
|
||||
|
||||
constraints.show_expanded = true;
|
||||
|
||||
if (! predicate.empty())
|
||||
predicate += "&";
|
||||
|
||||
if (argv[index][0] == '-') {
|
||||
predicate += "(!/";
|
||||
predicate += argv[index] + 1;
|
||||
} else {
|
||||
predicate += "(/";
|
||||
predicate += argv[index];
|
||||
}
|
||||
predicate += "/)";
|
||||
}
|
||||
|
||||
for (; index < argc; index++)
|
||||
constraints.payee_masks.push_back(ledger::mask_t(argv[index]));
|
||||
for (; index < argc; index++) {
|
||||
constraints.show_expanded = true;
|
||||
|
||||
if (! predicate.empty())
|
||||
predicate += "&";
|
||||
|
||||
if (argv[index][0] == '-') {
|
||||
predicate += "(!//";
|
||||
predicate += argv[index] + 1;
|
||||
} else {
|
||||
predicate += "(//";
|
||||
predicate += argv[index];
|
||||
}
|
||||
predicate += "/)";
|
||||
}
|
||||
|
||||
// Copy the constraints to the format object, and compile the value
|
||||
// and total style strings
|
||||
|
||||
if (! sort_order.empty())
|
||||
constraints.sort_order = ledger::parse_expr(sort_order);
|
||||
if (! predicate.empty()) {
|
||||
#ifdef DEBUG
|
||||
if (debug)
|
||||
std::cerr << "predicate = " << predicate << std::endl;
|
||||
#endif
|
||||
constraints.predicate = ledger::parse_expr(predicate);
|
||||
}
|
||||
|
||||
if (! sort_str.empty())
|
||||
sort_order = ledger::parse_expr(sort_str);
|
||||
|
||||
// Setup the meaning of %t and %T encountered in format strings
|
||||
|
||||
|
|
@ -784,8 +834,7 @@ int main(int argc, char * argv[])
|
|||
#if 0
|
||||
if (ledger::item_t * top
|
||||
= ledger::walk_entries(journal->entries.begin(),
|
||||
journal->entries.end(),
|
||||
constraints)) {
|
||||
journal->entries.end(), constraints)) {
|
||||
ledger::format_t * format = new ledger::format_t(format_string);
|
||||
ledger::entry_report(std::cout, top, *format);
|
||||
#ifdef DEBUG
|
||||
|
|
@ -808,15 +857,14 @@ int main(int argc, char * argv[])
|
|||
}
|
||||
#endif
|
||||
}
|
||||
else if (constraints.period == ledger::PERIOD_NONE &&
|
||||
! constraints.sort_order && ! constraints.show_related &&
|
||||
else if (! sort_order && ! constraints.show_related &&
|
||||
(command == "balance" || command == "bal")) {
|
||||
if (ledger::item_t * top
|
||||
= ledger::walk_accounts(journal->master, constraints)) {
|
||||
ledger::format_t * format
|
||||
= new ledger::format_t(format_string.empty() ?
|
||||
ledger::bal_fmt : format_string);
|
||||
ledger::balance_report(std::cout, top, constraints, *format);
|
||||
ledger::balance_report(std::cout, top, constraints, sort_order, *format);
|
||||
#ifdef DEBUG
|
||||
delete format;
|
||||
delete top;
|
||||
|
|
@ -832,7 +880,8 @@ int main(int argc, char * argv[])
|
|||
ledger::format_t * format
|
||||
= new ledger::format_t(format_string.empty() ?
|
||||
ledger::bal_fmt : format_string);
|
||||
ledger::balance_report(std::cout, top, constraints, *format);
|
||||
ledger::balance_report(std::cout, top, constraints, sort_order,
|
||||
*format);
|
||||
#ifdef DEBUG
|
||||
delete format;
|
||||
delete top;
|
||||
|
|
@ -862,7 +911,8 @@ int main(int argc, char * argv[])
|
|||
|
||||
ledger::format_t * format = new ledger::format_t(first_line_format);
|
||||
ledger::format_t * nformat = new ledger::format_t(next_lines_format);
|
||||
ledger::register_report(std::cout, top, constraints, *format, *nformat);
|
||||
ledger::register_report(std::cout, top, constraints, sort_order,
|
||||
*format, *nformat);
|
||||
#ifdef DEBUG
|
||||
delete format;
|
||||
delete top;
|
||||
|
|
@ -887,6 +937,9 @@ int main(int argc, char * argv[])
|
|||
#ifdef DEBUG
|
||||
delete journal;
|
||||
|
||||
if (sort_order)
|
||||
delete sort_order;
|
||||
|
||||
if (ledger::format_t::value_expr)
|
||||
delete ledger::format_t::value_expr;
|
||||
if (ledger::format_t::total_expr)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue