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
|
||||
appeared in the original journal file.
|
||||
.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 \-related Pq Fl r
|
||||
.It Fl \-related-all
|
||||
|
|
|
|||
|
|
@ -2158,9 +2158,23 @@ alias Checking=Assets:Credit Union:Joint Checking Account
|
|||
@end smallexample
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
@c instance_t::assert_directive
|
||||
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
|
||||
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
|
||||
The @option{--time-colon} option will display the value for a seconds
|
||||
based commodity as real hours and minutes.
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ void journal_t::initialize()
|
|||
check_payees = false;
|
||||
day_break = false;
|
||||
checking_style = CHECK_PERMISSIVE;
|
||||
recursive_aliases = false;
|
||||
}
|
||||
|
||||
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 * master_account)
|
||||
{
|
||||
account_t * result = NULL;
|
||||
|
||||
// If there any account aliases, substitute before creating an account
|
||||
// If there are any account aliases, substitute before creating an account
|
||||
// object.
|
||||
if (account_aliases.size() > 0) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
account_t * result = expand_aliases(name);
|
||||
|
||||
// Create the account object and associate it with the journal; this
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@ public:
|
|||
bool force_checking;
|
||||
bool check_payees;
|
||||
bool day_break;
|
||||
bool recursive_aliases;
|
||||
payee_mappings_t payee_mappings;
|
||||
account_mappings_t account_mappings;
|
||||
accounts_map account_aliases;
|
||||
|
|
@ -167,6 +168,8 @@ public:
|
|||
account_t * find_account(const string& name, bool auto_create = true);
|
||||
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 * master = NULL);
|
||||
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))
|
||||
journal->day_break = true;
|
||||
|
||||
if (HANDLED(recursive_aliases))
|
||||
journal->recursive_aliases = true;
|
||||
|
||||
if (HANDLED(permissive))
|
||||
journal->checking_style = journal_t::CHECK_PERMISSIVE;
|
||||
else if (HANDLED(pedantic))
|
||||
|
|
@ -350,6 +353,9 @@ option_t<session_t> * session_t::lookup_option(const char * p)
|
|||
else OPT(pedantic);
|
||||
else OPT(permissive);
|
||||
break;
|
||||
case 'r':
|
||||
OPT(recursive_aliases);
|
||||
break;
|
||||
case 's':
|
||||
OPT(strict);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ public:
|
|||
HANDLER(permissive).report(out);
|
||||
HANDLER(price_db_).report(out);
|
||||
HANDLER(price_exp_).report(out);
|
||||
HANDLER(recursive_aliases).report(out);
|
||||
HANDLER(strict).report(out);
|
||||
HANDLER(value_expr_).report(out);
|
||||
}
|
||||
|
|
@ -164,6 +165,7 @@ public:
|
|||
OPTION(session_t, price_db_);
|
||||
OPTION(session_t, strict);
|
||||
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'
|
||||
// map, which is used by the post parser to resolve alias references.
|
||||
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 =
|
||||
context.journal->account_aliases.insert
|
||||
(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