Merge pull request #247 from ecraven/master
Adding support for recursive aliases.
This commit is contained in:
commit
eddd03ed09
10 changed files with 132 additions and 20 deletions
|
|
@ -420,6 +420,9 @@ For use only with the
|
||||||
command, it causes Ledger to print out matching entries exactly as they
|
command, it causes Ledger to print out matching entries exactly as they
|
||||||
appeared in the original journal file.
|
appeared in the original journal file.
|
||||||
.It Fl \-real Pq Fl R
|
.It Fl \-real Pq Fl R
|
||||||
|
.It Fl \-recursive-aliases
|
||||||
|
Causes ledger to try to expand aliases recursively, i.e. try to expand
|
||||||
|
the result of an earlier expansion again, until no more expansions apply.
|
||||||
.It Fl \-register-format Ar FMT
|
.It Fl \-register-format Ar FMT
|
||||||
.It Fl \-related Pq Fl r
|
.It Fl \-related Pq Fl r
|
||||||
.It Fl \-related-all
|
.It Fl \-related-all
|
||||||
|
|
|
||||||
|
|
@ -2158,9 +2158,23 @@ alias Checking=Assets:Credit Union:Joint Checking Account
|
||||||
@end smallexample
|
@end smallexample
|
||||||
|
|
||||||
The aliases are only in effect for transactions read in after the alias
|
The aliases are only in effect for transactions read in after the alias
|
||||||
is defined and are effected by @code{account} directives that precede
|
is defined and are affected by @code{account} directives that precede
|
||||||
them.
|
them.
|
||||||
|
|
||||||
|
With the option @option{--recursive-aliases}, aliases can refer to other aliases,
|
||||||
|
the following example produces exactly the same transactions and account names
|
||||||
|
as the preceding one:
|
||||||
|
|
||||||
|
@smallexample
|
||||||
|
alias Entertainment=Expenses:Entertainment
|
||||||
|
alias Dining=Entertainment:Dining
|
||||||
|
alias Checking=Assets:Credit Union:Joint Checking Account
|
||||||
|
|
||||||
|
2011/11/30 ChopChop
|
||||||
|
Dining $10.00
|
||||||
|
Checking
|
||||||
|
@end smallexample
|
||||||
|
|
||||||
@item assert
|
@item assert
|
||||||
@c instance_t::assert_directive
|
@c instance_t::assert_directive
|
||||||
An assertion can throw an error if a condition is not met during
|
An assertion can throw an error if a condition is not met during
|
||||||
|
|
@ -5801,6 +5815,11 @@ correct, and if it finds a new account or commodity (same as
|
||||||
a misspelled commodity or account) it will issue a warning giving you
|
a misspelled commodity or account) it will issue a warning giving you
|
||||||
the file and line number of the problem.
|
the file and line number of the problem.
|
||||||
|
|
||||||
|
@item --recursive-aliases
|
||||||
|
Normally, ledger only expands aliases once. With this option, ledger tries
|
||||||
|
to expand the result of alias expansion recursively, until no more expansions
|
||||||
|
apply.
|
||||||
|
|
||||||
@item --time-colon
|
@item --time-colon
|
||||||
The @option{--time-colon} option will display the value for a seconds
|
The @option{--time-colon} option will display the value for a seconds
|
||||||
based commodity as real hours and minutes.
|
based commodity as real hours and minutes.
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,7 @@ void journal_t::initialize()
|
||||||
check_payees = false;
|
check_payees = false;
|
||||||
day_break = false;
|
day_break = false;
|
||||||
checking_style = CHECK_PERMISSIVE;
|
checking_style = CHECK_PERMISSIVE;
|
||||||
|
recursive_aliases = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void journal_t::add_account(account_t * acct)
|
void journal_t::add_account(account_t * acct)
|
||||||
|
|
@ -121,26 +122,9 @@ account_t * journal_t::find_account_re(const string& regexp)
|
||||||
account_t * journal_t::register_account(const string& name, post_t * post,
|
account_t * journal_t::register_account(const string& name, post_t * post,
|
||||||
account_t * master_account)
|
account_t * master_account)
|
||||||
{
|
{
|
||||||
account_t * result = NULL;
|
// If there are any account aliases, substitute before creating an account
|
||||||
|
|
||||||
// If there any account aliases, substitute before creating an account
|
|
||||||
// object.
|
// object.
|
||||||
if (account_aliases.size() > 0) {
|
account_t * result = expand_aliases(name);
|
||||||
accounts_map::const_iterator i = account_aliases.find(name);
|
|
||||||
if (i != account_aliases.end()) {
|
|
||||||
result = (*i).second;
|
|
||||||
} else {
|
|
||||||
// only check the very first account for alias expansion, in case
|
|
||||||
// that can be expanded successfully
|
|
||||||
size_t colon = name.find(':');
|
|
||||||
if(colon != string::npos) {
|
|
||||||
accounts_map::const_iterator j = account_aliases.find(name.substr(0, colon));
|
|
||||||
if (j != account_aliases.end()) {
|
|
||||||
result = find_account((*j).second->fullname() + name.substr(colon));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the account object and associate it with the journal; this
|
// Create the account object and associate it with the journal; this
|
||||||
// is registering the account.
|
// is registering the account.
|
||||||
|
|
@ -178,7 +162,60 @@ account_t * journal_t::register_account(const string& name, post_t * post,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
account_t * journal_t::expand_aliases(string name) {
|
||||||
|
// Aliases are expanded recursively, so if both alias Foo=Bar:Foo and
|
||||||
|
// alias Bar=Baaz:Bar are in effect, first Foo will be expanded to Bar:Foo,
|
||||||
|
// then Bar:Foo will be expanded to Baaz:Bar:Foo.
|
||||||
|
// The expansion loop keeps a list of already expanded names in order to
|
||||||
|
// prevent infinite excursion. Each alias may only be expanded at most once.
|
||||||
|
account_t * result = NULL;
|
||||||
|
|
||||||
|
bool keep_expanding = true;
|
||||||
|
std::list<string> already_seen;
|
||||||
|
// loop until no expansion can be found
|
||||||
|
do {
|
||||||
|
if (account_aliases.size() > 0) {
|
||||||
|
accounts_map::const_iterator i = account_aliases.find(name);
|
||||||
|
if (i != account_aliases.end()) {
|
||||||
|
if(std::find(already_seen.begin(), already_seen.end(), name) != already_seen.end()) {
|
||||||
|
throw_(std::runtime_error,
|
||||||
|
_f("Infinite recursion on alias expansion for %1%")
|
||||||
|
% name);
|
||||||
|
}
|
||||||
|
// there is an alias for the full account name, including colons
|
||||||
|
already_seen.push_back(name);
|
||||||
|
result = (*i).second;
|
||||||
|
name = result->fullname();
|
||||||
|
} else {
|
||||||
|
// only check the very first account for alias expansion, in case
|
||||||
|
// that can be expanded successfully
|
||||||
|
size_t colon = name.find(':');
|
||||||
|
if(colon != string::npos) {
|
||||||
|
string first_account_name = name.substr(0, colon);
|
||||||
|
accounts_map::const_iterator j = account_aliases.find(first_account_name);
|
||||||
|
if (j != account_aliases.end()) {
|
||||||
|
if(std::find(already_seen.begin(), already_seen.end(), first_account_name) != already_seen.end()) {
|
||||||
|
throw_(std::runtime_error,
|
||||||
|
_f("Infinite recursion on alias expansion for %1%")
|
||||||
|
% first_account_name);
|
||||||
|
}
|
||||||
|
already_seen.push_back(first_account_name);
|
||||||
|
result = find_account((*j).second->fullname() + name.substr(colon));
|
||||||
|
name = result->fullname();
|
||||||
|
} else {
|
||||||
|
keep_expanding = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
keep_expanding = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
keep_expanding = false;
|
||||||
|
}
|
||||||
|
} while(keep_expanding && recursive_aliases);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,7 @@ public:
|
||||||
bool force_checking;
|
bool force_checking;
|
||||||
bool check_payees;
|
bool check_payees;
|
||||||
bool day_break;
|
bool day_break;
|
||||||
|
bool recursive_aliases;
|
||||||
payee_mappings_t payee_mappings;
|
payee_mappings_t payee_mappings;
|
||||||
account_mappings_t account_mappings;
|
account_mappings_t account_mappings;
|
||||||
accounts_map account_aliases;
|
accounts_map account_aliases;
|
||||||
|
|
@ -167,6 +168,8 @@ public:
|
||||||
account_t * find_account(const string& name, bool auto_create = true);
|
account_t * find_account(const string& name, bool auto_create = true);
|
||||||
account_t * find_account_re(const string& regexp);
|
account_t * find_account_re(const string& regexp);
|
||||||
|
|
||||||
|
account_t * expand_aliases(string name);
|
||||||
|
|
||||||
account_t * register_account(const string& name, post_t * post,
|
account_t * register_account(const string& name, post_t * post,
|
||||||
account_t * master = NULL);
|
account_t * master = NULL);
|
||||||
string register_payee(const string& name, xact_t * xact);
|
string register_payee(const string& name, xact_t * xact);
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,9 @@ std::size_t session_t::read_data(const string& master_account)
|
||||||
if (HANDLED(day_break))
|
if (HANDLED(day_break))
|
||||||
journal->day_break = true;
|
journal->day_break = true;
|
||||||
|
|
||||||
|
if (HANDLED(recursive_aliases))
|
||||||
|
journal->recursive_aliases = true;
|
||||||
|
|
||||||
if (HANDLED(permissive))
|
if (HANDLED(permissive))
|
||||||
journal->checking_style = journal_t::CHECK_PERMISSIVE;
|
journal->checking_style = journal_t::CHECK_PERMISSIVE;
|
||||||
else if (HANDLED(pedantic))
|
else if (HANDLED(pedantic))
|
||||||
|
|
@ -350,6 +353,9 @@ option_t<session_t> * session_t::lookup_option(const char * p)
|
||||||
else OPT(pedantic);
|
else OPT(pedantic);
|
||||||
else OPT(permissive);
|
else OPT(permissive);
|
||||||
break;
|
break;
|
||||||
|
case 'r':
|
||||||
|
OPT(recursive_aliases);
|
||||||
|
break;
|
||||||
case 's':
|
case 's':
|
||||||
OPT(strict);
|
OPT(strict);
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,7 @@ public:
|
||||||
HANDLER(permissive).report(out);
|
HANDLER(permissive).report(out);
|
||||||
HANDLER(price_db_).report(out);
|
HANDLER(price_db_).report(out);
|
||||||
HANDLER(price_exp_).report(out);
|
HANDLER(price_exp_).report(out);
|
||||||
|
HANDLER(recursive_aliases).report(out);
|
||||||
HANDLER(strict).report(out);
|
HANDLER(strict).report(out);
|
||||||
HANDLER(value_expr_).report(out);
|
HANDLER(value_expr_).report(out);
|
||||||
}
|
}
|
||||||
|
|
@ -164,6 +165,7 @@ public:
|
||||||
OPTION(session_t, price_db_);
|
OPTION(session_t, price_db_);
|
||||||
OPTION(session_t, strict);
|
OPTION(session_t, strict);
|
||||||
OPTION(session_t, value_expr_);
|
OPTION(session_t, value_expr_);
|
||||||
|
OPTION(session_t, recursive_aliases);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -977,6 +977,11 @@ void instance_t::account_alias_directive(account_t * account, string alias)
|
||||||
// (account), add a reference to the account in the `account_aliases'
|
// (account), add a reference to the account in the `account_aliases'
|
||||||
// map, which is used by the post parser to resolve alias references.
|
// map, which is used by the post parser to resolve alias references.
|
||||||
trim(alias);
|
trim(alias);
|
||||||
|
// Ensure that no alias like "alias Foo=Foo" is registered.
|
||||||
|
if ( alias == account->fullname()) {
|
||||||
|
throw_(parse_error, _f("Illegal alias %1%=%2%")
|
||||||
|
% alias % account->fullname());
|
||||||
|
}
|
||||||
std::pair<accounts_map::iterator, bool> result =
|
std::pair<accounts_map::iterator, bool> result =
|
||||||
context.journal->account_aliases.insert
|
context.journal->account_aliases.insert
|
||||||
(accounts_map::value_type(alias, account));
|
(accounts_map::value_type(alias, account));
|
||||||
|
|
|
||||||
12
test/baseline/dir-alias-fail.test
Normal file
12
test/baseline/dir-alias-fail.test
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
--pedantic
|
||||||
|
--explicit
|
||||||
|
alias Foo=Foo
|
||||||
|
|
||||||
|
2011-01-01 Test
|
||||||
|
Foo 10 EUR
|
||||||
|
Bar
|
||||||
|
test source -> 1
|
||||||
|
__ERROR__
|
||||||
|
While parsing file "$FILE", line 3:
|
||||||
|
Error: Illegal alias Foo=Foo
|
||||||
|
end test
|
||||||
12
test/baseline/dir-alias-recursive.test
Normal file
12
test/baseline/dir-alias-recursive.test
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
alias A=B:A
|
||||||
|
alias B=C:B
|
||||||
|
alias C=D:C
|
||||||
|
|
||||||
|
2001-01-01 Test
|
||||||
|
A 10 EUR
|
||||||
|
Foo
|
||||||
|
|
||||||
|
test reg --recursive-aliases
|
||||||
|
01-Jan-01 Test D:C:B:A 10 EUR 10 EUR
|
||||||
|
Foo -10 EUR 0
|
||||||
|
end test
|
||||||
13
test/baseline/dir-alias.test
Normal file
13
test/baseline/dir-alias.test
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
alias A=B:A
|
||||||
|
alias B=C:B
|
||||||
|
alias C=D:C
|
||||||
|
|
||||||
|
2001-01-01 Test
|
||||||
|
A 10 EUR
|
||||||
|
Foo
|
||||||
|
|
||||||
|
test reg
|
||||||
|
01-Jan-01 Test B:A 10 EUR 10 EUR
|
||||||
|
Foo -10 EUR 0
|
||||||
|
end test
|
||||||
|
|
||||||
Loading…
Add table
Reference in a new issue