Implement the "tag" metadata directive
This commit is contained in:
parent
0e7b4fb182
commit
2ec35ea6e9
7 changed files with 158 additions and 46 deletions
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
16
src/xact.h
16
src/xact.h
|
|
@ -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;
|
||||||
|
|
|
||||||
21
test/baseline/dir-tag.test
Normal file
21
test/baseline/dir-tag.test
Normal 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
|
||||||
Loading…
Add table
Reference in a new issue