diff options
author | Peter Feigl <craven@gmx.net> | 2014-02-25 22:50:20 +0100 |
---|---|---|
committer | Peter Feigl <craven@gmx.net> | 2014-02-25 22:50:20 +0100 |
commit | c80b4955467a38a2be3aaaa60c9f49b33edef774 (patch) | |
tree | f83e871677be813384f68563d4da8bf83134a1a5 | |
parent | bc08eed3cb1bdb4f67d1b77273f3254d0cf13be2 (diff) | |
download | fork-ledger-c80b4955467a38a2be3aaaa60c9f49b33edef774.tar.gz fork-ledger-c80b4955467a38a2be3aaaa60c9f49b33edef774.tar.bz2 fork-ledger-c80b4955467a38a2be3aaaa60c9f49b33edef774.zip |
Adding support for recursive aliases.
Alias expansion is now a loop. If you define
alias A=B:A
alias B=C:B
then A will expand to C:B:A.
Also added a short section to the manual about this.
-rw-r--r-- | doc/ledger3.texi | 15 | ||||
-rw-r--r-- | src/journal.cc | 72 | ||||
-rw-r--r-- | src/journal.h | 2 | ||||
-rw-r--r-- | src/textual.cc | 5 |
4 files changed, 74 insertions, 20 deletions
diff --git a/doc/ledger3.texi b/doc/ledger3.texi index 4ab8f4fd..051c7c76 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -2158,9 +2158,22 @@ 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. +Aliases can refer to other aliases, the following example produces exactly +the same accounts 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 diff --git a/src/journal.cc b/src/journal.cc index a014a964..552f6ca3 100644 --- a/src/journal.cc +++ b/src/journal.cc @@ -121,26 +121,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 +161,58 @@ 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 + while(keep_expanding) { + 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; + } + } + } + } return result; } diff --git a/src/journal.h b/src/journal.h index 0d06e9f0..3c363962 100644 --- a/src/journal.h +++ b/src/journal.h @@ -167,6 +167,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); diff --git a/src/textual.cc b/src/textual.cc index d8648c93..627a1835 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -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)); |