From c80b4955467a38a2be3aaaa60c9f49b33edef774 Mon Sep 17 00:00:00 2001 From: Peter Feigl Date: Tue, 25 Feb 2014 22:50:20 +0100 Subject: 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. --- src/journal.cc | 72 ++++++++++++++++++++++++++++++++++++++++++---------------- src/journal.h | 2 ++ src/textual.cc | 5 ++++ 3 files changed, 60 insertions(+), 19 deletions(-) (limited to 'src') 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 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 result = context.journal->account_aliases.insert (accounts_map::value_type(alias, account)); -- cgit v1.2.3 From 2dabb914c0f0603c12506e28300ac4add37b43a2 Mon Sep 17 00:00:00 2001 From: Peter Feigl Date: Wed, 26 Feb 2014 01:16:03 +0100 Subject: fixing problem with previous commit if no aliases are registered --- src/journal.cc | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/journal.cc b/src/journal.cc index 552f6ca3..8406da91 100644 --- a/src/journal.cc +++ b/src/journal.cc @@ -211,6 +211,8 @@ account_t * journal_t::expand_aliases(string name) { keep_expanding = false; } } + } else { + keep_expanding = false; } } return result; -- cgit v1.2.3 From 75b0a5d8ff22abeac8c4f502154159f998ffbe99 Mon Sep 17 00:00:00 2001 From: Peter Feigl Date: Wed, 26 Feb 2014 09:29:31 +0100 Subject: Adding option --recursive-aliases, adding documentation to man-page and manual --- doc/ledger.1 | 3 +++ doc/ledger3.texi | 10 ++++++++-- src/journal.cc | 5 +++-- src/journal.h | 1 + src/session.cc | 6 ++++++ src/session.h | 2 ++ test/baseline/dir-alias-recursive.test | 12 ++++++++++++ test/baseline/dir-alias.test | 34 ++++++++-------------------------- 8 files changed, 43 insertions(+), 30 deletions(-) create mode 100644 test/baseline/dir-alias-recursive.test (limited to 'src') diff --git a/doc/ledger.1 b/doc/ledger.1 index 659d3fbb..ecd97ecc 100644 --- a/doc/ledger.1 +++ b/doc/ledger.1 @@ -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 diff --git a/doc/ledger3.texi b/doc/ledger3.texi index 051c7c76..1a8fd9aa 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -2161,8 +2161,9 @@ The aliases are only in effect for transactions read in after the alias 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: +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 @@ -5814,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. diff --git a/src/journal.cc b/src/journal.cc index 8406da91..007acd7b 100644 --- a/src/journal.cc +++ b/src/journal.cc @@ -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) @@ -175,7 +176,7 @@ account_t * journal_t::expand_aliases(string name) { bool keep_expanding = true; std::list already_seen; // loop until no expansion can be found - while(keep_expanding) { + do { if (account_aliases.size() > 0) { accounts_map::const_iterator i = account_aliases.find(name); if (i != account_aliases.end()) { @@ -214,7 +215,7 @@ account_t * journal_t::expand_aliases(string name) { } else { keep_expanding = false; } - } + } while(keep_expanding && recursive_aliases); return result; } diff --git a/src/journal.h b/src/journal.h index 3c363962..270a2912 100644 --- a/src/journal.h +++ b/src/journal.h @@ -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; diff --git a/src/session.cc b/src/session.cc index dbd49a0e..99467a43 100644 --- a/src/session.cc +++ b/src/session.cc @@ -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::lookup_option(const char * p) else OPT(pedantic); else OPT(permissive); break; + case 'r': + OPT(recursive_aliases); + break; case 's': OPT(strict); break; diff --git a/src/session.h b/src/session.h index 3572b991..c2345362 100644 --- a/src/session.h +++ b/src/session.h @@ -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); }; /** diff --git a/test/baseline/dir-alias-recursive.test b/test/baseline/dir-alias-recursive.test new file mode 100644 index 00000000..d9addcd1 --- /dev/null +++ b/test/baseline/dir-alias-recursive.test @@ -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 diff --git a/test/baseline/dir-alias.test b/test/baseline/dir-alias.test index ee363052..6245d944 100644 --- a/test/baseline/dir-alias.test +++ b/test/baseline/dir-alias.test @@ -1,31 +1,13 @@ -alias Cash=Assets:Cash -alias fast-food=food:FastFood -alias food=Expenses:Food +alias A=B:A +alias B=C:B +alias C=D:C -2012-02-27 KFC - fast-food $20.00 - Assets:Cash - -2012-02-28 KFC - fast-food $20.00 - Assets:Cash - -2012-02-29 KFC - fast-food $25.00 - Assets:Cash - -2012-02-29 KFC - fast-food $25.00 - Assets:Cash +2001-01-01 Test + A 10 EUR + Foo test reg -12-Feb-27 KFC Expenses:Food:FastFood $20.00 $20.00 - Assets:Cash $-20.00 0 -12-Feb-28 KFC Expenses:Food:FastFood $20.00 $20.00 - Assets:Cash $-20.00 0 -12-Feb-29 KFC Expenses:Food:FastFood $25.00 $25.00 - Assets:Cash $-25.00 0 -12-Feb-29 KFC Expenses:Food:FastFood $25.00 $25.00 - Assets:Cash $-25.00 0 +01-Jan-01 Test B:A 10 EUR 10 EUR + Foo -10 EUR 0 end test -- cgit v1.2.3 From 7bcc5b7c2cd057c60d4e25314e675ac3f7b3be46 Mon Sep 17 00:00:00 2001 From: Peter Feigl Date: Wed, 26 Feb 2014 12:29:57 +0100 Subject: Fixing two GCC warnings --- src/pstream.h | 5 ++++- src/wcwidth.cc | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/pstream.h b/src/pstream.h index bcbf6755..ed314068 100644 --- a/src/pstream.h +++ b/src/pstream.h @@ -83,7 +83,10 @@ class ptristream : public std::istream virtual pos_type seekoff(off_type off, ios_base::seekdir way, ios_base::openmode) { - switch (way) { + // cast to avoid gcc '-Wswitch' warning + // as ios_base::beg/cur/end are not necesssarily values of 'way' enum type ios_base::seekdir + // based on https://svn.boost.org/trac/boost/ticket/7644 + switch (static_cast(way)) { case std::ios::cur: setg(ptr, gptr()+off, ptr+len); break; diff --git a/src/wcwidth.cc b/src/wcwidth.cc index 71eaf6d6..c23f83d7 100644 --- a/src/wcwidth.cc +++ b/src/wcwidth.cc @@ -69,8 +69,8 @@ namespace ledger { namespace { struct interval { - int first; - int last; + boost::uint32_t first; + boost::uint32_t last; }; } -- cgit v1.2.3 From ecd5097d515f53703eb5dc6096da80182c452ad9 Mon Sep 17 00:00:00 2001 From: Peter Feigl Date: Wed, 26 Feb 2014 23:50:50 +0100 Subject: Adding option --no-aliases to completely disable alias expansion --- doc/ledger.1 | 2 ++ doc/ledger3.texi | 5 +++++ src/journal.cc | 3 +++ src/journal.h | 1 + src/session.cc | 5 +++++ src/session.h | 2 ++ 6 files changed, 18 insertions(+) (limited to 'src') diff --git a/doc/ledger.1 b/doc/ledger.1 index ecd97ecc..9544a071 100644 --- a/doc/ledger.1 +++ b/doc/ledger.1 @@ -381,6 +381,8 @@ See .It Fl \-meta Ar EXPR .It Fl \-meta-width Ar INT .It Fl \-monthly Pq Fl M +.It Fl \-no-aliases +Aliases are completely ignored. .It Fl \-no-color .It Fl \-no-pager .It Fl \-no-rounding diff --git a/doc/ledger3.texi b/doc/ledger3.texi index 1a8fd9aa..48297889 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -2175,6 +2175,8 @@ alias Checking=Assets:Credit Union:Joint Checking Account Checking @end smallexample +The option @option{--no-aliases} completely disables alias expansion. + @item assert @c instance_t::assert_directive An assertion can throw an error if a condition is not met during @@ -5788,6 +5790,9 @@ $ ledger -f drewr3.dat bal --no-total --master-account HUMBUG $ 200.00 Mortgage:Principal @end smallexample +@item --no-aliases +Ledger does not expand any aliases if this option is specified. + @item --pedantic FIX THIS ENTRY @c FIXME thdox diff --git a/src/journal.cc b/src/journal.cc index 007acd7b..160abe06 100644 --- a/src/journal.cc +++ b/src/journal.cc @@ -173,6 +173,9 @@ account_t * journal_t::expand_aliases(string name) { // prevent infinite excursion. Each alias may only be expanded at most once. account_t * result = NULL; + if(no_aliases) + return result; + bool keep_expanding = true; std::list already_seen; // loop until no expansion can be found diff --git a/src/journal.h b/src/journal.h index 270a2912..e4763482 100644 --- a/src/journal.h +++ b/src/journal.h @@ -132,6 +132,7 @@ public: bool check_payees; bool day_break; bool recursive_aliases; + bool no_aliases; payee_mappings_t payee_mappings; account_mappings_t account_mappings; accounts_map account_aliases; diff --git a/src/session.cc b/src/session.cc index 99467a43..b386607a 100644 --- a/src/session.cc +++ b/src/session.cc @@ -115,6 +115,8 @@ std::size_t session_t::read_data(const string& master_account) if (HANDLED(recursive_aliases)) journal->recursive_aliases = true; + if (HANDLED(no_aliases)) + journal->no_aliases = true; if (HANDLED(permissive)) journal->checking_style = journal_t::CHECK_PERMISSIVE; @@ -347,6 +349,9 @@ option_t * session_t::lookup_option(const char * p) case 'm': OPT(master_account_); break; + case 'n': + OPT(no_aliases); + break; case 'p': OPT(price_db_); else OPT(price_exp_); diff --git a/src/session.h b/src/session.h index c2345362..d20ba74a 100644 --- a/src/session.h +++ b/src/session.h @@ -110,6 +110,7 @@ public: HANDLER(price_db_).report(out); HANDLER(price_exp_).report(out); HANDLER(recursive_aliases).report(out); + HANDLER(no_aliases).report(out); HANDLER(strict).report(out); HANDLER(value_expr_).report(out); } @@ -166,6 +167,7 @@ public: OPTION(session_t, strict); OPTION(session_t, value_expr_); OPTION(session_t, recursive_aliases); + OPTION(session_t, no_aliases); }; /** -- cgit v1.2.3