Regular expressions are working again, such that very basic register reports

are now possible.
This commit is contained in:
John Wiegley 2008-08-03 23:44:18 -04:00
parent 8a21391d0a
commit bcffbc96ba
8 changed files with 159 additions and 594 deletions

413
format.cc
View file

@ -272,430 +272,33 @@ void format_t::format(std::ostream& out_str, scope_t& scope)
try {
elem->expr.compile(scope);
value_t value;
if (elem->expr.is_function()) {
call_scope_t args(scope);
args.push_back(long(elem->max_width));
elem->expr.get_function()(args).dump(out, elem->min_width);
value = elem->expr.get_function()(args);
} else {
elem->expr.calc(scope).dump(out, elem->min_width);
value = elem->expr.calc(scope);
}
value.strip_annotations().dump(out, elem->min_width);
}
catch (const calc_error&) {
out << (string("%") + elem->chars);
}
break;
#if 0
case element_t::ACCOUNT_FULLNAME:
scope.resolve("account").dump(out, elem->min_width);
break;
case element_t::ACCOUNT_NAME:
scope.resolve("account_base").dump(out, elem->min_width);
break;
case element_t::AMOUNT:
out << "a";
//out << scope.resolve("amount");
break;
case element_t::TOTAL:
out << "T";
//out << scope.resolve("total");
break;
case element_t::VALUE_EXPR: {
expr_t * calc;
switch (elem->type) {
case element_t::AMOUNT:
assert(value_expr::amount_expr.get());
calc = value_expr::amount_expr.get();
break;
case element_t::TOTAL:
assert(value_expr::total_expr.get());
calc = value_expr::total_expr.get();
break;
case element_t::VALUE_EXPR:
calc = const_cast<value_expr *>(&elem->val_expr);
break;
default:
assert(false);
break;
}
if (! calc)
break;
value_t value;
const balance_t * bal = NULL;
calc->compute(value, details);
if (! amount_t::keep_price ||
! amount_t::keep_date ||
! amount_t::keep_tag) {
switch (value.type()) {
case value_t::AMOUNT:
case value_t::BALANCE:
case value_t::BALANCE_PAIR:
value = value.strip_annotations();
break;
default:
break;
}
}
bool highlighted = false;
switch (value.type()) {
case value_t::BOOLEAN:
out << (value.as_boolean() ? "true" : "false");
break;
case value_t::INTEGER:
if (ansi_codes && elem->flags & ELEMENT_HIGHLIGHT) {
if (ansi_invert) {
if (value.as_long() > 0) {
mark_red(out, elem);
highlighted = true;
}
} else {
if (value.as_long() < 0) {
mark_red(out, elem);
highlighted = true;
}
}
}
out << value.as_long();
break;
case value_t::DATETIME:
out << value.as_datetime();
break;
case value_t::AMOUNT:
if (ansi_codes && elem->flags & ELEMENT_HIGHLIGHT) {
if (ansi_invert) {
if (value.as_amount().sign() > 0) {
mark_red(out, elem);
highlighted = true;
}
} else {
if (value.as_amount().sign() < 0) {
mark_red(out, elem);
highlighted = true;
}
}
}
out << value.as_amount();
break;
case value_t::BALANCE:
bal = &(value.as_balance());
// fall through...
case value_t::BALANCE_PAIR:
if (! bal)
bal = &(value.as_balance_pair().quantity());
if (ansi_codes && elem->flags & ELEMENT_HIGHLIGHT) {
if (ansi_invert) {
if (*bal > 0) {
mark_red(out, elem);
highlighted = true;
}
} else {
if (*bal < 0) {
mark_red(out, elem);
highlighted = true;
}
}
}
bal->print(out, elem->min_width,
(elem->max_width > 0 ?
elem->max_width : elem->min_width));
ignore_max_width = true;
break;
default:
assert(false);
break;
}
if (highlighted)
mark_plain(out);
break;
}
case element_t::OPT_AMOUNT:
if (details.xact) {
string disp;
bool use_disp = false;
if (details.xact->cost && details.xact->amount) {
std::ostringstream stream;
if (! details.xact->amount_expr.expr_str.empty())
stream << details.xact->amount_expr.expr_str;
else
stream << details.xact->amount.strip_annotations();
if (details.xact->cost_expr)
stream << details.xact->cost_expr->expr_str;
else
stream << " @ " << amount_t(*details.xact->cost /
details.xact->amount).unround();
disp = stream.str();
use_disp = true;
}
else if (details.entry) {
unsigned int xacts_count = 0;
xact_t * first = NULL;
xact_t * last = NULL;
foreach (const transaction_t * xact, details.entry->xacts) {
if (xact_has_xdata(*xact) &&
xact_xdata_(*xact).dflags & XACT_TO_DISPLAY) {
xacts_count++;
if (! first)
first = xact;
last = xact;
}
}
use_disp = (xacts_count == 2 && details.xact == last &&
first->amount == - last->amount);
}
if (! use_disp) {
if (! details.xact->amount_expr.expr_str.empty())
out << details.xact->amount_expr.expr_str;
else
out << details.xact->amount.strip_annotations();
} else {
out << disp;
}
}
break;
case element_t::SOURCE:
if (details.entry && details.entry->journal) {
int idx = details.entry->src_idx;
foreach (const path& path, details.entry->journal->sources)
if (! idx--) {
out << path;
break;
}
}
break;
case element_t::ENTRY_BEG_POS:
if (details.entry)
out << (unsigned long)details.entry->beg_pos;
break;
case element_t::ENTRY_BEG_LINE:
if (details.entry)
out << details.entry->beg_line;
break;
case element_t::ENTRY_END_POS:
if (details.entry)
out << (unsigned long)details.entry->end_pos;
break;
case element_t::ENTRY_END_LINE:
if (details.entry)
out << details.entry->end_line;
break;
case element_t::XACT_BEG_POS:
if (details.xact)
out << (unsigned long)details.xact->beg_pos;
break;
case element_t::XACT_BEG_LINE:
if (details.xact)
out << details.xact->beg_line;
break;
case element_t::XACT_END_POS:
if (details.xact)
out << (unsigned long)details.xact->end_pos;
break;
case element_t::XACT_END_LINE:
if (details.xact)
out << details.xact->end_line;
break;
case element_t::DATE_STRING:
out << format_date(scope.resolve("date").as_date());
break;
case element_t::COMPLETE_DATE_STRING: {
date_t actual_date;
date_t effective_date;
if (details.xact) {
actual_date = details.xact->actual_date();
effective_date = details.xact->effective_date();
}
else if (details.entry) {
actual_date = details.entry->actual_date();
effective_date = details.entry->effective_date();
}
char abuf[256];
#if 0
// jww (2008-04-20): This needs to be rewritten
std::strftime(abuf, 255, elem->chars.c_str(), actual_date.localtime());
#else
abuf[0] = '\0';
#endif
if (is_valid(effective_date) && effective_date != actual_date) {
char buf[512];
char ebuf[256];
#if 0
// jww (2008-04-20): This needs to be rewritten
std::strftime(ebuf, 255, elem->chars.c_str(),
effective_date.localtime());
#else
ebuf[0] = '\0';
#endif
std::strcpy(buf, abuf);
std::strcat(buf, "=");
std::strcat(buf, ebuf);
out << (elem->max_width == 0 ? buf : truncate(buf, elem->max_width));
} else {
out << (elem->max_width == 0 ? abuf : truncate(abuf, elem->max_width));
}
break;
}
case element_t::CLEARED:
if (details.xact) {
switch (details.xact->state) {
case xact_t::CLEARED:
out << "* ";
break;
case xact_t::PENDING:
out << "! ";
break;
case xact_t::UNCLEARED:
break;
}
}
break;
case element_t::ENTRY_CLEARED:
if (details.entry) {
xact_t::state_t state;
if (details.entry->get_state(&state))
switch (state) {
case xact_t::CLEARED:
out << "* ";
break;
case xact_t::PENDING:
out << "! ";
break;
case xact_t::UNCLEARED:
break;
}
}
break;
case element_t::CODE: {
string temp;
if (details.entry && details.entry->code) {
temp += "(";
temp += *details.entry->code;
temp += ") ";
}
out << temp;
break;
}
case element_t::PAYEE:
scope.resolve("payee").dump(out, elem->min_width);
break;
case element_t::OPT_NOTE:
if (details.xact && details.xact->note)
out << " ; ";
// fall through...
case element_t::NOTE:
if (details.xact)
out << (elem->max_width == 0 ?
details.xact->note : truncate(*details.xact->note,
elem->max_width));
break;
case element_t::OPT_ACCOUNT:
if (details.entry && details.xact) {
xact_t::state_t state;
if (! details.entry->get_state(&state))
switch (details.xact->state) {
case xact_t::CLEARED:
name = "* ";
break;
case xact_t::PENDING:
name = "! ";
break;
case xact_t::UNCLEARED:
break;
}
}
// fall through...
case element_t::ACCOUNT_NAME:
case element_t::ACCOUNT_FULLNAME:
if (details.account) {
name += (elem->type == element_t::ACCOUNT_FULLNAME ?
details.account->fullname() :
partial_account_name(*details.account));
if (details.xact && details.xact->has_flags(XACT_VIRTUAL)) {
if (elem->max_width > 2)
name = truncate(name, elem->max_width - 2, true);
if (details.xact->has_flags(XACT_BALANCE))
name = string("[") + name + "]";
else
name = string("(") + name + ")";
}
else if (elem->max_width > 0)
name = truncate(name, elem->max_width, true);
out << name;
} else {
out << " ";
}
break;
case element_t::DEPTH_SPACER:
for (const account_t * acct = details.account;
acct;
acct = acct->parent)
if (account_has_xdata(*acct) &&
account_xdata_(*acct).dflags & ACCOUNT_DISPLAYED) {
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;
#endif
default:
assert(false);
break;
}
string temp = out.str();
if (! ignore_max_width &&
elem->max_width > 0 && elem->max_width < temp.length())
truncate(temp, elem->max_width);
out_str << temp;
out_str << truncate(temp, elem->max_width);
else
out_str << temp;
}
}

110
main.cc
View file

@ -52,6 +52,102 @@
#endif
namespace ledger {
string args_to_predicate(value_t::sequence_t::const_iterator begin,
value_t::sequence_t::const_iterator end)
{
string acct_value_expr;
string payee_value_expr;
string note_value_expr;
string * value_expr;
enum regexp_kind_t {
ACCOUNT_REGEXP,
PAYEE_REGEXP,
NOTE_REGEXP
}
kind = ACCOUNT_REGEXP;
value_expr = &acct_value_expr;
for ( ; begin != end; begin++) {
const string& arg((*begin).as_string());
if (arg == "--") {
kind = PAYEE_REGEXP;
value_expr = &payee_value_expr;
}
else if (arg == "/") {
kind = NOTE_REGEXP;
value_expr = &note_value_expr;
}
else {
if (! value_expr->empty())
*value_expr += "|";
switch (kind) {
case ACCOUNT_REGEXP:
*value_expr += "account =~ ";
break;
case PAYEE_REGEXP:
*value_expr += "payee =~ ";
break;
case NOTE_REGEXP:
*value_expr += "note =~ ";
break;
}
const char * p = arg.c_str();
if (*p == '-') {
*value_expr += "!";
p++;
}
*value_expr += "/";
while (*p) {
if (*p == '/')
*value_expr += "\\";
*value_expr += *p;
p++;
}
*value_expr += "/";
}
}
string final_value_expr;
if (! acct_value_expr.empty()) {
if (! payee_value_expr.empty() ||
! note_value_expr.empty())
final_value_expr = string("(") + acct_value_expr + ")";
else
final_value_expr = acct_value_expr;
}
if (! payee_value_expr.empty()) {
if (! acct_value_expr.empty())
final_value_expr += string("&(") + payee_value_expr + ")";
else if (! note_value_expr.empty())
final_value_expr = string("(") + payee_value_expr + ")";
else
final_value_expr = payee_value_expr;
}
if (! note_value_expr.empty()) {
if (! acct_value_expr.empty() ||
! payee_value_expr.empty())
final_value_expr += string("&(") + note_value_expr + ")";
else if (acct_value_expr.empty() &&
payee_value_expr.empty())
final_value_expr = note_value_expr;
}
DEBUG("report.predicate",
"Regexp predicate expression = " << final_value_expr);
return final_value_expr;
}
template <class Formatter = format_xacts>
class xacts_report
{
@ -65,9 +161,19 @@ namespace ledger {
{
ptr_t<std::ostream> ostream(args, 0);
var_t<string> format(args, format_name);
report_t& report(find_scope<report_t>(args));
find_scope<report_t>(args).xacts_report
(xact_handler_ptr(new Formatter(*ostream, *format)));
if (! report.format_string.empty())
*format = report.format_string;
if (! report.predicate.empty())
report.predicate = string("(") + report.predicate + ")&";
report.predicate +=
args_to_predicate(++args.value().as_sequence().begin(),
args.value().as_sequence().end());
report.xacts_report(xact_handler_ptr(new Formatter(*ostream,
*format)));
return true;
}
};

29
mask.cc
View file

@ -34,7 +34,7 @@
namespace ledger {
mask_t::mask_t(const string& pat) : exclude(false), expr()
mask_t::mask_t(const string& pat) : expr()
{
TRACE_CTOR(mask_t, "const string&");
*this = pat;
@ -42,40 +42,17 @@ mask_t::mask_t(const string& pat) : exclude(false), expr()
mask_t& mask_t::operator=(const 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++;
}
expr.assign(p);
expr.assign(pat.c_str(), regex::perl | regex::icase);
return *this;
}
void mask_t::read(const char *& data)
{
binary::read_number(data, exclude);
string pattern;
binary::read_string(data, pattern);
*this = pattern;
*this = binary::read_string(data);
}
void mask_t::write(std::ostream& out) const
{
binary::write_number(out, exclude);
binary::write_string(out, expr.str());
}

5
mask.h
View file

@ -41,12 +41,11 @@ class mask_t
mask_t();
public:
bool exclude;
boost::regex expr;
explicit mask_t(const string& pattern);
mask_t(const mask_t& m) : exclude(m.exclude), expr(m.expr) {
mask_t(const mask_t& m) : expr(m.expr) {
TRACE_CTOR(mask_t, "copy");
}
~mask_t() throw() {
@ -56,7 +55,7 @@ public:
mask_t& operator=(const string& other);
bool match(const string& str) const {
return boost::regex_match(str, expr) && ! exclude;
return boost::regex_search(str, expr);
}
void read(const char *& data);

42
op.cc
View file

@ -428,48 +428,6 @@ void expr_t::op_t::compute(value_t& result,
break;
}
case F_CODE_MASK:
if (details.entry && details.entry->code)
result = as_mask().match(*details.entry->code);
else
result = false;
break;
case F_PAYEE_MASK:
if (details.entry)
result = as_mask().match(details.entry->payee);
else
result = false;
break;
case F_NOTE_MASK:
if (details.xact && details.xact->note)
result = as_mask().match(*details.xact->note);
else
result = false;
break;
case F_ACCOUNT_MASK:
if (details.account)
result = as_mask().match(details.account->fullname());
else
result = false;
break;
case F_SHORT_ACCOUNT_MASK:
if (details.account)
result = as_mask().match(details.account->name);
else
result = false;
break;
case F_COMMODITY_MASK:
if (details.xact)
result = as_mask().match(details.xact->amount.commodity().base_symbol());
else
result = false;
break;
case O_ARG: {
long arg_index = 0;
assert(left()->kind == INDEX);

123
report.cc
View file

@ -35,96 +35,6 @@
namespace ledger {
#if 0
void
report_t::regexps_to_predicate(const std::string& command,
std::list<std::string>::const_iterator begin,
std::list<std::string>::const_iterator end,
const bool account_regexp,
const bool add_account_short_masks,
const bool logical_and)
{
std::string regexps[2];
assert(begin != end);
// Treat the remaining command-line arguments as regular
// expressions, used for refining report results.
for (std::list<std::string>::const_iterator i = begin;
i != end;
i++)
if ((*i)[0] == '-') {
if (! regexps[1].empty())
regexps[1] += "|";
regexps[1] += (*i).substr(1);
}
else if ((*i)[0] == '+') {
if (! regexps[0].empty())
regexps[0] += "|";
regexps[0] += (*i).substr(1);
}
else {
if (! regexps[0].empty())
regexps[0] += "|";
regexps[0] += *i;
}
for (int i = 0; i < 2; i++) {
if (regexps[i].empty())
continue;
if (! predicate.empty())
predicate += logical_and ? "&" : "|";
int add_predicate = 0; // 1 adds /.../, 2 adds ///.../
if (i == 1) {
predicate += "!";
}
else if (add_account_short_masks) {
if (regexps[i].find(':') != std::string::npos ||
regexps[i].find('.') != std::string::npos ||
regexps[i].find('*') != std::string::npos ||
regexps[i].find('+') != std::string::npos ||
regexps[i].find('[') != std::string::npos ||
regexps[i].find('(') != std::string::npos) {
show_subtotal = true;
add_predicate = 1;
} else {
add_predicate = 2;
}
}
else {
add_predicate = 1;
}
if (i != 1 && command == "b" && account_regexp) {
if (! show_related && ! show_all_related) {
if (! display_predicate.empty())
display_predicate += "&";
if (! show_empty)
display_predicate += "T&";
if (add_predicate == 2)
display_predicate += "//";
display_predicate += "/(?:";
display_predicate += regexps[i];
display_predicate += ")/";
}
else if (! show_empty) {
if (! display_predicate.empty())
display_predicate += "&";
display_predicate += "T";
}
}
if (! account_regexp)
predicate += "/";
predicate += "/(?:";
predicate += regexps[i];
predicate += ")/";
}
}
void report_t::process_options(const std::string& command,
strings_list::iterator arg,
strings_list::iterator args_end)
@ -342,8 +252,11 @@ report_t::chain_xact_handlers(xact_handler_ptr base_handler,
// This filter_xacts will only pass through xacts
// matching the `predicate'.
if (! predicate.empty())
if (! predicate.empty()) {
DEBUG("report.predicate",
"Report predicate expression = " << predicate);
handler.reset(new filter_xacts(handler, predicate));
}
#if 0
// budget_xacts takes a set of xacts from a data
@ -493,31 +406,30 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
if (std::strncmp(p, "opt_", 4) == 0) {
p = p + 4;
switch (*p) {
#if 0
case 'a':
if (std::strcmp(p, "amount") == 0)
return MAKE_FUNCTOR(report_t::option_amount);
break;
#endif
case 'b':
if (std::strcmp(p, "bar") == 0)
return MAKE_FUNCTOR(report_t::option_bar);
if (std::strcmp(p, "bar_") == 0)
return MAKE_FUNCTOR(report_t::option_bar_);
break;
case 'f':
if (std::strcmp(p, "format") == 0)
return MAKE_FUNCTOR(report_t::option_format);
else if (name.find("fmt_") == 0) {
switch (name[4]) {
case 't':
return MAKE_FUNCTOR(report_t::get_amount_expr);
#if 0
case 'T':
return MAKE_FUNCTOR(report_t::get_total_expr);
#endif
}
}
if (std::strcmp(p, "F_") == 0 ||
std::strcmp(p, "format_") == 0)
return MAKE_FUNCTOR(report_t::option_format_);
break;
case 'l':
if (std::strcmp(p, "l_") || std::strcmp(p, "limit_"))
return MAKE_FUNCTOR(report_t::option_limit_);
break;
#if 0
case 't':
if (! *(p + 1))
return MAKE_FUNCTOR(report_t::option_amount);
@ -529,6 +441,7 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
if (! *(p + 1))
return MAKE_FUNCTOR(report_t::option_total);
break;
#endif
}
}
break;

View file

@ -198,6 +198,7 @@ public:
// Config options
//
#if 0
void eval(const string& expr) {
expr_t(expr).calc(*this);
}
@ -206,34 +207,42 @@ public:
return NULL_VALUE;
}
value_t option_amount(call_scope_t& args) {
value_t option_amount_(call_scope_t& args) {
eval(string("t=") + args[0].as_string());
return NULL_VALUE;
}
value_t option_total(call_scope_t& args) {
value_t option_total_(call_scope_t& args) {
eval(string("T()=") + args[0].as_string());
return NULL_VALUE;
}
value_t option_format(call_scope_t& args) {
format_string = args[0].as_string();
return NULL_VALUE;
}
value_t option_raw(call_scope_t&) {
raw_mode = true;
return NULL_VALUE;
}
#endif
value_t option_format_(call_scope_t& args) {
format_string = args[0].as_string();
return NULL_VALUE;
}
value_t option_foo(call_scope_t&) {
std::cout << "This is foo" << std::endl;
return NULL_VALUE;
}
value_t option_bar(call_scope_t& args) {
value_t option_bar_(call_scope_t& args) {
std::cout << "This is bar: " << args[0] << std::endl;
return args[0];
}
value_t option_limit_(call_scope_t& args) {
if (! predicate.empty())
predicate += "&";
predicate += args[0].as_string();
return true;
}
//
// Formatting functions
//

View file

@ -105,12 +105,12 @@ namespace {
{
xact_t& xact(downcast<xact_t>(*scope.parent));
var_t<long> width(scope, 0);
var_t<long> max_width(scope, 0);
string name = xact.reported_account()->fullname();
if (width && *width > 2)
name = format_t::truncate(name, *width - 2, true);
if (max_width && *max_width > 2)
name = format_t::truncate(name, *max_width - 2, true);
if (xact.has_flags(XACT_VIRTUAL)) {
if (xact.must_balance())