Added any() and all() value expression macros
any() matches an expression against every post in a transaction or account, and returns true if any of them are true. all() tests if all are true. For example: ledger -l 'account =~ /Expense/ & any(account =~ /MasterCard/)' reg This reports every posting affecting an Expense account (regex match), but only if some other posting in the same transaction affects the MasterCard account. Both functions also take a second boolean argument. If it is false, the "source" posting is not considered. For example: ledger -l 'any(/x/, false)' This matches any posting where a *different* posting in the same transaction contains the letter 'x'.
This commit is contained in:
parent
d5f8c3bc57
commit
4ec54b86f8
6 changed files with 131 additions and 6 deletions
|
|
@ -239,6 +239,36 @@ namespace {
|
|||
value_t get_parent(account_t& account) {
|
||||
return value_t(static_cast<scope_t *>(account.parent));
|
||||
}
|
||||
|
||||
value_t fn_any(call_scope_t& scope)
|
||||
{
|
||||
interactive_t args(scope, "X&X");
|
||||
|
||||
account_t& account(find_scope<account_t>(scope));
|
||||
expr_t& expr(args.get<expr_t&>(0));
|
||||
|
||||
foreach (post_t * p, account.posts) {
|
||||
bind_scope_t bound_scope(scope, *p);
|
||||
if (expr.calc(bound_scope).to_boolean())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
value_t fn_all(call_scope_t& scope)
|
||||
{
|
||||
interactive_t args(scope, "X&X");
|
||||
|
||||
account_t& account(find_scope<account_t>(scope));
|
||||
expr_t& expr(args.get<expr_t&>(0));
|
||||
|
||||
foreach (post_t * p, account.posts) {
|
||||
bind_scope_t bound_scope(scope, *p);
|
||||
if (! expr.calc(bound_scope).to_boolean())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
expr_t::ptr_op_t account_t::lookup(const symbol_t::kind_t kind,
|
||||
|
|
@ -255,6 +285,10 @@ expr_t::ptr_op_t account_t::lookup(const symbol_t::kind_t kind,
|
|||
return WRAP_FUNCTOR(get_wrapper<&get_account>);
|
||||
else if (name == "account_base")
|
||||
return WRAP_FUNCTOR(get_wrapper<&get_account_base>);
|
||||
else if (name == "any")
|
||||
return WRAP_FUNCTOR(&fn_any);
|
||||
else if (name == "all")
|
||||
return WRAP_FUNCTOR(&fn_all);
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
|
|
|
|||
|
|
@ -428,7 +428,9 @@ namespace {
|
|||
{
|
||||
switch (buf[0]) {
|
||||
case 'a':
|
||||
return std::strcmp(buf, "and") == 0;
|
||||
return (std::strcmp(buf, "and") == 0 ||
|
||||
std::strcmp(buf, "any") == 0 ||
|
||||
std::strcmp(buf, "all") == 0);
|
||||
case 'd':
|
||||
return std::strcmp(buf, "div") == 0;
|
||||
case 'e':
|
||||
|
|
|
|||
|
|
@ -171,10 +171,10 @@ void generate_posts_iterator::generate_commodity(std::ostream& out)
|
|||
generate_string(buf, six_gen(), true);
|
||||
comm = buf.str();
|
||||
}
|
||||
while (comm == "h" || comm == "m" || comm == "s" ||
|
||||
comm == "and" || comm == "div" || comm == "false" ||
|
||||
comm == "or" || comm == "not" || comm == "true" ||
|
||||
comm == "if" || comm == "else");
|
||||
while (comm == "h" || comm == "m" || comm == "s" || comm == "and" ||
|
||||
comm == "any" || comm == "all" || comm == "div" ||
|
||||
comm == "false" || comm == "or" || comm == "not" ||
|
||||
comm == "true" || comm == "if" || comm == "else");
|
||||
|
||||
out << comm;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,10 @@ expr_t::parser_t::parse_value_term(std::istream& in,
|
|||
// An identifier followed by ( represents a function call
|
||||
tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT));
|
||||
if (tok.kind == token_t::LPAREN) {
|
||||
ptr_op_t call_node(new op_t(op_t::O_CALL));
|
||||
op_t::kind_t kind = op_t::O_CALL;
|
||||
if (ident == "any" || ident == "all")
|
||||
kind = op_t::O_EXPAND;
|
||||
ptr_op_t call_node(new op_t(kind));
|
||||
call_node->set_left(node);
|
||||
node = call_node;
|
||||
|
||||
|
|
|
|||
48
src/post.cc
48
src/post.cc
|
|
@ -291,6 +291,50 @@ namespace {
|
|||
value_t get_wrapper(call_scope_t& scope) {
|
||||
return (*Func)(find_scope<post_t>(scope));
|
||||
}
|
||||
|
||||
value_t fn_any(call_scope_t& scope)
|
||||
{
|
||||
interactive_t args(scope, "X&X");
|
||||
|
||||
post_t& post(find_scope<post_t>(scope));
|
||||
expr_t& expr(args.get<expr_t&>(0));
|
||||
|
||||
foreach (post_t * p, post.xact->posts) {
|
||||
bind_scope_t bound_scope(scope, *p);
|
||||
if (p == &post && args.has(1) &&
|
||||
! args.get<expr_t&>(1).calc(bound_scope).to_boolean()) {
|
||||
// If the user specifies any(EXPR, false), and the context is a
|
||||
// posting, then that posting isn't considered by the test.
|
||||
; // skip it
|
||||
}
|
||||
else if (expr.calc(bound_scope).to_boolean()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
value_t fn_all(call_scope_t& scope)
|
||||
{
|
||||
interactive_t args(scope, "X&X");
|
||||
|
||||
post_t& post(find_scope<post_t>(scope));
|
||||
expr_t& expr(args.get<expr_t&>(0));
|
||||
|
||||
foreach (post_t * p, post.xact->posts) {
|
||||
bind_scope_t bound_scope(scope, *p);
|
||||
if (p == &post && args.has(1) &&
|
||||
! args.get<expr_t&>(1).calc(bound_scope).to_boolean()) {
|
||||
// If the user specifies any(EXPR, false), and the context is a
|
||||
// posting, then that posting isn't considered by the test.
|
||||
; // skip it
|
||||
}
|
||||
else if (! expr.calc(bound_scope).to_boolean()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
expr_t::ptr_op_t post_t::lookup(const symbol_t::kind_t kind,
|
||||
|
|
@ -307,6 +351,10 @@ expr_t::ptr_op_t post_t::lookup(const symbol_t::kind_t kind,
|
|||
return WRAP_FUNCTOR(get_account);
|
||||
else if (name == "account_base")
|
||||
return WRAP_FUNCTOR(get_wrapper<&get_account_base>);
|
||||
else if (name == "any")
|
||||
return WRAP_FUNCTOR(&fn_any);
|
||||
else if (name == "all")
|
||||
return WRAP_FUNCTOR(&fn_all);
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
|
|
|
|||
38
src/xact.cc
38
src/xact.cc
|
|
@ -36,6 +36,7 @@
|
|||
#include "account.h"
|
||||
#include "journal.h"
|
||||
#include "pool.h"
|
||||
#include "interactive.h"
|
||||
|
||||
namespace ledger {
|
||||
|
||||
|
|
@ -483,6 +484,36 @@ namespace {
|
|||
value_t get_wrapper(call_scope_t& scope) {
|
||||
return (*Func)(find_scope<xact_t>(scope));
|
||||
}
|
||||
|
||||
value_t fn_any(call_scope_t& scope)
|
||||
{
|
||||
interactive_t args(scope, "X&X");
|
||||
|
||||
post_t& post(find_scope<post_t>(scope));
|
||||
expr_t& expr(args.get<expr_t&>(0));
|
||||
|
||||
foreach (post_t * p, post.xact->posts) {
|
||||
bind_scope_t bound_scope(scope, *p);
|
||||
if (expr.calc(bound_scope).to_boolean())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
value_t fn_all(call_scope_t& scope)
|
||||
{
|
||||
interactive_t args(scope, "X&X");
|
||||
|
||||
post_t& post(find_scope<post_t>(scope));
|
||||
expr_t& expr(args.get<expr_t&>(0));
|
||||
|
||||
foreach (post_t * p, post.xact->posts) {
|
||||
bind_scope_t bound_scope(scope, *p);
|
||||
if (! expr.calc(bound_scope).to_boolean())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
expr_t::ptr_op_t xact_t::lookup(const symbol_t::kind_t kind,
|
||||
|
|
@ -492,6 +523,13 @@ expr_t::ptr_op_t xact_t::lookup(const symbol_t::kind_t kind,
|
|||
return item_t::lookup(kind, name);
|
||||
|
||||
switch (name[0]) {
|
||||
case 'a':
|
||||
if (name == "any")
|
||||
return WRAP_FUNCTOR(&fn_any);
|
||||
else if (name == "all")
|
||||
return WRAP_FUNCTOR(&fn_all);
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
if (name == "code")
|
||||
return WRAP_FUNCTOR(get_wrapper<&get_code>);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue