Implement the "tag" metadata directive

This commit is contained in:
John Wiegley 2012-02-27 04:59:04 -06:00
parent 0e7b4fb182
commit 2ec35ea6e9
7 changed files with 158 additions and 46 deletions

View file

@ -58,6 +58,15 @@ public:
typedef intrusive_ptr<op_t> ptr_op_t; typedef intrusive_ptr<op_t> ptr_op_t;
typedef intrusive_ptr<const op_t> const_ptr_op_t; typedef intrusive_ptr<const op_t> const_ptr_op_t;
enum check_expr_kind_t {
EXPR_GENERAL,
EXPR_ASSERTION,
EXPR_CHECK
};
typedef std::pair<expr_t, check_expr_kind_t> check_expr_pair;
typedef std::list<check_expr_pair> check_expr_list;
protected: protected:
ptr_op_t ptr; ptr_op_t ptr;

View file

@ -217,8 +217,38 @@ void journal_t::register_commodity(commodity_t& comm,
} }
} }
#if 0 namespace {
void journal_t::register_metadata(const string& key, const string& value, void check_metadata(journal_t& journal,
const string& key, const value_t& value,
variant<int, xact_t *, post_t *> context,
const string& location)
{
std::pair<tag_check_exprs_map::iterator,
tag_check_exprs_map::iterator> range =
journal.tag_check_exprs.equal_range(key);
for (tag_check_exprs_map::iterator i = range.first;
i != range.second;
++i) {
value_scope_t val_scope
(context.which() == 1 ?
static_cast<scope_t&>(*boost::get<xact_t *>(context)) :
static_cast<scope_t&>(*boost::get<post_t *>(context)), value);
if (! (*i).second.first.calc(val_scope).to_boolean()) {
if ((*i).second.second == expr_t::EXPR_ASSERTION)
throw_(parse_error,
_("Metadata assertion failed for (%1: %2): %3")
<< key << value << (*i).second.first);
else
warning_(_("%1Metadata check failed for (%2: %3): %4")
<< location << key << value << (*i).second.first);
}
}
}
}
void journal_t::register_metadata(const string& key, const value_t& value,
variant<int, xact_t *, post_t *> context, variant<int, xact_t *, post_t *> context,
const string& location) const string& location)
{ {
@ -244,8 +274,34 @@ void journal_t::register_metadata(const string& key, const string& value,
throw_(parse_error, _("Unknown metadata tag '%1'") << key); throw_(parse_error, _("Unknown metadata tag '%1'") << key);
} }
} }
if (! value.is_null())
check_metadata(*this, key, value, context, location);
}
namespace {
void check_all_metadata(journal_t& journal,
variant<int, xact_t *, post_t *> context)
{
xact_t * xact = context.which() == 1 ? boost::get<xact_t *>(context) : NULL;
post_t * post = context.which() == 2 ? boost::get<post_t *>(context) : NULL;
if ((xact || post) && xact ? xact->metadata : post->metadata) {
foreach (const item_t::string_map::value_type& pair,
xact ? *xact->metadata : *post->metadata) {
const string& key(pair.first);
// jww (2012-02-27): We really need to know the parsing context,
// both here and for the call to warning_ in
// xact_t::extend_xact.
if (optional<value_t> value = pair.second.first)
journal.register_metadata(key, *value, context, "");
else
journal.register_metadata(key, NULL_VALUE, context, "");
}
}
}
} }
#endif
bool journal_t::add_xact(xact_t * xact) bool journal_t::add_xact(xact_t * xact)
{ {
@ -258,6 +314,10 @@ bool journal_t::add_xact(xact_t * xact)
extend_xact(xact); extend_xact(xact);
check_all_metadata(*this, xact);
foreach (post_t * post, xact->posts)
check_all_metadata(*this, post);
// If a transaction with this UUID has already been seen, simply do // If a transaction with this UUID has already been seen, simply do
// not add this one to the journal. However, all automated checks // not add this one to the journal. However, all automated checks
// will have been performed by extend_xact, so asserts can still be // will have been performed by extend_xact, so asserts can still be

View file

@ -45,10 +45,10 @@
#include "utils.h" #include "utils.h"
#include "times.h" #include "times.h"
#include "mask.h" #include "mask.h"
#include "expr.h"
namespace ledger { namespace ledger {
class commodity_t;
class xact_base_t; class xact_base_t;
class xact_t; class xact_t;
class auto_xact_t; class auto_xact_t;
@ -57,16 +57,17 @@ class post_t;
class account_t; class account_t;
class scope_t; class scope_t;
typedef std::list<xact_t *> xacts_list; typedef std::list<xact_t *> xacts_list;
typedef std::list<auto_xact_t *> auto_xacts_list; typedef std::list<auto_xact_t *> auto_xacts_list;
typedef std::list<period_xact_t *> period_xacts_list; typedef std::list<period_xact_t *> period_xacts_list;
typedef std::pair<mask_t, string> payee_mapping_t;
typedef std::pair<mask_t, string> payee_mapping_t; typedef std::list<payee_mapping_t> payee_mappings_t;
typedef std::list<payee_mapping_t> payee_mappings_t; typedef std::pair<mask_t, account_t *> account_mapping_t;
typedef std::pair<mask_t, account_t *> account_mapping_t; typedef std::list<account_mapping_t> account_mappings_t;
typedef std::list<account_mapping_t> account_mappings_t; typedef std::map<const string, account_t *> accounts_map;
typedef std::map<const string, account_t *> accounts_map; typedef std::map<string, xact_t *> checksum_map_t;
typedef std::map<string, xact_t *> checksum_map_t; typedef std::multimap<string,
expr_t::check_expr_pair> tag_check_exprs_map;
class journal_t : public noncopyable class journal_t : public noncopyable
{ {
@ -130,6 +131,7 @@ public:
accounts_map account_aliases; accounts_map account_aliases;
account_mappings_t payees_for_unknown_accounts; account_mappings_t payees_for_unknown_accounts;
checksum_map_t checksum_map; checksum_map_t checksum_map;
tag_check_exprs_map tag_check_exprs;
bool was_loaded; bool was_loaded;
bool force_checking; bool force_checking;
@ -168,11 +170,9 @@ public:
void register_commodity(commodity_t& comm, void register_commodity(commodity_t& comm,
variant<int, xact_t *, post_t *> context, variant<int, xact_t *, post_t *> context,
const string& location); const string& location);
#if 0 void register_metadata(const string& key, const value_t& value,
void register_metadata(const string& key, const string& value,
variant<int, xact_t *, post_t *> context, variant<int, xact_t *, post_t *> context,
const string& location); const string& location);
#endif
bool add_xact(xact_t * xact); bool add_xact(xact_t * xact);
void extend_xact(xact_base_t * xact); void extend_xact(xact_base_t * xact);

View file

@ -158,6 +158,8 @@ namespace {
void default_commodity_directive(char * line); void default_commodity_directive(char * line);
void tag_directive(char * line);
void apply_directive(char * line); void apply_directive(char * line);
void apply_account_directive(char * line); void apply_account_directive(char * line);
void apply_tag_directive(char * line); void apply_tag_directive(char * line);
@ -605,14 +607,14 @@ void instance_t::automated_xact_directive(char * line)
const char c = *p; const char c = *p;
p = skip_ws(&p[*p == 'a' ? 6 : (*p == 'c' ? 5 : 4)]); p = skip_ws(&p[*p == 'a' ? 6 : (*p == 'c' ? 5 : 4)]);
if (! ae->check_exprs) if (! ae->check_exprs)
ae->check_exprs = auto_xact_t::check_expr_list(); ae->check_exprs = expr_t::check_expr_list();
ae->check_exprs->push_back ae->check_exprs->push_back
(auto_xact_t::check_expr_pair(expr_t(p), (expr_t::check_expr_pair(expr_t(p),
c == 'a' ? c == 'a' ?
auto_xact_t::EXPR_ASSERTION : expr_t::EXPR_ASSERTION :
(c == 'c' ? (c == 'c' ?
auto_xact_t::EXPR_CHECK : expr_t::EXPR_CHECK :
auto_xact_t::EXPR_GENERAL))); expr_t::EXPR_GENERAL)));
} }
else { else {
reveal_context = false; reveal_context = false;
@ -903,14 +905,14 @@ void instance_t::account_directive(char * line)
ae->pos->beg_pos = beg_pos; ae->pos->beg_pos = beg_pos;
ae->pos->beg_line = beg_linenum; ae->pos->beg_line = beg_linenum;
ae->pos->sequence = context.sequence++; ae->pos->sequence = context.sequence++;
ae->check_exprs = auto_xact_t::check_expr_list(); ae->check_exprs = expr_t::check_expr_list();
} }
ae->check_exprs->push_back ae->check_exprs->push_back
(auto_xact_t::check_expr_pair(expr_t(b), (expr_t::check_expr_pair(expr_t(b),
keyword == "assert" ? keyword == "assert" ?
auto_xact_t::EXPR_ASSERTION : expr_t::EXPR_ASSERTION :
auto_xact_t::EXPR_CHECK)); expr_t::EXPR_CHECK));
} }
else if (keyword == "eval" || keyword == "expr") { else if (keyword == "eval" || keyword == "expr") {
bind_scope_t bound_scope(context.scope, *account); bind_scope_t bound_scope(context.scope, *account);
@ -1057,6 +1059,32 @@ void instance_t::commodity_default_directive(commodity_t& comm)
commodity_pool_t::current_pool->default_commodity = &comm; commodity_pool_t::current_pool->default_commodity = &comm;
} }
void instance_t::tag_directive(char * line)
{
char * p = skip_ws(line);
context.journal.register_metadata(p, NULL_VALUE, 0,
file_context(pathname, linenum));
while (peek_whitespace_line()) {
read_line(line);
char * q = skip_ws(line);
if (! *q)
break;
char * b = next_element(q);
string keyword(q);
if (keyword == "assert" || keyword == "check") {
context.journal.tag_check_exprs.insert
(tag_check_exprs_map::value_type
(string(p),
expr_t::check_expr_pair(expr_t(b),
keyword == "assert" ?
expr_t::EXPR_ASSERTION :
expr_t::EXPR_CHECK)));
}
}
}
void instance_t::eval_directive(char * line) void instance_t::eval_directive(char * line)
{ {
expr_t expr(line); expr_t expr(line);
@ -1176,7 +1204,11 @@ bool instance_t::general_directive(char * line)
break; break;
case 't': case 't':
if (std::strcmp(p, "test") == 0) { if (std::strcmp(p, "tag") == 0) {
tag_directive(arg);
return true;
}
else if (std::strcmp(p, "test") == 0) {
comment_directive(arg); comment_directive(arg);
return true; return true;
} }

View file

@ -664,12 +664,12 @@ void auto_xact_t::extend_xact(xact_base_t& xact)
} }
if (check_exprs) { if (check_exprs) {
foreach (check_expr_pair& pair, *check_exprs) { foreach (expr_t::check_expr_pair& pair, *check_exprs) {
if (pair.second == auto_xact_t::EXPR_GENERAL) { if (pair.second == expr_t::EXPR_GENERAL) {
pair.first.calc(bound_scope); pair.first.calc(bound_scope);
} }
else if (! pair.first.calc(bound_scope).to_boolean()) { else if (! pair.first.calc(bound_scope).to_boolean()) {
if (pair.second == auto_xact_t::EXPR_ASSERTION) if (pair.second == expr_t::EXPR_ASSERTION)
throw_(parse_error, throw_(parse_error,
_("Transaction assertion failed: %1") << pair.first); _("Transaction assertion failed: %1") << pair.first);
else else

View file

@ -152,21 +152,11 @@ private:
class auto_xact_t : public xact_base_t class auto_xact_t : public xact_base_t
{ {
public: public:
predicate_t predicate; predicate_t predicate;
bool try_quick_match; bool try_quick_match;
std::map<string, bool> memoized_results; std::map<string, bool> memoized_results;
enum xact_expr_kind_t { optional<expr_t::check_expr_list> check_exprs;
EXPR_GENERAL,
EXPR_ASSERTION,
EXPR_CHECK
};
typedef std::pair<expr_t, xact_expr_kind_t> check_expr_pair;
typedef std::list<check_expr_pair> check_expr_list;
optional<check_expr_list> check_exprs;
struct deferred_tag_data_t { struct deferred_tag_data_t {
string tag_data; string tag_data;

View file

@ -0,0 +1,21 @@
tag Happy
check value == 'Valley'
2012-02-27 * KFC
; Happy: Valley
Expenses:Unknown $20.00
; Happy: Summer
Assets:Cash
2012-02-28 * KFC
food $20.00
Assets:Cash
test reg
12-Feb-27 KFC Expenses:Unknown $20.00 $20.00
Assets:Cash $-20.00 0
12-Feb-28 KFC food $20.00 $20.00
Assets:Cash $-20.00 0
__ERROR__
Warning: Metadata check failed for (Happy: Summer): (value == "Valley")
end test