diff options
author | John Wiegley <johnw@newartisans.com> | 2010-06-02 01:55:55 -0400 |
---|---|---|
committer | John Wiegley <johnw@newartisans.com> | 2010-06-02 01:55:55 -0400 |
commit | cda19829bd1100d6563b48ddb121f2afc1c55d95 (patch) | |
tree | 5b6ab0d1eb7ec1b0b3eccf817678d0aa9a3feb47 | |
parent | 017492ef5e80003073c5d053252d4a68a44260ae (diff) | |
parent | fb7cafa8965c89bbd66b09f827bd5989a87c983b (diff) | |
download | fork-ledger-cda19829bd1100d6563b48ddb121f2afc1c55d95.tar.gz fork-ledger-cda19829bd1100d6563b48ddb121f2afc1c55d95.tar.bz2 fork-ledger-cda19829bd1100d6563b48ddb121f2afc1c55d95.zip |
Merge branch 'next'
57 files changed, 1745 insertions, 689 deletions
@@ -451,6 +451,9 @@ class PrepareBuild(CommandLineApp): op.add_option('', '--no-git', action='store_true', dest='no_git', default=False, help='Do not call out to Git; useful for offline builds') + op.add_option('', '--no-python', action='store_true', dest='no_python', + default=False, + help='Do not enable Python support by default') op.add_option('', '--output', metavar='DIR', action="callback", callback=self.option_output, help='Build in the specified directory') @@ -474,6 +477,9 @@ class PrepareBuild(CommandLineApp): help='Enable full warning flags') def main(self, *args): + if self.options.no_python: + self.configure_args.remove('--enable-python') + if args and args[0] in ['default', 'debug', 'opt', 'gcov', 'gprof']: self.current_flavor = args[0] args = args[1:] @@ -740,7 +746,7 @@ class PrepareBuild(CommandLineApp): if system == 'Darwin': if exists('/opt/local/bin/port'): self.log.info('Looks like you are using MacPorts on OS X') - packages = self.boost_info.dependencies('darwin') + [ + packages = [ 'sudo', 'port', 'install', '-f', 'automake', 'autoconf', @@ -761,7 +767,7 @@ class PrepareBuild(CommandLineApp): 'texinfo', 'lcov', 'sloccount' - ] + ] + self.boost_info.dependencies('darwin') self.log.info('Executing: ' + string.join(packages, ' ')) self.execute(*packages) elif exists('/sw/bin/fink'): @@ -778,7 +784,7 @@ class PrepareBuild(CommandLineApp): release.close() if re.search('karmic', info): self.log.info('Looks like you are using APT on Ubuntu Karmic') - packages = self.boost_info.dependencies('ubuntu-karmic') + [ + packages = [ 'sudo', 'apt-get', 'install', 'build-essential', 'libtool', @@ -799,10 +805,10 @@ class PrepareBuild(CommandLineApp): 'texinfo', 'lcov', 'sloccount' - ] + ] + self.boost_info.dependencies('ubuntu-karmic') else: self.log.info('Looks like you are using APT on Ubuntu Hardy') - packages = self.boost_info.dependencies('ubuntu-hardy') + [ + packages = [ 'sudo', 'apt-get', 'install', 'build-essential', 'libtool', @@ -823,7 +829,7 @@ class PrepareBuild(CommandLineApp): 'texinfo', 'lcov', 'sloccount' - ] + ] + self.boost_info.dependencies('ubuntu-hardy') self.log.info('Executing: ' + string.join(packages, ' ')) self.execute(*packages) @@ -908,9 +914,6 @@ class PrepareBuild(CommandLineApp): self.CXXFLAGS.append('-march=nocona') self.CXXFLAGS.append('-msse3') - self.configure_args.append('--enable-doxygen') - self.configure_args.append('--enable-python') - self.locate_darwin_libraries() def setup_for_system(self): @@ -1119,13 +1122,13 @@ class PrepareBuild(CommandLineApp): self.log.debug('We are using GLIBCXX_DEBUG, so setting up flags') self.CPPFLAGS.append('-D_GLIBCXX_DEBUG=1') - if self.boost_info.configure(home_path = '/usr/local/stow/boost_1_42_0', - suffix = '-xgcc44-sd-1_42', - include_path = 'include/boost-1_42'): + if self.boost_info.configure(home_path = '/usr/local/stow/boost_1_43_0', + suffix = '-xgcc44-sd-1_43', + include_path = 'include/boost-1_43'): pass - elif self.boost_info.configure(home_path = '/usr/local/stow/boost_1_42_0', - suffix = '-xgcc44-d-1_42', - include_path = 'include/boost-1_42'): + elif self.boost_info.configure(home_path = '/usr/local/stow/boost_1_43_0', + suffix = '-xgcc44-d-1_43', + include_path = 'include/boost-1_43'): pass elif self.boost_info.configure(suffix = '-d'): pass @@ -1133,17 +1136,18 @@ class PrepareBuild(CommandLineApp): else: if self.boost_info.configure(): pass - elif self.boost_info.configure(home_path = '/usr/local/stow/boost_1_42_0', - suffix = '-xgcc44-s-1_42', - include_path = 'include/boost-1_42'): + elif self.boost_info.configure(home_path = '/usr/local/stow/boost_1_43_0', + suffix = '-xgcc44-s-1_43', + include_path = 'include/boost-1_43'): pass - elif self.boost_info.configure(home_path = '/usr/local/stow/boost_1_42_0', - suffix = '-xgcc44-1_42', - include_path = 'include/boost-1_42'): + elif self.boost_info.configure(home_path = '/usr/local/stow/boost_1_43_0', + suffix = '-xgcc44-1_43', + include_path = 'include/boost-1_43'): pass def setup_flavor_default(self): - pass + if self.darwin_gcc: + self.option_no_pch() def setup_flavor_debug(self): self.configure_args.append('--enable-debug') @@ -1184,38 +1188,6 @@ class PrepareBuild(CommandLineApp): self.LDFLAGS.append('-pg') ######################################################################### - # Prettify the output from automake, by rewriting the Makefile # - ######################################################################### - - def phase_patch(self, *args): - """Alter the Makefile so that it's not nearly so verbose. - - This makes errors and warnings much easier to spot.""" - self.log.info('Executing phase: patch') - - if exists('Makefile'): - self.log.debug('Patching generated Makefile') - Makefile = open('Makefile') - Makefile_new = open('Makefile.new', 'w') - for line in Makefile.readlines(): - line = re.sub('^\t(\$\((LIBTOOL|CXX)\).*?\.(cc|cpp))$', - '\t@echo " " CXX \$@;\\1 > /dev/null', line) - line = re.sub('^\tmv -f', '\t@mv -f', line) - line = re.sub('^\t\$\(am__mv\)', '\t@$(am__mv)', line) - line = re.sub('^\t(\$\((.*?)LINK\).*)', - '\t@echo " LD " \$@;\\1 > /dev/null', line) - Makefile_new.write(line) - Makefile_new.close() - Makefile.close() - - os.remove('Makefile') - os.rename('Makefile.new', 'Makefile') - - stamp = open('.timestamp', 'w') - stamp.write('timestamp') - stamp.close() - - ######################################################################### # Configure build tree using autoconf # ######################################################################### @@ -1290,18 +1262,11 @@ class PrepareBuild(CommandLineApp): self.log.error("Execution failed: " + string.join(conf_args, ' ')) sys.exit(1) - if not self.options.no_patch: - self.phase_patch() - # Wipe the pre-compiled header, if there is one pch = join(self.build_directory(), 'system.hh.gch') if exists(pch): os.remove(pch) else: - if not self.options.no_patch and \ - self.isnewer('Makefile', '.timestamp'): - self.phase_patch() - self.log.debug('configure does not need to be run') finally: @@ -2,37 +2,16 @@ * 3.0 -** INCOMPATIBLE CHANGES +Due to the magnitude of changes in 3.0, only changes that affect compatibility +with 2.x files and usage is mentioned here. For a description of new +features, please see the manual. -*** Commands +- The option -g (--performance) was removed. -**** Removed 'output' -**** Removed 'xml' - -*** Options - -**** --descend -**** --descend_if EXPR -**** --reconcile -**** --reconcile-date DATE - -**** -s, -n - -The balance report now defaults to showing all relevant accounts. This is the -opposite of how it worked before. That is, "bal" in 3.0 does what "-s bal" -did in 2.x. To get the 2.6 behavior, use "bal -n" in 3.0. The -s option no -longer has any effect on balance reports. - -** NEW FEATURES - -*** Commands - -*** Options - -**** --anon - -For anonymizing the private details of reports. This is almost solely for the -purpose of easing the creation of accurate bug reports. +- The balance report now defaults to showing all relevant accounts. This is + the opposite of 2.x. That is, "bal" in 3.0 does what "-s bal" did in 2.x. + To see 2.6 behavior, use "bal -n" in 3.0. The -s option no longer has any + effect on balance reports. * 2.6.1 diff --git a/doc/ledger.texi b/doc/ledger.texi index 10e80a60..0a0224dc 100644 --- a/doc/ledger.texi +++ b/doc/ledger.texi @@ -228,7 +228,7 @@ Here is the Pacific Bell example from above, given as a Ledger posting: @smallexample -9/29 (100) Pacific Bell +9/29 Pacific Bell Expenses:Utilities:Phone $23.00 Assets:Checking $-23.00 @end smallexample @@ -240,7 +240,7 @@ smart about many things, you don't need to specify the balanced amount, if it is the same as the first line: @smallexample -9/29 (100) Pacific Bell +9/29 Pacific Bell Expenses:Utilities:Phone $23.00 Assets:Checking @end smallexample @@ -367,7 +367,7 @@ file whose formatting has gotten out of hand. The @command{output} command is very similar to the @command{print} command, except that it attempts to replicate the specified ledger -file epostly. The format of the command is: +file exactly. The format of the command is: @example ledger -f FILENAME output FILENAME @@ -430,7 +430,7 @@ Say you currently have this posting in your ledger file: @end smallexample Now it's @samp{2004/4/9}, and you've just eating at @samp{Viva -Italiano} again. The epost amounts are different, but the overall +Italiano} again. The exact amounts are different, but the overall form is the same. With the @command{xact} command you can type: @example @@ -709,13 +709,13 @@ deviation from the average. It is only meaningful in the @command{xml} report. @option{--amount-data} (@option{-j}) changes the @command{register} -report so that it output nothing but the date and the value column, +report so that it outputs nothing but the date and the value column, and the latter without commodities. This is only meaningful if the report uses a single commodity. This data can then be fed to other programs, which could plot the date, analyze it, etc. @option{--total-data} (@option{-J}) changes the @command{register} -report so that it output nothing but the date and totals column, +report so that it outputs nothing but the date and totals column, without commodities. @option{--display EXPR} (@option{-d EXPR}) limits which postings @@ -986,7 +986,7 @@ stripped from the total. @item [DATEFMT] Inserts the result of formatting a posting's date with a date -format string, epostly like those supported by @code{strftime}. For +format string, exactly like those supported by @code{strftime}. For example: @samp{%[%Y/%m/%d %H:%M:%S]}. @item S @@ -1013,7 +1013,7 @@ output. @item d This is the same as the @samp{%D} option, unless the transaction has an effective date, in which case it prints -@samp{[ACTUAL_DATE=EFFECtIVE_DATE]}. +@samp{[ACTUAL_DATE=EFFECTIVE_DATE]}. @item X If a posting has been cleared, this inserts @samp{*} followed by a @@ -1311,7 +1311,7 @@ DATE[=EDATE] [*|!] [(CODE)] DESC If @samp{*} appears after the date (with optional effective date), it indicates the transaction is ``cleared'', which can mean whatever the user -wants it t omean. If @samp{!} appears after the date, it indicates d +wants it to mean. If @samp{!} appears after the date, it indicates d the transaction is ``pending''; i.e., tentatively cleared from the user's point of view, but not yet actually cleared. If a @samp{CODE} appears in parentheses, it may be used to indicate a check number, or the type @@ -1521,7 +1521,7 @@ Now the report is: @end smallexample Since the liability was a virtual posting, it has dropped from the -report and we see that final total is balanced. +report and we see that the final total is balanced. But we only know that it balances because @file{sample.dat} is quite simple, and we happen to know that the 50 shares of Apple stock cost @@ -1571,7 +1571,7 @@ This reports: @end smallexample This shows that the @samp{Assets} total is made up from two child -account, but that the total for each of the other accounts comes from +accounts, but that the total for each of the other accounts comes from one child account. Sometimes you may have a lot of children, nested very deeply, but only @@ -1762,7 +1762,7 @@ Although the easiest way to use the register is to report all the postings affecting a set of accounts, it can often result in more information than you want. To cope with an ever-growing amount of data, there are several options which can help you pinpoint your -report to epostly the postings that interest you most. This is +report to include just the postings that interest you most. This is called the ``calculation'' phase of Ledger. All of its related options are documented under @option{--help-calc}. @@ -1969,7 +1969,7 @@ Reports: 2004/05/14 Pay day Income:Salary $-500.00 0 @end smallexample -The final total is zero, indicating that the budget matched epostly +The final total is zero, indicating that the budget matched exactly for the reported period. Budgeting is most often helpful with period reporting; for example, to show monthly budget results use @option{--budget -p monthly}. @@ -2010,7 +2010,7 @@ Reports: @end smallexample The date this report was made was November 5, 2004; the reason the -first forecast transaction is in december is that forecast transactions are only +first forecast transaction is in December is that forecast transactions are only added for the future, and they only stop after the value expression has matched at least once, which is why the January transaction appears. A forecast report can be very useful for determining when money will run @@ -2206,10 +2206,11 @@ Show yearly sub-totals. Same as @samp{-p yearly}. @c --dow show a days-of-the-week report -There is one kind of period report cannot be done with @option{-p}. -This is the @option{--dow}, or ``days of the week'' report, which -shows summarized totals for each day of the week. The following -examples shows a ``day of the week'' report of income and expenses: +There is one kind of period report that cannot be done with +@option{-p}. This is the @option{--dow}, or ``days of the week'' +report, which shows summarized totals for each day of the week. The +following examples shows a ``day of the week'' report of income and +expenses: @example ledger --dow reg ^inc ^exp @@ -2581,7 +2582,7 @@ Based on that explanation, here's another way to look at your balance report: every negative figure means that that account or person or place has less money now than when you started your ledger; and every positive figure means that that account or person or place has more -money now that when you started your ledger. Make sense? +money now than when you started your ledger. Make sense? @node Assets and Liabilities, Typical queries, Stating where money goes, Ledger in Practice @section Assets and Liabilities @@ -3507,7 +3508,7 @@ To view balances without any virtual balances factored in, using the As a Bahá'í, I need to compute Huqúqu'lláh whenever I acquire assets. It is similar to tithing for Jews and Christians, or to Zakát for -Muslims. The epost details of computing Huqúqu'lláh are somewhat +Muslims. The exact details of computing Huqúqu'lláh are somewhat complex, but if you have further interest, please consult the Web. Ledger makes this otherwise difficult law very easy. Just set up an @@ -3762,7 +3763,7 @@ And now the time spent has been turned into hard cash in the checking account. The advantage to using timeclock and invoicing to bill time is that -you will always know, by looking at the balance report, epostly how +you will always know, by looking at the balance report, exactly how much unbilled and unpaid time you've spent working for any particular client. @@ -3963,13 +3964,13 @@ price-setting directive): P 2009/01/15 00:00:00 S 2 P 2009/02/01 Sample 2a - Assets:Brokerage:Stocks 100 S @ 1 P + Assets:Brokerage:Stocks 100 S @@ 1 P Assets:Brokerage:Cash P 2009/02/01 00:00:00 S 4 P 2009/03/01 Sample 3a - Assets:Brokerage:Stocks 100 S @@ 100 P + Assets:Brokerage:Stocks 100 S @@@@ 100 P Assets:Brokerage:Cash P 2009/03/01 00:00:00 S 8 P diff --git a/lib/Makefile b/lib/Makefile index f0f2b3d1..14605176 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -12,7 +12,7 @@ CPPFLAGS = -D_GLIBCXX_DEBUG=1 -D_GLIBCXX_FULLY_DYNAMIC_STRING=1 CFLAGS = $(CPPFLAGS) -g LDFLAGS = -g -BOOST_VERSION = 1_42_0 +BOOST_VERSION = 1_43_0 BOOST_SOURCE = boost_$(BOOST_VERSION) BOOST_TOOLSET = darwin BOOST_DEFINES = define=_GLIBCXX_DEBUG=1 define=_GLIBCXX_FULLY_DYNAMIC_STRING=1 diff --git a/src/account.cc b/src/account.cc index e02d21d7..8d4341e7 100644 --- a/src/account.cc +++ b/src/account.cc @@ -42,9 +42,12 @@ account_t::~account_t() { TRACE_DTOR(account_t); - foreach (accounts_map::value_type& pair, accounts) - if (! pair.second->has_flags(ACCOUNT_TEMP)) + foreach (accounts_map::value_type& pair, accounts) { + if (! pair.second->has_flags(ACCOUNT_TEMP) || + has_flags(ACCOUNT_TEMP)) { checked_delete(pair.second); + } + } } account_t * account_t::find_account(const string& name, @@ -79,6 +82,14 @@ account_t * account_t::find_account(const string& name, return NULL; account = new account_t(this, first); + + // An account created within a temporary or generated account is itself + // temporary or generated, so that the whole tree has the same status. + if (has_flags(ACCOUNT_TEMP)) + account->add_flags(ACCOUNT_TEMP); + if (has_flags(ACCOUNT_GENERATED)) + account->add_flags(ACCOUNT_GENERATED); + std::pair<accounts_map::iterator, bool> result = accounts.insert(accounts_map::value_type(first, account)); assert(result.second); diff --git a/src/amount.cc b/src/amount.cc index 3a64577f..13f30755 100644 --- a/src/amount.cc +++ b/src/amount.cc @@ -165,8 +165,8 @@ namespace { for (const char * p = buf; *p; p++) { if (*p == '.') { - if (commodity_t::european_by_default || - (comm && comm->has_flags(COMMODITY_STYLE_EUROPEAN))) + if (commodity_t::decimal_comma_by_default || + (comm && comm->has_flags(COMMODITY_STYLE_DECIMAL_COMMA))) out << ','; else out << *p; @@ -179,8 +179,8 @@ namespace { out << *p; if (integer_digits > 3 && --integer_digits % 3 == 0) { - if (commodity_t::european_by_default || - (comm && comm->has_flags(COMMODITY_STYLE_EUROPEAN))) + if (commodity_t::decimal_comma_by_default || + (comm && comm->has_flags(COMMODITY_STYLE_DECIMAL_COMMA))) out << '.'; else out << ','; @@ -594,6 +594,44 @@ void amount_t::in_place_round() set_keep_precision(false); } +void amount_t::in_place_truncate() +{ +#if 1 + if (! quantity) + throw_(amount_error, _("Cannot truncate an uninitialized amount")); + + _dup(); + + DEBUG("amount.truncate", + "Truncating " << *this << " to precision " << display_precision()); + + std::ostringstream out; + stream_out_mpq(out, MP(quantity), display_precision()); + + scoped_array<char> buf(new char [out.str().length() + 1]); + std::strcpy(buf.get(), out.str().c_str()); + + char * q = buf.get(); + for (char * p = q; *p != '\0'; p++, q++) { + if (*p == '.') p++; + if (p != q) *q = *p; + } + *q = '\0'; + + mpq_set_str(MP(quantity), buf.get(), 10); + + mpz_ui_pow_ui(temp, 10, display_precision()); + mpq_set_z(tempq, temp); + mpq_div(MP(quantity), MP(quantity), tempq); + + DEBUG("amount.truncate", "Truncated = " << *this); +#else + // This naive implementation is straightforward, but extremely inefficient + // as it requires parsing the commodity too, which might be fully annotated. + *this = amount_t(to_string()); +#endif +} + void amount_t::in_place_floor() { if (! quantity) @@ -993,8 +1031,9 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags) bool no_more_commas = false; bool no_more_periods = false; - bool european_style = (commodity_t::european_by_default || - commodity().has_flags(COMMODITY_STYLE_EUROPEAN)); + bool decimal_comma_style + = (commodity_t::decimal_comma_by_default || + commodity().has_flags(COMMODITY_STYLE_DECIMAL_COMMA)); new_quantity->prec = 0; @@ -1005,16 +1044,16 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags) if (no_more_periods) throw_(amount_error, _("Too many periods in amount")); - if (european_style) { + if (decimal_comma_style) { if (decimal_offset % 3 != 0) - throw_(amount_error, _("Incorrect use of european-style period")); + throw_(amount_error, _("Incorrect use of thousand-mark period")); comm_flags |= COMMODITY_STYLE_THOUSANDS; no_more_commas = true; } else { if (last_comma != string::npos) { - european_style = true; + decimal_comma_style = true; if (decimal_offset % 3 != 0) - throw_(amount_error, _("Incorrect use of european-style period")); + throw_(amount_error, _("Incorrect use of thousand-mark period")); } else { no_more_periods = true; new_quantity->prec = decimal_offset; @@ -1029,9 +1068,9 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags) if (no_more_commas) throw_(amount_error, _("Too many commas in amount")); - if (european_style) { + if (decimal_comma_style) { if (last_period != string::npos) { - throw_(amount_error, _("Incorrect use of european-style comma")); + throw_(amount_error, _("Incorrect use of decimal comma")); } else { no_more_commas = true; new_quantity->prec = decimal_offset; @@ -1041,12 +1080,12 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags) if (decimal_offset % 3 != 0) { if (last_comma != string::npos || last_period != string::npos) { - throw_(amount_error, _("Incorrect use of American-style comma")); + throw_(amount_error, _("Incorrect use of thousand-mark comma")); } else { - european_style = true; - no_more_commas = true; - new_quantity->prec = decimal_offset; - decimal_offset = 0; + decimal_comma_style = true; + no_more_commas = true; + new_quantity->prec = decimal_offset; + decimal_offset = 0; } } else { comm_flags |= COMMODITY_STYLE_THOUSANDS; @@ -1062,8 +1101,8 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags) } } - if (european_style) - comm_flags |= COMMODITY_STYLE_EUROPEAN; + if (decimal_comma_style) + comm_flags |= COMMODITY_STYLE_DECIMAL_COMMA; if (flags.has_flags(PARSE_NO_MIGRATE)) { // Can't call set_keep_precision here, because it assumes that `quantity' diff --git a/src/amount.h b/src/amount.h index 5c1bca46..ae0e5a69 100644 --- a/src/amount.h +++ b/src/amount.h @@ -346,9 +346,7 @@ public: temp.in_place_truncate(); return temp; } - void in_place_truncate() { - *this = amount_t(to_string()); - } + void in_place_truncate(); /** Yields an amount which has lost all of its extra precision, beyond what the display precision of the commodity would have printed. */ diff --git a/src/balance.h b/src/balance.h index f8455d49..5c00c55a 100644 --- a/src/balance.h +++ b/src/balance.h @@ -321,10 +321,8 @@ public: return temp; } void in_place_round() { - balance_t temp; - foreach (const amounts_map::value_type& pair, amounts) - temp += pair.second.rounded(); - *this = temp; + foreach (amounts_map::value_type& pair, amounts) + pair.second.in_place_round(); } balance_t truncated() const { @@ -333,10 +331,8 @@ public: return temp; } void in_place_truncate() { - balance_t temp; - foreach (const amounts_map::value_type& pair, amounts) - temp += pair.second.truncated(); - *this = temp; + foreach (amounts_map::value_type& pair, amounts) + pair.second.in_place_truncate(); } balance_t floored() const { @@ -345,10 +341,8 @@ public: return temp; } void in_place_floor() { - balance_t temp; - foreach (const amounts_map::value_type& pair, amounts) - temp += pair.second.floored(); - *this = temp; + foreach (amounts_map::value_type& pair, amounts) + pair.second.in_place_floor(); } balance_t unrounded() const { @@ -357,10 +351,8 @@ public: return temp; } void in_place_unround() { - balance_t temp; - foreach (const amounts_map::value_type& pair, amounts) - temp += pair.second.unrounded(); - *this = temp; + foreach (amounts_map::value_type& pair, amounts) + pair.second.in_place_unround(); } balance_t reduced() const { diff --git a/src/chain.cc b/src/chain.cc index 86f639ad..b8c2eb0a 100644 --- a/src/chain.cc +++ b/src/chain.cc @@ -39,6 +39,73 @@ namespace ledger { +post_handler_ptr chain_pre_post_handlers(report_t& report, + post_handler_ptr base_handler) +{ + post_handler_ptr handler(base_handler); + + // anonymize_posts removes all meaningful information from xact payee's and + // account names, for the sake of creating useful bug reports. + if (report.HANDLED(anon)) + handler.reset(new anonymize_posts(handler)); + + // This filter_posts will only pass through posts matching the `predicate'. + if (report.HANDLED(limit_)) { + DEBUG("report.predicate", + "Report predicate expression = " << report.HANDLER(limit_).str()); + handler.reset(new filter_posts + (handler, predicate_t(report.HANDLER(limit_).str(), + report.what_to_keep()), + report)); + } + + // budget_posts takes a set of posts from a data file and uses them to + // generate "budget posts" which balance against the reported posts. + // + // forecast_posts is a lot like budget_posts, except that it adds xacts + // only for the future, and does not balance them against anything but the + // future balance. + + if (report.budget_flags != BUDGET_NO_BUDGET) { + budget_posts * budget_handler = new budget_posts(handler, + report.budget_flags); + budget_handler->add_period_xacts(report.session.journal->period_xacts); + handler.reset(budget_handler); + + // Apply this before the budget handler, so that only matching posts are + // calculated toward the budget. The use of filter_posts above will + // further clean the results so that no automated posts that don't match + // the filter get reported. + if (report.HANDLED(limit_)) + handler.reset(new filter_posts + (handler, predicate_t(report.HANDLER(limit_).str(), + report.what_to_keep()), + report)); + } + else if (report.HANDLED(forecast_while_)) { + forecast_posts * forecast_handler + = new forecast_posts(handler, + predicate_t(report.HANDLER(forecast_while_).str(), + report.what_to_keep()), + report, + report.HANDLED(forecast_years_) ? + static_cast<std::size_t> + (report.HANDLER(forecast_years_).value.to_long()) : + 5UL); + forecast_handler->add_period_xacts(report.session.journal->period_xacts); + handler.reset(forecast_handler); + + // See above, under budget_posts. + if (report.HANDLED(limit_)) + handler.reset(new filter_posts + (handler, predicate_t(report.HANDLER(limit_).str(), + report.what_to_keep()), + report)); + } + + return handler; +} + post_handler_ptr chain_post_handlers(report_t& report, post_handler_ptr base_handler, bool for_accounts_report) @@ -86,7 +153,8 @@ post_handler_ptr chain_post_handlers(report_t& report, report.HANDLED(unrealized))) handler.reset(new changed_value_posts(handler, report, for_accounts_report, - report.HANDLED(unrealized))); + report.HANDLED(unrealized), + ! report.HANDLED(no_rounding))); // calc_posts computes the running total. When this appears will determine, // for example, whether filtered posts are included or excluded from the @@ -188,65 +256,6 @@ post_handler_ptr chain_post_handlers(report_t& report, if (report.HANDLED(related)) handler.reset(new related_posts(handler, report.HANDLED(related_all))); - // anonymize_posts removes all meaningful information from xact payee's and - // account names, for the sake of creating useful bug reports. - if (report.HANDLED(anon)) - handler.reset(new anonymize_posts(handler)); - - // This filter_posts will only pass through posts matching the `predicate'. - if (report.HANDLED(limit_)) { - DEBUG("report.predicate", - "Report predicate expression = " << report.HANDLER(limit_).str()); - handler.reset(new filter_posts - (handler, predicate_t(report.HANDLER(limit_).str(), - report.what_to_keep()), - report)); - } - - // budget_posts takes a set of posts from a data file and uses them to - // generate "budget posts" which balance against the reported posts. - // - // forecast_posts is a lot like budget_posts, except that it adds xacts - // only for the future, and does not balance them against anything but the - // future balance. - - if (report.budget_flags != BUDGET_NO_BUDGET) { - budget_posts * budget_handler = new budget_posts(handler, - report.budget_flags); - budget_handler->add_period_xacts(report.session.journal->period_xacts); - handler.reset(budget_handler); - - // Apply this before the budget handler, so that only matching posts are - // calculated toward the budget. The use of filter_posts above will - // further clean the results so that no automated posts that don't match - // the filter get reported. - if (report.HANDLED(limit_)) - handler.reset(new filter_posts - (handler, predicate_t(report.HANDLER(limit_).str(), - report.what_to_keep()), - report)); - } - else if (report.HANDLED(forecast_while_)) { - forecast_posts * forecast_handler - = new forecast_posts(handler, - predicate_t(report.HANDLER(forecast_while_).str(), - report.what_to_keep()), - report, - report.HANDLED(forecast_years_) ? - static_cast<std::size_t> - (report.HANDLER(forecast_years_).value.to_long()) : - 5UL); - forecast_handler->add_period_xacts(report.session.journal->period_xacts); - handler.reset(forecast_handler); - - // See above, under budget_posts. - if (report.HANDLED(limit_)) - handler.reset(new filter_posts - (handler, predicate_t(report.HANDLER(limit_).str(), - report.what_to_keep()), - report)); - } - return handler; } diff --git a/src/chain.h b/src/chain.h index 94d54317..59b04eb8 100644 --- a/src/chain.h +++ b/src/chain.h @@ -65,27 +65,51 @@ public: TRACE_DTOR(item_handler); } + virtual void title(const string& str) { + if (handler) + handler->title(str); + } + virtual void flush() { - if (handler.get()) + if (handler) handler->flush(); } virtual void operator()(T& item) { - if (handler.get()) { + if (handler) { check_for_signal(); - (*handler.get())(item); + (*handler)(item); } } + + virtual void clear() { + if (handler) + handler->clear(); + } }; typedef shared_ptr<item_handler<post_t> > post_handler_ptr; typedef shared_ptr<item_handler<account_t> > acct_handler_ptr; class report_t; + +post_handler_ptr +chain_pre_post_handlers(report_t& report, + post_handler_ptr base_handler); + post_handler_ptr chain_post_handlers(report_t& report, post_handler_ptr base_handler, bool for_accounts_report = false); +inline post_handler_ptr +chain_handlers(report_t& report, + post_handler_ptr handler, + bool for_accounts_report = false) { + handler = chain_post_handlers(report, handler, for_accounts_report); + handler = chain_pre_post_handlers(report, handler); + return handler; +} + } // namespace ledger #endif // _CHAIN_H diff --git a/src/commodity.cc b/src/commodity.cc index 836a4269..1b85910f 100644 --- a/src/commodity.cc +++ b/src/commodity.cc @@ -38,7 +38,7 @@ namespace ledger { -bool commodity_t::european_by_default = false; +bool commodity_t::decimal_comma_by_default = false; void commodity_t::history_t::add_price(commodity_t& source, const datetime_t& date, @@ -454,7 +454,7 @@ void commodity_t::parse_symbol(std::istream& in, string& symbol) { // Invalid commodity characters: // SPACE, TAB, NEWLINE, RETURN - // 0-9 . , ; - + * / ^ ? : & | ! = + // 0-9 . , ; : ? ! - + * / ^ & | = // < > { } [ ] ( ) @ static int invalid_chars[256] = { @@ -663,10 +663,10 @@ void to_xml(std::ostream& out, const commodity_t& comm, push_xml x(out, "commodity", true); out << " flags=\""; - if (! (comm.has_flags(COMMODITY_STYLE_SUFFIXED))) out << 'P'; - if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) out << 'S'; - if (comm.has_flags(COMMODITY_STYLE_THOUSANDS)) out << 'T'; - if (comm.has_flags(COMMODITY_STYLE_EUROPEAN)) out << 'E'; + if (! (comm.has_flags(COMMODITY_STYLE_SUFFIXED))) out << 'P'; + if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) out << 'S'; + if (comm.has_flags(COMMODITY_STYLE_THOUSANDS)) out << 'T'; + if (comm.has_flags(COMMODITY_STYLE_DECIMAL_COMMA)) out << 'D'; out << '"'; x.close_attrs(); diff --git a/src/commodity.h b/src/commodity.h index 10f209fa..53e3033f 100644 --- a/src/commodity.h +++ b/src/commodity.h @@ -155,16 +155,16 @@ protected: class base_t : public noncopyable, public supports_flags<uint_least16_t> { public: -#define COMMODITY_STYLE_DEFAULTS 0x000 -#define COMMODITY_STYLE_SUFFIXED 0x001 -#define COMMODITY_STYLE_SEPARATED 0x002 -#define COMMODITY_STYLE_EUROPEAN 0x004 -#define COMMODITY_STYLE_THOUSANDS 0x008 -#define COMMODITY_NOMARKET 0x010 -#define COMMODITY_BUILTIN 0x020 -#define COMMODITY_WALKED 0x040 -#define COMMODITY_KNOWN 0x080 -#define COMMODITY_PRIMARY 0x100 +#define COMMODITY_STYLE_DEFAULTS 0x000 +#define COMMODITY_STYLE_SUFFIXED 0x001 +#define COMMODITY_STYLE_SEPARATED 0x002 +#define COMMODITY_STYLE_DECIMAL_COMMA 0x004 +#define COMMODITY_STYLE_THOUSANDS 0x008 +#define COMMODITY_NOMARKET 0x010 +#define COMMODITY_BUILTIN 0x020 +#define COMMODITY_WALKED 0x040 +#define COMMODITY_KNOWN 0x080 +#define COMMODITY_PRIMARY 0x100 string symbol; amount_t::precision_t precision; @@ -179,8 +179,8 @@ protected: public: explicit base_t(const string& _symbol) : supports_flags<uint_least16_t> - (commodity_t::european_by_default ? - static_cast<uint_least16_t>(COMMODITY_STYLE_EUROPEAN) : + (commodity_t::decimal_comma_by_default ? + static_cast<uint_least16_t>(COMMODITY_STYLE_DECIMAL_COMMA) : static_cast<uint_least16_t>(COMMODITY_STYLE_DEFAULTS)), symbol(_symbol), precision(0), searched(false) { TRACE_CTOR(base_t, "const string&"); @@ -228,7 +228,7 @@ protected: } public: - static bool european_by_default; + static bool decimal_comma_by_default; virtual ~commodity_t() { TRACE_DTOR(commodity_t); diff --git a/src/convert.cc b/src/convert.cc index 2e6da2f6..aa9bbb6f 100644 --- a/src/convert.cc +++ b/src/convert.cc @@ -117,7 +117,7 @@ value_t convert_command(call_scope_t& scope) if (matched) { DEBUG("convert.csv", "Ignored xact with code: " << *xact->code); - delete xact; // ignore it + checked_delete(xact); // ignore it } else { if (xact->posts.front()->account == NULL) { @@ -135,7 +135,7 @@ value_t convert_command(call_scope_t& scope) } if (! journal.add_xact(xact)) { - delete xact; + checked_delete(xact); throw_(std::runtime_error, _("Failed to finalize derived transaction (check commodities)")); } diff --git a/src/draft.cc b/src/draft.cc index 18075731..69dc7025 100644 --- a/src/draft.cc +++ b/src/draft.cc @@ -240,6 +240,9 @@ void draft_t::parse_args(const value_t& args) xact_t * draft_t::insert(journal_t& journal) { + if (! tmpl) + return NULL; + if (tmpl->payee_mask.empty()) throw std::runtime_error(_("'xact' command requires at least a payee")); @@ -528,7 +531,8 @@ value_t xact_command(call_scope_t& args) // Only consider actual postings for the "xact" command report.HANDLER(limit_).on(string("#xact"), "actual"); - report.xact_report(post_handler_ptr(new print_xacts(report)), *new_xact); + if (new_xact) + report.xact_report(post_handler_ptr(new print_xacts(report)), *new_xact); return true; } diff --git a/src/emacs.cc b/src/emacs.cc index d47f04ad..3c8bb256 100644 --- a/src/emacs.cc +++ b/src/emacs.cc @@ -40,18 +40,21 @@ namespace ledger { void format_emacs_posts::write_xact(xact_t& xact) { - out << "\"" << xact.pos->pathname << "\" " - << xact.pos->beg_line << " "; + if (xact.pos) + out << "\"" << xact.pos->pathname << "\" " + << xact.pos->beg_line << " "; + else + out << "\"\" " << -1 << " "; tm when = gregorian::to_tm(xact.date()); std::time_t date = std::mktime(&when); out << "(" << (date / 65536) << " " << (date % 65536) << " 0) "; - if (! xact.code) - out << "nil "; - else + if (xact.code) out << "\"" << *xact.code << "\" "; + else + out << "nil "; if (xact.payee.empty()) out << "nil"; @@ -77,7 +80,11 @@ void format_emacs_posts::operator()(post_t& post) out << "\n"; } - out << " (" << post.pos->beg_line << " "; + if (post.pos) + out << " (" << post.pos->beg_line << " "; + else + out << " (" << -1 << " "; + out << "\"" << post.reported_account()->fullname() << "\" \"" << post.amount << "\""; diff --git a/src/filters.cc b/src/filters.cc index 0c45d356..ad4b88a0 100644 --- a/src/filters.cc +++ b/src/filters.cc @@ -39,8 +39,51 @@ namespace ledger { +void post_splitter::print_title(const value_t& val) +{ + if (! report.HANDLED(no_titles)) { + std::ostringstream buf; + val.print(buf); + post_chain->title(buf.str()); + } +} + +void post_splitter::flush() +{ + foreach (value_to_posts_map::value_type pair, posts_map) { + preflush_func(pair.first); + + foreach (post_t * post, pair.second) + (*post_chain)(*post); + + post_chain->flush(); + post_chain->clear(); + + if (postflush_func) + (*postflush_func)(pair.first); + } +} + +void post_splitter::operator()(post_t& post) +{ + bind_scope_t bound_scope(report, post); + value_t result(group_by_expr.calc(bound_scope)); + + if (! result.is_null()) { + value_to_posts_map::iterator i = posts_map.find(result); + if (i != posts_map.end()) { + (*i).second.push_back(&post); + } else { + std::pair<value_to_posts_map::iterator, bool> inserted + = posts_map.insert(value_to_posts_map::value_type(result, posts_list())); + assert(inserted.second); + (*inserted.first).second.push_back(&post); + } + } +} + pass_down_posts::pass_down_posts(post_handler_ptr handler, - posts_iterator& iter) + posts_iterator& iter) : item_handler<post_t>(handler) { TRACE_CTOR(pass_down_posts, "post_handler_ptr, posts_iterator"); @@ -312,7 +355,7 @@ namespace { if (functor) (*functor)(post); - DEBUG("filter.changed_value.rounding", "post.amount = " << post.amount); + DEBUG("filters.changed_value.rounding", "post.amount = " << post.amount); (*handler)(post); @@ -355,7 +398,7 @@ void collapse_posts::report_subtotal() xact.payee = last_xact->payee; xact._date = (is_valid(earliest_date) ? earliest_date : last_xact->_date); - DEBUG("filter.collapse", "Pseudo-xact date = " << *xact._date); + DEBUG("filters.collapse", "Pseudo-xact date = " << *xact._date); handle_value(subtotal, &totals_account, &xact, temps, handler); } @@ -420,10 +463,12 @@ void related_posts::flush() changed_value_posts::changed_value_posts(post_handler_ptr handler, report_t& _report, bool _for_accounts_report, - bool _show_unrealized) + bool _show_unrealized, + bool _show_rounding) : item_handler<post_t>(handler), report(_report), for_accounts_report(_for_accounts_report), - show_unrealized(_show_unrealized), last_post(NULL), + show_unrealized(_show_unrealized), + show_rounding(_show_rounding), last_post(NULL), revalued_account(temps.create_account(_("<Revalued>"))), rounding_account(temps.create_account(_("<Rounding>"))) { @@ -458,6 +503,8 @@ changed_value_posts::changed_value_posts(post_handler_ptr handler, void changed_value_posts::flush() { if (last_post && last_post->date() <= report.terminus.date()) { + if (! for_accounts_report) + output_intermediate_prices(*last_post, report.terminus.date()); output_revaluation(*last_post, report.terminus.date()); last_post = NULL; } @@ -469,7 +516,6 @@ void changed_value_posts::output_revaluation(post_t& post, const date_t& date) if (is_valid(date)) post.xdata().date = date; - value_t repriced_total; try { bind_scope_t bound_scope(report, post); repriced_total = total_expr.calc(bound_scope); @@ -480,14 +526,14 @@ void changed_value_posts::output_revaluation(post_t& post, const date_t& date) } post.xdata().date = date_t(); - DEBUG("filter.changed_value", - "output_revaluation(last_balance) = " << last_total); - DEBUG("filter.changed_value", + DEBUG("filters.changed_value", + "output_revaluation(last_total) = " << last_total); + DEBUG("filters.changed_value", "output_revaluation(repriced_total) = " << repriced_total); if (! last_total.is_null()) { if (value_t diff = repriced_total - last_total) { - DEBUG("filter.changed_value", "output_revaluation(strip(diff)) = " + DEBUG("filters.changed_value", "output_revaluation(strip(diff)) = " << diff.strip_annotations(report.what_to_keep())); xact_t& xact = temps.create_xact(); @@ -505,9 +551,10 @@ void changed_value_posts::output_revaluation(post_t& post, const date_t& date) /* total= */ repriced_total, /* direct_amount= */ false, /* mark_visited= */ false, - /* functor= */ (optional<post_functor_t> + /* functor= */ (show_rounding ? + optional<post_functor_t> (bind(&changed_value_posts::output_rounding, - this, _1)))); + this, _1)) : none)); } else if (show_unrealized) { handle_value @@ -527,29 +574,146 @@ void changed_value_posts::output_revaluation(post_t& post, const date_t& date) } } +void changed_value_posts::output_intermediate_prices(post_t& post, + const date_t& current) +{ + // To fix BZ#199, examine the balance of last_post and determine whether the + // price of that amount changed after its date and before the new post's + // date. If so, generate an output_revaluation for that price change. + // Mostly this is only going to occur if the user has a series of pricing + // entries, since a posting-based revaluation would be seen here as a post. + + value_t display_total(last_total); + + if (display_total.type() == value_t::SEQUENCE) { + xact_t& xact(temps.create_xact()); + + xact.payee = _("Commodities revalued"); + xact._date = is_valid(current) ? current : post.date(); + + post_t& temp(temps.copy_post(post, xact)); + temp.add_flags(ITEM_GENERATED); + + post_t::xdata_t& xdata(temp.xdata()); + if (is_valid(current)) + xdata.date = current; + + DEBUG("filters.revalued", "intermediate last_total = " << last_total); + + switch (last_total.type()) { + case value_t::BOOLEAN: + case value_t::INTEGER: + last_total.in_place_cast(value_t::AMOUNT); + // fall through... + + case value_t::AMOUNT: + temp.amount = last_total.as_amount(); + break; + + case value_t::BALANCE: + case value_t::SEQUENCE: + xdata.compound_value = last_total; + xdata.add_flags(POST_EXT_COMPOUND); + break; + + case value_t::DATETIME: + case value_t::DATE: + default: + assert(false); + break; + } + + bind_scope_t inner_scope(report, temp); + display_total = display_total_expr.calc(inner_scope); + + DEBUG("filters.revalued", "intermediate display_total = " << display_total); + } + + switch (display_total.type()) { + case value_t::VOID: + case value_t::INTEGER: + case value_t::SEQUENCE: + break; + + case value_t::AMOUNT: + display_total.in_place_cast(value_t::BALANCE); + // fall through... + + case value_t::BALANCE: { + commodity_t::history_map all_prices; + + foreach (const balance_t::amounts_map::value_type& amt_comm, + display_total.as_balance().amounts) { + if (optional<commodity_t::varied_history_t&> hist = + amt_comm.first->varied_history()) { + foreach + (const commodity_t::history_by_commodity_map::value_type& comm_hist, + hist->histories) { + foreach (const commodity_t::history_map::value_type& price, + comm_hist.second.prices) { + if (price.first.date() > post.date() && + price.first.date() < current) { + DEBUG("filters.revalued", post.date() << " < " + << price.first.date() << " < " << current); + DEBUG("filters.revalued", "inserting " + << price.second << " at " << price.first.date()); + all_prices.insert(price); + } + } + } + } + } + + // Choose the last price from each day as the price to use + typedef std::map<const date_t, bool> date_map; + date_map pricing_dates; + + BOOST_REVERSE_FOREACH + (const commodity_t::history_map::value_type& price, all_prices) { + // This insert will fail if a later price has already been inserted + // for that date. + DEBUG("filters.revalued", + "re-inserting " << price.second << " at " << price.first.date()); + pricing_dates.insert(date_map::value_type(price.first.date(), true)); + } + + // Go through the time-sorted prices list, outputting a revaluation for + // each price difference. + foreach (const date_map::value_type& price, pricing_dates) { + output_revaluation(post, price.first); + last_total = repriced_total; + } + break; + } + default: + assert(false); + break; + } +} + void changed_value_posts::output_rounding(post_t& post) { bind_scope_t bound_scope(report, post); value_t new_display_total(display_total_expr.calc(bound_scope)); - DEBUG("filter.changed_value.rounding", + DEBUG("filters.changed_value.rounding", "rounding.new_display_total = " << new_display_total); if (! last_display_total.is_null()) { if (value_t repriced_amount = display_amount_expr.calc(bound_scope)) { - DEBUG("filter.changed_value.rounding", + DEBUG("filters.changed_value.rounding", "rounding.repriced_amount = " << repriced_amount); value_t precise_display_total(new_display_total.truncated() - repriced_amount.truncated()); - DEBUG("filter.changed_value.rounding", + DEBUG("filters.changed_value.rounding", "rounding.precise_display_total = " << precise_display_total); - DEBUG("filter.changed_value.rounding", + DEBUG("filters.changed_value.rounding", "rounding.last_display_total = " << last_display_total); if (value_t diff = precise_display_total - last_display_total) { - DEBUG("filter.changed_value.rounding", + DEBUG("filters.changed_value.rounding", "rounding.diff = " << diff); xact_t& xact = temps.create_xact(); @@ -566,13 +730,16 @@ void changed_value_posts::output_rounding(post_t& post) void changed_value_posts::operator()(post_t& post) { - if (last_post) + if (last_post) { + if (! for_accounts_report) + output_intermediate_prices(*last_post, post.date()); output_revaluation(*last_post, post.date()); + } if (changed_values_only) post.xdata().add_flags(POST_EXT_DISPLAYED); - if (! for_accounts_report) + if (! for_accounts_report && show_rounding) output_rounding(post); item_handler<post_t>::operator()(post); @@ -793,41 +960,43 @@ void transfer_details::operator()(post_t& post) bind_scope_t bound_scope(scope, temp); value_t substitute(expr.calc(bound_scope)); - switch (which_element) { - case SET_DATE: - temp.xdata().date = substitute.to_date(); - break; + if (! substitute.is_null()) { + switch (which_element) { + case SET_DATE: + temp.xdata().date = substitute.to_date(); + break; - case SET_ACCOUNT: { - string account_name = substitute.to_string(); - if (! account_name.empty() && - account_name[account_name.length() - 1] != ':') { - account_t * prev_account = temp.account; - temp.account->remove_post(&temp); - - account_name += ':'; - account_name += prev_account->fullname(); - - std::list<string> account_names; - split_string(account_name, ':', account_names); - temp.account = create_temp_account_from_path(account_names, temps, - xact.journal->master); - temp.account->add_post(&temp); - - temp.account->add_flags(prev_account->flags()); - if (prev_account->has_xdata()) - temp.account->xdata().add_flags(prev_account->xdata().flags()); + case SET_ACCOUNT: { + string account_name = substitute.to_string(); + if (! account_name.empty() && + account_name[account_name.length() - 1] != ':') { + account_t * prev_account = temp.account; + temp.account->remove_post(&temp); + + account_name += ':'; + account_name += prev_account->fullname(); + + std::list<string> account_names; + split_string(account_name, ':', account_names); + temp.account = create_temp_account_from_path(account_names, temps, + xact.journal->master); + temp.account->add_post(&temp); + + temp.account->add_flags(prev_account->flags()); + if (prev_account->has_xdata()) + temp.account->xdata().add_flags(prev_account->xdata().flags()); + } + break; } - break; - } - case SET_PAYEE: - xact.payee = substitute.to_string(); - break; + case SET_PAYEE: + xact.payee = substitute.to_string(); + break; - default: - assert(false); - break; + default: + assert(false); + break; + } } item_handler<post_t>::operator()(temp); @@ -869,7 +1038,8 @@ void budget_posts::report_budget_items(const date_t& date) optional<date_t> begin = pair.first.start; if (! begin) { if (! pair.first.find_period(date)) - throw_(std::runtime_error, _("Something odd has happened")); + throw_(std::runtime_error, + _("Something odd has happened at date %1") << date); begin = pair.first.start; } assert(begin); diff --git a/src/filters.h b/src/filters.h index 82fbf687..a66d8c47 100644 --- a/src/filters.h +++ b/src/filters.h @@ -52,6 +52,56 @@ namespace ledger { ////////////////////////////////////////////////////////////////////// // +// Posting collector +// + +class post_splitter : public item_handler<post_t> +{ +public: + typedef std::map<value_t, posts_list> value_to_posts_map; + typedef function<void (const value_t&)> custom_flusher_t; + +protected: + value_to_posts_map posts_map; + report_t& report; + post_handler_ptr post_chain; + expr_t group_by_expr; + custom_flusher_t preflush_func; + optional<custom_flusher_t> postflush_func; + +public: + post_splitter(report_t& _report, + post_handler_ptr _post_chain, + expr_t _group_by_expr) + : report(_report), post_chain(_post_chain), + group_by_expr(_group_by_expr), + preflush_func(bind(&post_splitter::print_title, this, _1)) { + TRACE_CTOR(post_splitter, "scope_t&, post_handler_ptr, expr_t"); + } + virtual ~post_splitter() { + TRACE_DTOR(post_splitter); + } + + void set_preflush_func(custom_flusher_t functor) { + preflush_func = functor; + } + void set_postflush_func(custom_flusher_t functor) { + postflush_func = functor; + } + + virtual void print_title(const value_t& val); + + virtual void flush(); + virtual void operator()(post_t& post); + + virtual void clear() { + posts_map.clear(); + post_chain->clear(); + } +}; + +////////////////////////////////////////////////////////////////////// +// // Posting filters // @@ -88,6 +138,11 @@ public: virtual void operator()(post_t& post) { posts.push_back(&post); } + + virtual void clear() { + posts.clear(); + item_handler<post_t>::clear(); + } }; class posts_iterator; @@ -149,27 +204,34 @@ public: virtual void flush(); virtual void operator()(post_t& post); + + virtual void clear() { + completed = false; + posts.clear(); + xacts_seen = 0; + last_xact = NULL; + + item_handler<post_t>::clear(); + } }; class sort_posts : public item_handler<post_t> { typedef std::deque<post_t *> posts_deque; - posts_deque posts; - const expr_t sort_order; + posts_deque posts; + expr_t sort_order; sort_posts(); public: - sort_posts(post_handler_ptr handler, - const expr_t& _sort_order) + sort_posts(post_handler_ptr handler, const expr_t& _sort_order) : item_handler<post_t>(handler), sort_order(_sort_order) { TRACE_CTOR(sort_posts, "post_handler_ptr, const value_expr&"); } - sort_posts(post_handler_ptr handler, - const string& _sort_order) + sort_posts(post_handler_ptr handler, const string& _sort_order) : item_handler<post_t>(handler), sort_order(_sort_order) { TRACE_CTOR(sort_posts, @@ -189,6 +251,13 @@ public: virtual void operator()(post_t& post) { posts.push_back(&post); } + + virtual void clear() { + posts.clear(); + sort_order.mark_uncompiled(); + + item_handler<post_t>::clear(); + } }; class sort_xacts : public item_handler<post_t> @@ -199,14 +268,12 @@ class sort_xacts : public item_handler<post_t> sort_xacts(); public: - sort_xacts(post_handler_ptr handler, - const expr_t& _sort_order) + sort_xacts(post_handler_ptr handler, const expr_t& _sort_order) : sorter(handler, _sort_order) { TRACE_CTOR(sort_xacts, "post_handler_ptr, const value_expr&"); } - sort_xacts(post_handler_ptr handler, - const string& _sort_order) + sort_xacts(post_handler_ptr handler, const string& _sort_order) : sorter(handler, _sort_order) { TRACE_CTOR(sort_xacts, "post_handler_ptr, const string&"); @@ -228,6 +295,13 @@ public: last_xact = post.xact; } + + virtual void clear() { + sorter.clear(); + last_xact = NULL; + + item_handler<post_t>::clear(); + } }; class filter_posts : public item_handler<post_t> @@ -255,6 +329,11 @@ public: (*handler)(post); } } + + virtual void clear() { + pred.mark_uncompiled(); + item_handler<post_t>::clear(); + } }; class anonymize_posts : public item_handler<post_t> @@ -274,6 +353,13 @@ public: } virtual void operator()(post_t& post); + + virtual void clear() { + temps.clear(); + last_xact = NULL; + + item_handler<post_t>::clear(); + } }; class calc_posts : public item_handler<post_t> @@ -297,6 +383,13 @@ public: } virtual void operator()(post_t& post); + + virtual void clear() { + last_post = NULL; + amount_expr.mark_uncompiled(); + + item_handler<post_t>::clear(); + } }; class collapse_posts : public item_handler<post_t> @@ -334,13 +427,29 @@ public: } virtual void flush() { - report_subtotal(); + report_subtotal(); item_handler<post_t>::flush(); } void report_subtotal(); virtual void operator()(post_t& post); + + virtual void clear() { + amount_expr.mark_uncompiled(); + display_predicate.mark_uncompiled(); + only_predicate.mark_uncompiled(); + + subtotal = value_t(); + count = 0; + last_xact = NULL; + last_post = NULL; + + temps.clear(); + component_posts.clear(); + + item_handler<post_t>::clear(); + } }; class related_posts : public item_handler<post_t> @@ -367,6 +476,11 @@ public: post.xdata().add_flags(POST_EXT_RECEIVED); posts.push_back(&post); } + + virtual void clear() { + posts.clear(); + item_handler<post_t>::clear(); + } }; class changed_value_posts : public item_handler<post_t> @@ -381,9 +495,11 @@ class changed_value_posts : public item_handler<post_t> bool changed_values_only; bool for_accounts_report; bool show_unrealized; + bool show_rounding; post_t * last_post; value_t last_total; value_t last_display_total; + value_t repriced_total; temporaries_t temps; account_t& revalued_account; account_t& rounding_account; @@ -396,7 +512,8 @@ public: changed_value_posts(post_handler_ptr handler, report_t& _report, bool _for_accounts_report, - bool _show_unrealized); + bool _show_unrealized, + bool _show_rounding); virtual ~changed_value_posts() { TRACE_DTOR(changed_value_posts); @@ -405,9 +522,24 @@ public: virtual void flush(); void output_revaluation(post_t& post, const date_t& current); + void output_intermediate_prices(post_t& post, const date_t& current); void output_rounding(post_t& post); virtual void operator()(post_t& post); + + virtual void clear() { + display_amount_expr.mark_uncompiled(); + total_expr.mark_uncompiled(); + display_total_expr.mark_uncompiled(); + + last_post = NULL; + last_total = value_t(); + last_display_total = value_t(); + + temps.clear(); + + item_handler<post_t>::clear(); + } }; class subtotal_posts : public item_handler<post_t> @@ -469,10 +601,20 @@ public: item_handler<post_t>::flush(); } virtual void operator()(post_t& post); + + virtual void clear() { + amount_expr.mark_uncompiled(); + values.clear(); + temps.clear(); + component_posts.clear(); + + item_handler<post_t>::clear(); + } }; class interval_posts : public subtotal_posts { + date_interval_t start_interval; date_interval_t interval; date_interval_t last_interval; post_t * last_post; @@ -489,8 +631,9 @@ public: const date_interval_t& _interval, bool _exact_periods = false, bool _generate_empty_posts = false) - : subtotal_posts(_handler, amount_expr), interval(_interval), - last_post(NULL), empty_account(temps.create_account(_("<None>"))), + : subtotal_posts(_handler, amount_expr), start_interval(_interval), + interval(start_interval), last_post(NULL), + empty_account(temps.create_account(_("<None>"))), exact_periods(_exact_periods), generate_empty_posts(_generate_empty_posts) { TRACE_CTOR(interval_posts, @@ -510,6 +653,14 @@ public: } } virtual void operator()(post_t& post); + + virtual void clear() { + interval = start_interval; + last_interval = date_interval_t(); + last_post = NULL; + + item_handler<post_t>::clear(); + } }; class posts_as_equity : public subtotal_posts @@ -537,6 +688,11 @@ public: report_subtotal(); subtotal_posts::flush(); } + + virtual void clear() { + last_post = NULL; + item_handler<post_t>::clear(); + } }; class by_payee_posts : public item_handler<post_t> @@ -560,6 +716,13 @@ class by_payee_posts : public item_handler<post_t> virtual void flush(); virtual void operator()(post_t& post); + + virtual void clear() { + amount_expr.mark_uncompiled(); + payee_subtotals.clear(); + + item_handler<post_t>::clear(); + } }; class transfer_details : public item_handler<post_t> @@ -593,6 +756,13 @@ public: } virtual void operator()(post_t& post); + + virtual void clear() { + expr.mark_uncompiled(); + temps.clear(); + + item_handler<post_t>::clear(); + } }; class dow_posts : public subtotal_posts @@ -614,6 +784,13 @@ public: virtual void operator()(post_t& post) { days_of_the_week[post.date().day_of_week()].push_back(&post); } + + virtual void clear() { + for (int i = 0; i < 7; i++) + days_of_the_week[i].clear(); + + item_handler<post_t>::clear(); + } }; class generate_posts : public item_handler<post_t> @@ -640,6 +817,13 @@ public: void add_period_xacts(period_xacts_list& period_xacts); virtual void add_post(const date_interval_t& period, post_t& post); + + virtual void clear() { + pending_posts.clear(); + temps.clear(); + + item_handler<post_t>::clear(); + } }; class budget_posts : public generate_posts @@ -690,6 +874,11 @@ class forecast_posts : public generate_posts virtual void add_post(const date_interval_t& period, post_t& post); virtual void flush(); + + virtual void clear() { + pred.mark_uncompiled(); + item_handler<post_t>::clear(); + } }; ////////////////////////////////////////////////////////////////////// @@ -715,6 +904,13 @@ public: virtual ~pass_down_accounts() { TRACE_DTOR(pass_down_accounts); } + + virtual void clear() { + if (pred) + pred->mark_uncompiled(); + + item_handler<account_t>::clear(); + } }; } // namespace ledger diff --git a/src/item.cc b/src/item.cc index 14a0896f..0a22b260 100644 --- a/src/item.cc +++ b/src/item.cc @@ -227,7 +227,7 @@ namespace { return NULL_VALUE; } value_t get_note(item_t& item) { - return string_value(item.note ? *item.note : empty_string); + return item.note ? string_value(*item.note) : NULL_VALUE; } value_t has_tag(call_scope_t& args) { @@ -260,7 +260,8 @@ namespace { return false; } - value_t get_tag(call_scope_t& args) { + value_t get_tag(call_scope_t& args) + { item_t& item(find_scope<item_t>(args)); optional<string> str; @@ -292,14 +293,14 @@ namespace { if (str) return string_value(*str); else - return string_value(empty_string); + return NULL_VALUE; } value_t get_pathname(item_t& item) { if (item.pos) return string_value(item.pos->pathname.string()); else - return string_value(empty_string); + return NULL_VALUE; } value_t get_beg_pos(item_t& item) { diff --git a/src/option.h b/src/option.h index 9688171e..f11497a4 100644 --- a/src/option.h +++ b/src/option.h @@ -90,13 +90,16 @@ public: void report(std::ostream& out) const { if (handled && source) { + out.width(24); + out << std::right << desc(); if (wants_arg) { - out << desc() << " => "; - value.dump(out); + out << " = "; + value.print(out, 42); } else { - out << desc(); + out.width(45); + out << ' '; } - out << " <" << *source << ">" << std::endl; + out << std::left << *source << std::endl; } } @@ -202,20 +205,20 @@ public: }; #define BEGIN(type, name) \ - struct name ## _option_t : public option_t<type> + struct name ## option_t : public option_t<type> #define CTOR(type, name) \ - name ## _option_t() : option_t<type>(#name) + name ## option_t() : option_t<type>(#name) #define DECL1(type, name, vartype, var, value) \ vartype var ; \ - name ## _option_t() : option_t<type>(#name), var(value) + name ## option_t() : option_t<type>(#name), var(value) #define DO() virtual void handler_thunk(call_scope_t&) #define DO_(var) virtual void handler_thunk(call_scope_t& var) -#define END(name) name ## _handler +#define END(name) name ## handler -#define COPY_OPT(name, other) name ## _handler(other.name ## _handler) +#define COPY_OPT(name, other) name ## handler(other.name ## handler) #define MAKE_OPT_HANDLER(type, x) \ expr_t::op_t::wrap_functor(bind(&option_t<type>::handler_wrapper, x, _1)) @@ -235,26 +238,26 @@ inline bool is_eq(const char * p, const char * n) { #define OPT(name) \ if (is_eq(p, #name)) \ - return ((name ## _handler).parent = this, &(name ## _handler)) + return ((name ## handler).parent = this, &(name ## handler)) #define OPT_ALT(name, alt) \ if (is_eq(p, #name) || is_eq(p, #alt)) \ - return ((name ## _handler).parent = this, &(name ## _handler)) + return ((name ## handler).parent = this, &(name ## handler)) #define OPT_(name) \ if (! *(p + 1) || \ - ((name ## _handler).wants_arg && \ + ((name ## handler).wants_arg && \ *(p + 1) == '_' && ! *(p + 2)) || \ is_eq(p, #name)) \ - return ((name ## _handler).parent = this, &(name ## _handler)) + return ((name ## handler).parent = this, &(name ## handler)) #define OPT_CH(name) \ if (! *(p + 1) || \ - ((name ## _handler).wants_arg && \ + ((name ## handler).wants_arg && \ *(p + 1) == '_' && ! *(p + 2))) \ - return ((name ## _handler).parent = this, &(name ## _handler)) + return ((name ## handler).parent = this, &(name ## handler)) -#define HANDLER(name) name ## _handler +#define HANDLER(name) name ## handler #define HANDLED(name) HANDLER(name) #define OPTION(type, name) \ diff --git a/src/output.cc b/src/output.cc index 30775310..f697dee4 100644 --- a/src/output.cc +++ b/src/output.cc @@ -42,8 +42,10 @@ namespace ledger { format_posts::format_posts(report_t& _report, const string& format, - const optional<string>& _prepend_format) - : report(_report), last_xact(NULL), last_post(NULL) + const optional<string>& _prepend_format, + std::size_t _prepend_width) + : report(_report), prepend_width(_prepend_width), + last_xact(NULL), last_post(NULL), first_report_title(true) { TRACE_CTOR(format_posts, "report&, const string&, bool"); @@ -76,14 +78,34 @@ void format_posts::flush() void format_posts::operator()(post_t& post) { - std::ostream& out(report.output_stream); - if (! post.has_xdata() || ! post.xdata().has_flags(POST_EXT_DISPLAYED)) { + std::ostream& out(report.output_stream); + bind_scope_t bound_scope(report, post); - if (prepend_format) + if (! report_title.empty()) { + if (first_report_title) + first_report_title = false; + else + out << '\n'; + + value_scope_t val_scope(string_value(report_title)); + bind_scope_t inner_scope(bound_scope, val_scope); + + format_t group_title_format; + group_title_format + .parse_format(report.HANDLER(group_title_format_).str()); + + out << group_title_format(inner_scope); + + report_title = ""; + } + + if (prepend_format) { + out.width(prepend_width); out << prepend_format(bound_scope); + } if (last_xact != post.xact) { if (last_xact) { @@ -107,8 +129,10 @@ void format_posts::operator()(post_t& post) format_accounts::format_accounts(report_t& _report, const string& format, - const optional<string>& _prepend_format) - : report(_report), disp_pred() + const optional<string>& _prepend_format, + std::size_t _prepend_width) + : report(_report), prepend_width(_prepend_width), disp_pred(), + first_report_title(true) { TRACE_CTOR(format_accounts, "report&, const string&"); @@ -139,17 +163,37 @@ std::size_t format_accounts::post_account(account_t& account, const bool flat) if (account.xdata().has_flags(ACCOUNT_EXT_TO_DISPLAY) && ! account.xdata().has_flags(ACCOUNT_EXT_DISPLAYED)) { + std::ostream& out(report.output_stream); + DEBUG("account.display", "Displaying account: " << account.fullname()); account.xdata().add_flags(ACCOUNT_EXT_DISPLAYED); bind_scope_t bound_scope(report, account); - if (prepend_format) - static_cast<std::ostream&>(report.output_stream) - << prepend_format(bound_scope); + if (! report_title.empty()) { + if (first_report_title) + first_report_title = false; + else + out << '\n'; + + value_scope_t val_scope(string_value(report_title)); + bind_scope_t inner_scope(bound_scope, val_scope); + + format_t group_title_format; + group_title_format + .parse_format(report.HANDLER(group_title_format_).str()); - static_cast<std::ostream&>(report.output_stream) - << account_line_format(bound_scope); + out << group_title_format(inner_scope); + + report_title = ""; + } + + if (prepend_format) { + out.width(prepend_width); + out << prepend_format(bound_scope); + } + + out << account_line_format(bound_scope); return 1; } @@ -216,9 +260,11 @@ void format_accounts::flush() bind_scope_t bound_scope(report, *report.session.journal->master); out << separator_format(bound_scope); - if (prepend_format) + if (prepend_format) { + static_cast<std::ostream&>(report.output_stream).width(prepend_width); static_cast<std::ostream&>(report.output_stream) << prepend_format(bound_scope); + } out << total_line_format(bound_scope); } @@ -232,4 +278,89 @@ void format_accounts::operator()(account_t& account) posted_accounts.push_back(&account); } +void report_accounts::flush() +{ + std::ostream& out(report.output_stream); + + foreach (accounts_pair& entry, accounts) { + if (report.HANDLED(count)) + out << entry.second << ' '; + out << *entry.first << '\n'; + } +} + +void report_accounts::operator()(post_t& post) +{ + std::map<account_t *, std::size_t>::iterator i = accounts.find(post.account); + if (i == accounts.end()) + accounts.insert(accounts_pair(post.account, 1)); + else + (*i).second++; +} + +void report_payees::flush() +{ + std::ostream& out(report.output_stream); + + foreach (payees_pair& entry, payees) { + if (report.HANDLED(count)) + out << entry.second << ' '; + out << entry.first << '\n'; + } +} + +void report_payees::operator()(post_t& post) +{ + std::map<string, std::size_t>::iterator i = payees.find(post.xact->payee); + if (i == payees.end()) + payees.insert(payees_pair(post.xact->payee, 1)); + else + (*i).second++; +} + +void report_commodities::flush() +{ + std::ostream& out(report.output_stream); + + foreach (commodities_pair& entry, commodities) { + if (report.HANDLED(count)) + out << entry.second << ' '; + out << *entry.first << '\n'; + } +} + +void report_commodities::operator()(post_t& post) +{ + amount_t temp(post.amount.strip_annotations(report.what_to_keep())); + commodity_t& comm(temp.commodity()); + + std::map<commodity_t *, std::size_t>::iterator i = commodities.find(&comm); + if (i == commodities.end()) + commodities.insert(commodities_pair(&comm, 1)); + else + (*i).second++; + + if (comm.has_annotation()) { + annotated_commodity_t& ann_comm(as_annotated_commodity(comm)); + if (ann_comm.details.price) { + std::map<commodity_t *, std::size_t>::iterator i = + commodities.find(&ann_comm.details.price->commodity()); + if (i == commodities.end()) + commodities.insert + (commodities_pair(&ann_comm.details.price->commodity(), 1)); + else + (*i).second++; + } + } + + if (post.cost) { + amount_t temp_cost(post.cost->strip_annotations(report.what_to_keep())); + i = commodities.find(&temp_cost.commodity()); + if (i == commodities.end()) + commodities.insert(commodities_pair(&temp_cost.commodity(), 1)); + else + (*i).second++; + } +} + } // namespace ledger diff --git a/src/output.h b/src/output.h index 7618e567..a19c6235 100644 --- a/src/output.h +++ b/src/output.h @@ -56,23 +56,40 @@ class report_t; class format_posts : public item_handler<post_t> { protected: - report_t& report; - format_t first_line_format; - format_t next_lines_format; - format_t between_format; - format_t prepend_format; - xact_t * last_xact; - post_t * last_post; + report_t& report; + format_t first_line_format; + format_t next_lines_format; + format_t between_format; + format_t prepend_format; + std::size_t prepend_width; + xact_t * last_xact; + post_t * last_post; + bool first_report_title; + string report_title; public: format_posts(report_t& _report, const string& format, - const optional<string>& _prepend_format = none); + const optional<string>& _prepend_format = none, + std::size_t _prepend_width = 0); virtual ~format_posts() { TRACE_DTOR(format_posts); } + virtual void title(const string& str) { + report_title = str; + } + virtual void flush(); virtual void operator()(post_t& post); + + virtual void clear() { + last_xact = NULL; + last_post = NULL; + + report_title = ""; + + item_handler<post_t>::clear(); + } }; class format_accounts : public item_handler<account_t> @@ -83,13 +100,17 @@ protected: format_t total_line_format; format_t separator_format; format_t prepend_format; + std::size_t prepend_width; predicate_t disp_pred; + bool first_report_title; + string report_title; std::list<account_t *> posted_accounts; public: format_accounts(report_t& _report, const string& _format, - const optional<string>& _prepend_format = none); + const optional<string>& _prepend_format = none, + std::size_t _prepend_width = 0); virtual ~format_accounts() { TRACE_DTOR(format_accounts); } @@ -97,10 +118,101 @@ public: std::pair<std::size_t, std::size_t> mark_accounts(account_t& account, const bool flat); + virtual void title(const string& str) { + report_title = str; + } + virtual std::size_t post_account(account_t& account, const bool flat); virtual void flush(); virtual void operator()(account_t& account); + + virtual void clear() { + disp_pred.mark_uncompiled(); + posted_accounts.clear(); + + report_title = ""; + + item_handler<account_t>::clear(); + } +}; + +class report_accounts : public item_handler<post_t> +{ +protected: + report_t& report; + + std::map<account_t *, std::size_t> accounts; + + typedef std::map<account_t *, std::size_t>::value_type accounts_pair; + +public: + report_accounts(report_t& _report) : report(_report) { + TRACE_CTOR(report_accounts, "report&"); + } + virtual ~report_accounts() { + TRACE_DTOR(report_accounts); + } + + virtual void flush(); + virtual void operator()(post_t& post); + + virtual void clear() { + accounts.clear(); + item_handler<post_t>::clear(); + } +}; + +class report_payees : public item_handler<post_t> +{ +protected: + report_t& report; + + std::map<string, std::size_t> payees; + + typedef std::map<string, std::size_t>::value_type payees_pair; + +public: + report_payees(report_t& _report) : report(_report) { + TRACE_CTOR(report_payees, "report&"); + } + virtual ~report_payees() { + TRACE_DTOR(report_payees); + } + + virtual void flush(); + virtual void operator()(post_t& post); + + virtual void clear() { + payees.clear(); + item_handler<post_t>::clear(); + } +}; + +class report_commodities : public item_handler<post_t> +{ +protected: + report_t& report; + + std::map<commodity_t *, std::size_t> commodities; + + typedef std::map<commodity_t *, std::size_t>::value_type commodities_pair; + +public: + report_commodities(report_t& _report) : report(_report) { + TRACE_CTOR(report_commodities, "report&"); + } + virtual ~report_commodities() { + TRACE_DTOR(report_commodities); + } + + virtual void flush(); + virtual void operator()(post_t& post); + + virtual void clear() { + commodities.clear(); + item_handler<post_t>::clear(); + } }; } // namespace ledger diff --git a/src/parser.cc b/src/parser.cc index e8e987cb..f52949ce 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -79,9 +79,7 @@ expr_t::parser_t::parse_value_term(std::istream& in, case token_t::LPAREN: node = parse_value_expr(in, tflags.plus_flags(PARSE_PARTIAL) .minus_flags(PARSE_SINGLE)); - tok = next_token(in, tflags); - if (tok.kind != token_t::RPAREN) - tok.expected(')'); + tok = next_token(in, tflags, ')'); if (node->kind == op_t::O_CONS) { ptr_op_t prev(node); @@ -383,10 +381,7 @@ expr_t::parser_t::parse_querycolon_expr(std::istream& in, throw_(parse_error, _("%1 operator not followed by argument") << tok.symbol); - token_t& next_tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT)); - if (next_tok.kind != token_t::COLON) - next_tok.expected(':'); - + next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT), ':'); prev = node->right(); ptr_op_t subnode = new op_t(op_t::O_COLON); subnode->set_left(prev); diff --git a/src/parser.h b/src/parser.h index 5eba4ffd..aab48830 100644 --- a/src/parser.h +++ b/src/parser.h @@ -52,11 +52,12 @@ class expr_t::parser_t : public noncopyable mutable token_t lookahead; mutable bool use_lookahead; - token_t& next_token(std::istream& in, const parse_flags_t& tflags) const { + token_t& next_token(std::istream& in, const parse_flags_t& tflags, + const char expecting = '\0') const { if (use_lookahead) use_lookahead = false; else - lookahead.next(in, tflags); + lookahead.next(in, tflags, expecting); return lookahead; } diff --git a/src/post.cc b/src/post.cc index 183fb901..7dc15830 100644 --- a/src/post.cc +++ b/src/post.cc @@ -142,11 +142,15 @@ namespace { return value_t(static_cast<scope_t *>(post.xact)); } + value_t get_xact_id(post_t& post) { + return static_cast<long>(post.xact_id()); + } + value_t get_code(post_t& post) { if (post.xact->code) return string_value(*post.xact->code); else - return string_value(empty_string); + return NULL_VALUE; } value_t get_payee(post_t& post) { @@ -154,9 +158,13 @@ namespace { } value_t get_note(post_t& post) { - string note = post.note ? *post.note : empty_string; - note += post.xact->note ? *post.xact->note : empty_string; - return string_value(note); + if (post.note || post.xact->note) { + string note = post.note ? *post.note : empty_string; + note += post.xact->note ? *post.xact->note : empty_string; + return string_value(note); + } else { + return NULL_VALUE; + } } value_t get_magnitude(post_t& post) { @@ -183,11 +191,21 @@ namespace { } value_t get_commodity(post_t& post) { - return string_value(post.amount.commodity().symbol()); + if (post.has_xdata() && + post.xdata().has_flags(POST_EXT_COMPOUND)) + return string_value(post.xdata().compound_value.to_amount() + .commodity().symbol()); + else + return string_value(post.amount.commodity().symbol()); } value_t get_commodity_is_primary(post_t& post) { - return post.amount.commodity().has_flags(COMMODITY_PRIMARY); + if (post.has_xdata() && + post.xdata().has_flags(POST_EXT_COMPOUND)) + return post.xdata().compound_value.to_amount() + .commodity().has_flags(COMMODITY_PRIMARY); + else + return post.amount.commodity().has_flags(COMMODITY_PRIMARY); } value_t get_has_cost(post_t& post) { @@ -222,7 +240,7 @@ namespace { return 1L; } - value_t get_account(call_scope_t& scope) + value_t account_name(call_scope_t& scope) { in_context_t<post_t> env(scope, "&v"); @@ -265,14 +283,32 @@ namespace { } else { name = env->reported_account()->fullname(); } + return string_value(name); + } - if (env->has_flags(POST_VIRTUAL)) { - if (env->must_balance()) - name = string("[") + name + "]"; - else - name = string("(") + name + ")"; + value_t get_display_account(call_scope_t& scope) + { + in_context_t<post_t> env(scope, "&v"); + + value_t acct = account_name(scope); + if (acct.is_string()) { + if (env->has_flags(POST_VIRTUAL)) { + if (env->must_balance()) + acct = string_value(string("[") + acct.as_string() + "]"); + else + acct = string_value(string("(") + acct.as_string() + ")"); + } } - return string_value(name); + return acct; + } + + value_t get_account(call_scope_t& scope) + { + return account_name(scope); + } + + value_t get_account_id(post_t& post) { + return static_cast<long>(post.account_id()); } value_t get_account_base(post_t& post) { @@ -351,6 +387,8 @@ expr_t::ptr_op_t post_t::lookup(const symbol_t::kind_t kind, return WRAP_FUNCTOR(get_account); else if (name == "account_base") return WRAP_FUNCTOR(get_wrapper<&get_account_base>); + else if (name == "account_id") + return WRAP_FUNCTOR(get_wrapper<&get_account_id>); else if (name == "any") return WRAP_FUNCTOR(&fn_any); else if (name == "all") @@ -378,7 +416,9 @@ expr_t::ptr_op_t post_t::lookup(const symbol_t::kind_t kind, break; case 'd': - if (name == "depth") + if (name == "display_account") + return WRAP_FUNCTOR(get_display_account); + else if (name == "depth") return WRAP_FUNCTOR(get_wrapper<&get_account_depth>); else if (name == "datetime") return WRAP_FUNCTOR(get_wrapper<&get_datetime>); @@ -444,6 +484,8 @@ expr_t::ptr_op_t post_t::lookup(const symbol_t::kind_t kind, case 'x': if (name == "xact") return WRAP_FUNCTOR(get_wrapper<&get_xact>); + else if (name == "xact_id") + return WRAP_FUNCTOR(get_wrapper<&get_xact_id>); break; case 'N': @@ -479,6 +521,30 @@ amount_t post_t::resolve_expr(scope_t& scope, expr_t& expr) } } +std::size_t post_t::xact_id() const +{ + std::size_t id = 1; + foreach (post_t * p, xact->posts) { + if (p == this) + return id; + id++; + } + assert(! "Failed to find posting within its transaction"); + return 0; +} + +std::size_t post_t::account_id() const +{ + std::size_t id = 1; + foreach (post_t * p, account->posts) { + if (p == this) + return id; + id++; + } + assert(! "Failed to find posting within its transaction"); + return 0; +} + bool post_t::valid() const { if (! xact) { @@ -118,6 +118,9 @@ public: amount_t resolve_expr(scope_t& scope, expr_t& expr); + std::size_t xact_id() const; + std::size_t account_id() const; + bool valid() const; struct xdata_t : public supports_flags<uint_least16_t> diff --git a/src/print.cc b/src/print.cc index 34f5af51..a8aa5872 100644 --- a/src/print.cc +++ b/src/print.cc @@ -42,15 +42,20 @@ namespace ledger { print_xacts::print_xacts(report_t& _report, bool _print_raw) - : report(_report), print_raw(_print_raw) + : report(_report), print_raw(_print_raw), first_title(true) { TRACE_CTOR(print_xacts, "report&, bool"); } namespace { - void print_note(std::ostream& out, const string& note) + void print_note(std::ostream& out, + const string& note, + const std::size_t columns, + const std::size_t prior_width) { - if (note.length() > 15) + // The 4 is for four leading spaces at the beginning of the posting, and + // the 3 is for two spaces and a semi-colon before the note. + if (columns > 0 && note.length() > columns - (prior_width + 3)) out << "\n ;"; else out << " ;"; @@ -71,23 +76,40 @@ namespace { void print_xact(report_t& report, std::ostream& out, xact_t& xact) { - out << format_date(item_t::use_effective_date ? + format_type_t format_type = FMT_WRITTEN; + optional<const char *> format; + + if (report.HANDLED(date_format_)) { + format_type = FMT_CUSTOM; + format = report.HANDLER(date_format_).str().c_str(); + } + + std::ostringstream buf; + + buf << format_date(item_t::use_effective_date ? xact.date() : xact.actual_date(), - FMT_WRITTEN); + format_type, format); if (! item_t::use_effective_date && xact.effective_date()) - out << '=' << format_date(*xact.effective_date(), FMT_WRITTEN); - out << ' '; + buf << '=' << format_date(*xact.effective_date(), + format_type, format); + buf << ' '; - out << (xact.state() == item_t::CLEARED ? "* " : + buf << (xact.state() == item_t::CLEARED ? "* " : (xact.state() == item_t::PENDING ? "! " : "")); if (xact.code) - out << '(' << *xact.code << ") "; + buf << '(' << *xact.code << ") "; + + buf << xact.payee; - out << xact.payee; + string leader = buf.str(); + out << leader; + + std::size_t columns = (report.HANDLED(columns_) ? + report.HANDLER(columns_).value.to_long() : 80); if (xact.note) - print_note(out, *xact.note); + print_note(out, *xact.note, columns, unistring(leader).length()); out << '\n'; if (xact.metadata) { @@ -132,20 +154,37 @@ namespace { buf << ')'; } - if (! post->has_flags(POST_CALCULATED) || report.HANDLED(print_virtual)) { - unistring name(buf.str()); + unistring name(buf.str()); + + std::size_t account_width = + (report.HANDLER(account_width_).specified ? + report.HANDLER(account_width_).value.to_long() : 36); + if (account_width < name.length()) + account_width = name.length(); + + if (! post->has_flags(POST_CALCULATED) || report.HANDLED(print_virtual)) { out << name.extract(); - int slip = 36 - static_cast<int>(name.length()); - if (slip > 0) - out << string(slip, ' '); + int slip = (static_cast<int>(account_width) - + static_cast<int>(name.length())); + if (slip > 0) { + out.width(slip); + out << ' '; + } + + std::ostringstream amtbuf; string amt; if (post->amount_expr) { amt = post->amount_expr->text(); } else { + std::size_t amount_width = + (report.HANDLER(amount_width_).specified ? + report.HANDLER(amount_width_).value.to_long() : 12); + std::ostringstream amt_str; - report.scrub(post->amount).print(amt_str, 12, -1, true); + report.scrub(post->amount) + .print(amt_str, static_cast<int>(amount_width), -1, true); amt = amt_str.str(); } @@ -154,29 +193,44 @@ namespace { int amt_slip = (static_cast<int>(amt.length()) - static_cast<int>(trimmed_amt.length())); if (slip + amt_slip < 2) - out << string(2 - (slip + amt_slip), ' '); - out << amt; + amtbuf << string(2 - (slip + amt_slip), ' '); + amtbuf << amt; if (post->cost && ! post->has_flags(POST_CALCULATED)) { if (post->has_flags(POST_COST_IN_FULL)) - out << " @@ " << report.scrub(post->cost->abs()); + amtbuf << " @@ " << report.scrub(post->cost->abs()); else - out << " @ " << report.scrub((*post->cost / post->amount).abs()); + amtbuf << " @ " << report.scrub((*post->cost / post->amount).abs()); } if (post->assigned_amount) - out << " = " << report.scrub(*post->assigned_amount); + amtbuf << " = " << report.scrub(*post->assigned_amount); + + string trailer = amtbuf.str(); + out << trailer; + + account_width += unistring(trailer).length(); } else { out << buf.str(); } if (post->note) - print_note(out, *post->note); + print_note(out, *post->note, columns, 4 + account_width); out << '\n'; } } } +void print_xacts::title(const string&) +{ + if (first_title) { + first_title = false; + } else { + std::ostream& out(report.output_stream); + out << '\n'; + } +} + void print_xacts::flush() { std::ostream& out(report.output_stream); diff --git a/src/print.h b/src/print.h index f323b153..5263ec91 100644 --- a/src/print.h +++ b/src/print.h @@ -62,6 +62,7 @@ protected: xacts_present_map xacts_present; xacts_list xacts; bool print_raw; + bool first_title; public: print_xacts(report_t& _report, bool _print_raw = false); @@ -69,8 +70,17 @@ public: TRACE_DTOR(print_xacts); } + virtual void title(const string&); + virtual void flush(); virtual void operator()(post_t& post); + + virtual void clear() { + xacts_present.clear(); + xacts.clear(); + + item_handler<post_t>::clear(); + } }; diff --git a/src/py_account.cc b/src/py_account.cc index 3114cc0b..2b860a24 100644 --- a/src/py_account.cc +++ b/src/py_account.cc @@ -207,11 +207,11 @@ void export_account() .def("__len__", accounts_len) .def("__getitem__", accounts_getitem, return_internal_reference<>()) - .def("__iter__", range<return_internal_reference<> > + .def("__iter__", python::range<return_internal_reference<> > (&account_t::accounts_begin, &account_t::accounts_end)) - .def("accounts", range<return_internal_reference<> > + .def("accounts", python::range<return_internal_reference<> > (&account_t::accounts_begin, &account_t::accounts_end)) - .def("posts", range<return_internal_reference<> > + .def("posts", python::range<return_internal_reference<> > (&account_t::posts_begin, &account_t::posts_end)) .def("has_xdata", &account_t::has_xdata) diff --git a/src/py_commodity.cc b/src/py_commodity.cc index 22a4f153..fc7e8c3e 100644 --- a/src/py_commodity.cc +++ b/src/py_commodity.cc @@ -295,13 +295,16 @@ void export_commodity() .def("keys", py_pool_keys) .def("has_key", py_pool_contains) .def("__contains__", py_pool_contains) - .def("__iter__", range<return_value_policy<reference_existing_object> > + .def("__iter__", + python::range<return_value_policy<reference_existing_object> > (py_pool_commodities_begin, py_pool_commodities_end)) - .def("iteritems", range<return_value_policy<reference_existing_object> > + .def("iteritems", + python::range<return_value_policy<reference_existing_object> > (py_pool_commodities_begin, py_pool_commodities_end)) - .def("iterkeys", range<>(py_pool_commodities_keys_begin, - py_pool_commodities_keys_end)) - .def("itervalues", range<return_value_policy<reference_existing_object> > + .def("iterkeys", python::range<>(py_pool_commodities_keys_begin, + py_pool_commodities_keys_end)) + .def("itervalues", + python::range<return_value_policy<reference_existing_object> > (py_pool_commodities_values_begin, py_pool_commodities_values_end)) ; @@ -309,16 +312,16 @@ void export_commodity() scope().attr("commodities") = commodity_pool_t::current_pool; - scope().attr("COMMODITY_STYLE_DEFAULTS") = COMMODITY_STYLE_DEFAULTS; - scope().attr("COMMODITY_STYLE_SUFFIXED") = COMMODITY_STYLE_SUFFIXED; - scope().attr("COMMODITY_STYLE_SEPARATED") = COMMODITY_STYLE_SEPARATED; - scope().attr("COMMODITY_STYLE_EUROPEAN") = COMMODITY_STYLE_EUROPEAN; - scope().attr("COMMODITY_STYLE_THOUSANDS") = COMMODITY_STYLE_THOUSANDS; - scope().attr("COMMODITY_NOMARKET") = COMMODITY_NOMARKET; - scope().attr("COMMODITY_BUILTIN") = COMMODITY_BUILTIN; - scope().attr("COMMODITY_WALKED") = COMMODITY_WALKED; - scope().attr("COMMODITY_KNOWN") = COMMODITY_KNOWN; - scope().attr("COMMODITY_PRIMARY") = COMMODITY_PRIMARY; + scope().attr("COMMODITY_STYLE_DEFAULTS") = COMMODITY_STYLE_DEFAULTS; + scope().attr("COMMODITY_STYLE_SUFFIXED") = COMMODITY_STYLE_SUFFIXED; + scope().attr("COMMODITY_STYLE_SEPARATED") = COMMODITY_STYLE_SEPARATED; + scope().attr("COMMODITY_STYLE_DECIMAL_COMMA") = COMMODITY_STYLE_DECIMAL_COMMA; + scope().attr("COMMODITY_STYLE_THOUSANDS") = COMMODITY_STYLE_THOUSANDS; + scope().attr("COMMODITY_NOMARKET") = COMMODITY_NOMARKET; + scope().attr("COMMODITY_BUILTIN") = COMMODITY_BUILTIN; + scope().attr("COMMODITY_WALKED") = COMMODITY_WALKED; + scope().attr("COMMODITY_KNOWN") = COMMODITY_KNOWN; + scope().attr("COMMODITY_PRIMARY") = COMMODITY_PRIMARY; class_< commodity_t, boost::noncopyable > ("Commodity", no_init) #if 1 @@ -331,9 +334,9 @@ void export_commodity() .def("drop_flags", &delegates_flags<uint_least16_t>::drop_flags) #endif - .add_static_property("european_by_default", - make_getter(&commodity_t::european_by_default), - make_setter(&commodity_t::european_by_default)) + .add_static_property("decimal_comma_by_default", + make_getter(&commodity_t::decimal_comma_by_default), + make_setter(&commodity_t::decimal_comma_by_default)) .def("__str__", &commodity_t::symbol) .def("__unicode__", py_commodity_unicode) diff --git a/src/py_journal.cc b/src/py_journal.cc index 81ce290d..1848adc4 100644 --- a/src/py_journal.cc +++ b/src/py_journal.cc @@ -226,8 +226,8 @@ void export_journal() class_< collect_posts, bases<item_handler<post_t> >, shared_ptr<collect_posts>, boost::noncopyable >("PostCollector") .def("__len__", &collect_posts::length) - .def("__iter__", range<return_internal_reference<1, - with_custodian_and_ward_postcall<1, 0> > > + .def("__iter__", python::range<return_internal_reference<1, + with_custodian_and_ward_postcall<1, 0> > > (&collect_posts::begin, &collect_posts::end)) ; @@ -236,8 +236,9 @@ void export_journal() .def("__len__", &collector_wrapper::length) .def("__getitem__", posts_getitem, return_internal_reference<1, with_custodian_and_ward_postcall<0, 1> >()) - .def("__iter__", range<return_value_policy<reference_existing_object, - with_custodian_and_ward_postcall<0, 1> > > + .def("__iter__", + python::range<return_value_policy<reference_existing_object, + with_custodian_and_ward_postcall<0, 1> > > (&collector_wrapper::begin, &collector_wrapper::end)) ; @@ -296,15 +297,15 @@ void export_journal() with_custodian_and_ward_postcall<0, 1> >()) #endif - .def("__iter__", range<return_internal_reference<> > + .def("__iter__", python::range<return_internal_reference<> > (&journal_t::xacts_begin, &journal_t::xacts_end)) - .def("xacts", range<return_internal_reference<> > + .def("xacts", python::range<return_internal_reference<> > (&journal_t::xacts_begin, &journal_t::xacts_end)) - .def("auto_xacts", range<return_internal_reference<> > + .def("auto_xacts", python::range<return_internal_reference<> > (&journal_t::auto_xacts_begin, &journal_t::auto_xacts_end)) - .def("period_xacts", range<return_internal_reference<> > + .def("period_xacts", python::range<return_internal_reference<> > (&journal_t::period_xacts_begin, &journal_t::period_xacts_end)) - .def("sources", range<return_internal_reference<> > + .def("sources", python::range<return_internal_reference<> > (&journal_t::sources_begin, &journal_t::sources_end)) .def("read", py_read) diff --git a/src/py_xact.cc b/src/py_xact.cc index c4e1fd32..6553a67f 100644 --- a/src/py_xact.cc +++ b/src/py_xact.cc @@ -98,9 +98,9 @@ void export_xact() .def("finalize", &xact_base_t::finalize) - .def("__iter__", range<return_internal_reference<> > + .def("__iter__", python::range<return_internal_reference<> > (&xact_t::posts_begin, &xact_t::posts_end)) - .def("posts", range<return_internal_reference<> > + .def("posts", python::range<return_internal_reference<> > (&xact_t::posts_begin, &xact_t::posts_end)) .def("valid", &xact_base_t::valid) diff --git a/src/query.cc b/src/query.cc index 1f086df8..363c6f73 100644 --- a/src/query.cc +++ b/src/query.cc @@ -53,45 +53,51 @@ query_t::lexer_t::token_t query_t::lexer_t::next_token() } } - if (consume_next_arg) { - consume_next_arg = false; - arg_i = arg_end; - return token_t(token_t::TERM, (*begin).as_string()); - } - - resume: - bool consume_next = false; switch (*arg_i) { - case ' ': - case '\t': - case '\r': - case '\n': - if (++arg_i == arg_end) - return next_token(); - goto resume; - + case '\'': + case '"': case '/': { string pat; - bool found_end_slash = false; + char closing = *arg_i; + bool found_closing = false; for (++arg_i; arg_i != arg_end; ++arg_i) { if (*arg_i == '\\') { if (++arg_i == arg_end) throw_(parse_error, _("Unexpected '\\' at end of pattern")); } - else if (*arg_i == '/') { + else if (*arg_i == closing) { ++arg_i; - found_end_slash = true; + found_closing = true; break; } pat.push_back(*arg_i); } - if (! found_end_slash) - throw_(parse_error, _("Expected '/' at end of pattern")); + if (! found_closing) + throw_(parse_error, _("Expected '%1' at end of pattern") << closing); if (pat.empty()) throw_(parse_error, _("Match pattern is empty")); return token_t(token_t::TERM, pat); } + } + + if (multiple_args && consume_next_arg) { + consume_next_arg = false; + token_t tok(token_t::TERM, string(arg_i, arg_end)); + arg_i = arg_end; + return tok; + } + + resume: + bool consume_next = false; + switch (*arg_i) { + case ' ': + case '\t': + case '\r': + case '\n': + if (++arg_i == arg_end) + return next_token(); + goto resume; case '(': ++arg_i; return token_t(token_t::LPAREN); case ')': ++arg_i; return token_t(token_t::RPAREN); @@ -101,7 +107,10 @@ query_t::lexer_t::token_t query_t::lexer_t::next_token() case '@': ++arg_i; return token_t(token_t::TOK_PAYEE); case '#': ++arg_i; return token_t(token_t::TOK_CODE); case '%': ++arg_i; return token_t(token_t::TOK_META); - case '=': ++arg_i; return token_t(token_t::TOK_EQ); + case '=': + ++arg_i; + consume_next_arg = true; + return token_t(token_t::TOK_EQ); case '\\': consume_next = true; diff --git a/src/query.h b/src/query.h index 2b0bc75d..59adfd72 100644 --- a/src/query.h +++ b/src/query.h @@ -62,6 +62,7 @@ public: bool consume_whitespace; bool consume_next_arg; + bool multiple_args; public: struct token_t @@ -177,10 +178,11 @@ public: token_t token_cache; lexer_t(value_t::sequence_t::const_iterator _begin, - value_t::sequence_t::const_iterator _end) + value_t::sequence_t::const_iterator _end, + bool _multiple_args = true) : begin(_begin), end(_end), - consume_whitespace(false), - consume_next_arg(false) + consume_whitespace(false), consume_next_arg(false), + multiple_args(_multiple_args) { TRACE_CTOR(query_t::lexer_t, ""); assert(begin != end); @@ -192,6 +194,7 @@ public: arg_i(lexer.arg_i), arg_end(lexer.arg_end), consume_whitespace(lexer.consume_whitespace), consume_next_arg(lexer.consume_next_arg), + multiple_args(lexer.multiple_args), token_cache(lexer.token_cache) { TRACE_CTOR(query_t::lexer_t, "copy"); @@ -227,8 +230,8 @@ protected: expr_t::ptr_op_t parse_query_expr(lexer_t::token_t::kind_t tok_context); public: - parser_t(const value_t& _args) - : args(_args), lexer(args.begin(), args.end()) { + parser_t(const value_t& _args, bool multiple_args = true) + : args(_args), lexer(args.begin(), args.end(), multiple_args) { TRACE_CTOR(query_t::parser_t, ""); } parser_t(const parser_t& parser) @@ -261,28 +264,30 @@ public: TRACE_CTOR(query_t, "copy"); } query_t(const string& arg, - const keep_details_t& _what_to_keep = keep_details_t()) + const keep_details_t& _what_to_keep = keep_details_t(), + bool multiple_args = true) : predicate_t(_what_to_keep) { TRACE_CTOR(query_t, "string, keep_details_t"); if (! arg.empty()) { value_t temp(string_value(arg)); - parse_args(temp.to_sequence()); + parse_args(temp.to_sequence(), multiple_args); } } query_t(const value_t& args, - const keep_details_t& _what_to_keep = keep_details_t()) + const keep_details_t& _what_to_keep = keep_details_t(), + bool multiple_args = true) : predicate_t(_what_to_keep) { TRACE_CTOR(query_t, "value_t, keep_details_t"); if (! args.empty()) - parse_args(args); + parse_args(args, multiple_args); } virtual ~query_t() { TRACE_DTOR(query_t); } - void parse_args(const value_t& args) { + void parse_args(const value_t& args, bool multiple_args = true) { if (! parser) - parser = parser_t(args); + parser = parser_t(args, multiple_args); ptr = parser->parse(); // expr_t::ptr } diff --git a/src/report.cc b/src/report.cc index 1180c019..90de9a3f 100644 --- a/src/report.cc +++ b/src/report.cc @@ -119,6 +119,8 @@ void report_t::normalize_options(const string& verb) HANDLER(meta_).str() + "\"))"); } } + if (! HANDLED(prepend_width_)) + HANDLER(prepend_width_).on_with(string("?normalize"), static_cast<long>(0)); if (verb == "print" || verb == "xact" || verb == "dump") { HANDLER(related).on_only(string("?normalize")); @@ -143,9 +145,6 @@ void report_t::normalize_options(const string& verb) // then ignore the period since the begin/end are the only interesting // details. if (HANDLED(period_)) { - if (! HANDLED(sort_all_)) - HANDLER(sort_xacts_).on_only(string("?normalize")); - date_interval_t interval(HANDLER(period_).str()); optional<date_t> begin = interval.begin(session.current_year); @@ -162,6 +161,8 @@ void report_t::normalize_options(const string& verb) if (! interval.duration) HANDLER(period_).off(); + else if (! HANDLED(sort_all_)) + HANDLER(sort_xacts_).on_only(string("?normalize")); } // If -j or -J were specified, set the appropriate format string now so as @@ -273,77 +274,158 @@ void report_t::parse_query_args(const value_t& args, const string& whence) } } +namespace { + struct posts_flusher + { + report_t& report; + post_handler_ptr handler; + + posts_flusher(report_t& _report, post_handler_ptr _handler) + : report(_report), handler(_handler) {} + + void operator()(const value_t&) { + report.session.journal->clear_xdata(); + } + }; +} + void report_t::posts_report(post_handler_ptr handler) { + handler = chain_post_handlers(*this, handler); + if (HANDLED(group_by_)) { + std::auto_ptr<post_splitter> + splitter(new post_splitter(*this, handler, HANDLER(group_by_).expr)); + splitter->set_postflush_func(posts_flusher(*this, handler)); + handler = post_handler_ptr(splitter.release()); + } + handler = chain_pre_post_handlers(*this, handler); + journal_posts_iterator walker(*session.journal.get()); - pass_down_posts(chain_post_handlers(*this, handler), walker); - session.journal->clear_xdata(); + pass_down_posts(handler, walker); + + if (! HANDLED(group_by_)) + posts_flusher(*this, handler)(value_t()); } void report_t::generate_report(post_handler_ptr handler) { HANDLER(limit_).on(string("#generate"), "actual"); + handler = chain_handlers(*this, handler); + generate_posts_iterator walker (session, HANDLED(seed_) ? static_cast<unsigned int>(HANDLER(seed_).value.to_long()) : 0, HANDLED(head_) ? static_cast<unsigned int>(HANDLER(head_).value.to_long()) : 50); - pass_down_posts(chain_post_handlers(*this, handler), walker); + pass_down_posts(handler, walker); } void report_t::xact_report(post_handler_ptr handler, xact_t& xact) { + handler = chain_handlers(*this, handler); + xact_posts_iterator walker(xact); - pass_down_posts(chain_post_handlers(*this, handler), walker); + pass_down_posts(handler, walker); + xact.clear_xdata(); } +namespace { + struct accounts_title_printer + { + report_t& report; + acct_handler_ptr handler; + + accounts_title_printer(report_t& _report, acct_handler_ptr _handler) + : report(_report), handler(_handler) {} + + void operator()(const value_t& val) + { + if (! report.HANDLED(no_titles)) { + std::ostringstream buf; + val.print(buf); + handler->title(buf.str()); + } + } + }; + + struct accounts_flusher + { + report_t& report; + acct_handler_ptr handler; + + accounts_flusher(report_t& _report, acct_handler_ptr _handler) + : report(_report), handler(_handler) {} + + void operator()(const value_t&) + { + report.HANDLER(amount_).expr.mark_uncompiled(); + report.HANDLER(total_).expr.mark_uncompiled(); + report.HANDLER(display_amount_).expr.mark_uncompiled(); + report.HANDLER(display_total_).expr.mark_uncompiled(); + report.HANDLER(revalued_total_).expr.mark_uncompiled(); + + scoped_ptr<accounts_iterator> iter; + if (! report.HANDLED(sort_)) { + iter.reset(new basic_accounts_iterator(*report.session.journal->master)); + } else { + expr_t sort_expr(report.HANDLER(sort_).str()); + sort_expr.set_context(&report); + iter.reset(new sorted_accounts_iterator(*report.session.journal->master, + sort_expr, report.HANDLED(flat))); + } + + if (report.HANDLED(display_)) { + DEBUG("report.predicate", + "Display predicate = " << report.HANDLER(display_).str()); + pass_down_accounts(handler, *iter.get(), + predicate_t(report.HANDLER(display_).str(), + report.what_to_keep()), + report); + } else { + pass_down_accounts(handler, *iter.get()); + } + + report.session.journal->clear_xdata(); + } + }; +} + void report_t::accounts_report(acct_handler_ptr handler) { - journal_posts_iterator walker(*session.journal.get()); - - // The lifetime of the chain object controls the lifetime of all temporary - // objects created within it during the call to pass_down_posts, which will - // be needed later by the pass_down_accounts. post_handler_ptr chain = - chain_post_handlers(*this, post_handler_ptr(new ignore_posts), true); - pass_down_posts(chain, walker); + chain_post_handlers(*this, post_handler_ptr(new ignore_posts), + /* for_accounts_report= */ true); + if (HANDLED(group_by_)) { + std::auto_ptr<post_splitter> + splitter(new post_splitter(*this, chain, HANDLER(group_by_).expr)); - HANDLER(amount_).expr.mark_uncompiled(); - HANDLER(total_).expr.mark_uncompiled(); - HANDLER(display_amount_).expr.mark_uncompiled(); - HANDLER(display_total_).expr.mark_uncompiled(); - HANDLER(revalued_total_).expr.mark_uncompiled(); + splitter->set_preflush_func(accounts_title_printer(*this, handler)); + splitter->set_postflush_func(accounts_flusher(*this, handler)); - scoped_ptr<accounts_iterator> iter; - if (! HANDLED(sort_)) { - iter.reset(new basic_accounts_iterator(*session.journal->master)); - } else { - expr_t sort_expr(HANDLER(sort_).str()); - sort_expr.set_context(this); - iter.reset(new sorted_accounts_iterator(*session.journal->master, - sort_expr, HANDLED(flat))); + chain = post_handler_ptr(splitter.release()); } + chain = chain_pre_post_handlers(*this, chain); - if (HANDLED(display_)) { - DEBUG("report.predicate", - "Display predicate = " << HANDLER(display_).str()); - pass_down_accounts(handler, *iter.get(), - predicate_t(HANDLER(display_).str(), what_to_keep()), - *this); - } else { - pass_down_accounts(handler, *iter.get()); - } + // The lifetime of the chain object controls the lifetime of all temporary + // objects created within it during the call to pass_down_posts, which will + // be needed later by the pass_down_accounts. + journal_posts_iterator walker(*session.journal.get()); + pass_down_posts(chain, walker); - session.journal->clear_xdata(); + if (! HANDLED(group_by_)) + accounts_flusher(*this, handler)(value_t()); } void report_t::commodities_report(post_handler_ptr handler) { + handler = chain_handlers(*this, handler); + posts_commodities_iterator walker(*session.journal.get()); - pass_down_posts(chain_post_handlers(*this, handler), walker); + pass_down_posts(handler, walker); + session.journal->clear_xdata(); } @@ -853,6 +935,7 @@ option_t<report_t> * report_t::lookup_option(const char * p) else OPT(columns_); else OPT_ALT(basis, cost); else OPT_(current); + else OPT(count); break; case 'd': OPT(daily); @@ -886,6 +969,8 @@ option_t<report_t> * report_t::lookup_option(const char * p) break; case 'g': OPT(gain); + else OPT(group_by_); + else OPT(group_title_format_); break; case 'h': OPT(head_); @@ -914,6 +999,8 @@ option_t<report_t> * report_t::lookup_option(const char * p) case 'n': OPT_CH(collapse); else OPT(no_color); + else OPT(no_rounding); + else OPT(no_titles); else OPT(no_total); else OPT(now_); break; @@ -936,6 +1023,7 @@ option_t<report_t> * report_t::lookup_option(const char * p) else OPT(pricedb_format_); else OPT(payee_width_); else OPT(prepend_format_); + else OPT(prepend_width_); else OPT(print_virtual); break; case 'q': @@ -1222,12 +1310,19 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, case symbol_t::COMMAND: switch (*p) { + case 'a': + if (is_eq(p, "accounts")) + return WRAP_FUNCTOR(reporter<>(new report_accounts(*this), *this, + "#accounts")); + break; + case 'b': if (*(p + 1) == '\0' || is_eq(p, "bal") || is_eq(p, "balance")) { return expr_t::op_t::wrap_functor (reporter<account_t, acct_handler_ptr, &report_t::accounts_report> (new format_accounts(*this, report_format(HANDLER(balance_format_)), - maybe_format(HANDLER(prepend_format_))), + maybe_format(HANDLER(prepend_format_)), + HANDLER(prepend_width_).value.to_long()), *this, "#balance")); } else if (is_eq(p, "budget")) { @@ -1240,7 +1335,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return expr_t::op_t::wrap_functor (reporter<account_t, acct_handler_ptr, &report_t::accounts_report> (new format_accounts(*this, report_format(HANDLER(budget_format_)), - maybe_format(HANDLER(prepend_format_))), + maybe_format(HANDLER(prepend_format_)), + HANDLER(prepend_width_).value.to_long()), *this, "#budget")); } break; @@ -1250,7 +1346,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return WRAP_FUNCTOR (reporter<> (new format_posts(*this, report_format(HANDLER(csv_format_)), - maybe_format(HANDLER(prepend_format_))), + maybe_format(HANDLER(prepend_format_)), + HANDLER(prepend_width_).value.to_long()), *this, "#csv")); } else if (is_eq(p, "cleared")) { @@ -1259,11 +1356,17 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return expr_t::op_t::wrap_functor (reporter<account_t, acct_handler_ptr, &report_t::accounts_report> (new format_accounts(*this, report_format(HANDLER(cleared_format_)), - maybe_format(HANDLER(prepend_format_))), + maybe_format(HANDLER(prepend_format_)), + HANDLER(prepend_width_).value.to_long()), *this, "#cleared")); } - else if (is_eq(p, "convert")) + else if (is_eq(p, "convert")) { return WRAP_FUNCTOR(convert_command); + } + else if (is_eq(p, "commodities")) { + return WRAP_FUNCTOR(reporter<>(new report_commodities(*this), *this, + "#commodities")); + } break; case 'e': @@ -1288,14 +1391,19 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return expr_t::op_t::wrap_functor (reporter<post_t, post_handler_ptr, &report_t::commodities_report> (new format_posts(*this, report_format(HANDLER(prices_format_)), - maybe_format(HANDLER(prepend_format_))), + maybe_format(HANDLER(prepend_format_)), + HANDLER(prepend_width_).value.to_long()), *this, "#prices")); else if (is_eq(p, "pricedb")) return expr_t::op_t::wrap_functor (reporter<post_t, post_handler_ptr, &report_t::commodities_report> (new format_posts(*this, report_format(HANDLER(pricedb_format_)), - maybe_format(HANDLER(prepend_format_))), + maybe_format(HANDLER(prepend_format_)), + HANDLER(prepend_width_).value.to_long()), *this, "#pricedb")); + else if (is_eq(p, "payees")) + return WRAP_FUNCTOR(reporter<>(new report_payees(*this), *this, + "#payees")); break; case 'r': @@ -1303,7 +1411,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return WRAP_FUNCTOR (reporter<> (new format_posts(*this, report_format(HANDLER(register_format_)), - maybe_format(HANDLER(prepend_format_))), + maybe_format(HANDLER(prepend_format_)), + HANDLER(prepend_width_).value.to_long()), *this, "#register")); else if (is_eq(p, "reload")) return MAKE_FUNCTOR(report_t::reload_command); diff --git a/src/report.h b/src/report.h index 6b10dbcc..6fa238f0 100644 --- a/src/report.h +++ b/src/report.h @@ -256,6 +256,8 @@ public: HANDLER(forecast_years_).report(out); HANDLER(format_).report(out); HANDLER(gain).report(out); + HANDLER(group_by_).report(out); + HANDLER(group_title_format_).report(out); HANDLER(head_).report(out); HANDLER(invert).report(out); HANDLER(limit_).report(out); @@ -267,6 +269,8 @@ public: HANDLER(market).report(out); HANDLER(meta_).report(out); HANDLER(monthly).report(out); + HANDLER(no_rounding).report(out); + HANDLER(no_titles).report(out); HANDLER(no_total).report(out); HANDLER(now_).report(out); HANDLER(only_).report(out); @@ -280,6 +284,7 @@ public: HANDLER(plot_amount_format_).report(out); HANDLER(plot_total_format_).report(out); HANDLER(prepend_format_).report(out); + HANDLER(prepend_width_).report(out); HANDLER(price).report(out); HANDLER(prices_format_).report(out); HANDLER(pricedb_format_).report(out); @@ -372,7 +377,7 @@ public: OPTION__(report_t, balance_format_, CTOR(report_t, balance_format_) { on(none, - "%(justify(scrub(display_total), 20, -1, true, color))" + "%(justify(scrub(display_total), 20, 20 + prepend_width, true, color))" " %(!options.flat ? depth_spacer : \"\")" "%-(ansify_if(partial_account(options.flat), blue if color))\n%/" "%$1\n%/" @@ -432,8 +437,9 @@ public: OPTION__(report_t, cleared_format_, CTOR(report_t, cleared_format_) { on(none, - "%(justify(scrub(get_at(total_expr, 0)), 16, -1, true, color))" - " %(justify(scrub(get_at(total_expr, 1)), 16, -1, true, color))" + "%(justify(scrub(get_at(total_expr, 0)), 16, 16 + prepend_width, " + " true, color)) %(justify(scrub(get_at(total_expr, 1)), 18, " + " 36 + prepend_width, true, color))" " %(latest_cleared ? format_date(latest_cleared) : \" \")" " %(!options.flat ? depth_spacer : \"\")" "%-(ansify_if(partial_account(options.flat), blue if color))\n%/" @@ -454,15 +460,17 @@ public: }); OPTION(report_t, columns_); + OPTION(report_t, count); OPTION__(report_t, csv_format_, CTOR(report_t, csv_format_) { on(none, "%(quoted(date))," + "%(quoted(code))," "%(quoted(payee))," - "%(quoted(account))," - "%(quoted(scrub(display_amount)))," + "%(quoted(display_account))," + "%(quoted(commodity))," + "%(quoted(quantity(scrub(display_amount))))," "%(quoted(cleared ? \"*\" : (pending ? \"!\" : \"\")))," - "%(quoted(code))," "%(quoted(join(note | xact.note)))\n"); }); @@ -590,6 +598,22 @@ public: " - get_at(total_expr, 1)"); }); + OPTION__ + (report_t, group_by_, + expr_t expr; + CTOR(report_t, group_by_) {} + void set_expr(const optional<string>& whence, const string& str) { + expr = str; + on(whence, str); + } + DO_(args) { + set_expr(args[0].to_string(), args[1].to_string()); + }); + + OPTION__(report_t, group_title_format_, CTOR(report_t, group_title_format_) { + on(none, "%(value)\n"); + }); + OPTION(report_t, head_); OPTION_(report_t, invert, DO() { @@ -632,6 +656,8 @@ public: parent->HANDLER(color).off(); }); + OPTION(report_t, no_rounding); + OPTION(report_t, no_titles); OPTION(report_t, no_total); OPTION_(report_t, now_, DO_(args) { @@ -735,6 +761,9 @@ public: }); OPTION(report_t, prepend_format_); + OPTION_(report_t, prepend_width_, DO_(args) { + value = args[1].to_long(); + }); OPTION_(report_t, price, DO() { // -I parent->HANDLER(display_amount_) @@ -745,13 +774,13 @@ public: OPTION__(report_t, prices_format_, CTOR(report_t, prices_format_) { on(none, - "%(date) %-8(account) %(justify(scrub(display_amount), 12, " + "%(date) %-8(display_account) %(justify(scrub(display_amount), 12, " " 2 + 9 + 8 + 12, true, color))\n"); }); OPTION__(report_t, pricedb_format_, CTOR(report_t, pricedb_format_) { on(none, - "P %(datetime) %(account) %(scrub(display_amount))\n"); + "P %(datetime) %(display_account) %(scrub(display_amount))\n"); }); OPTION(report_t, print_virtual); @@ -778,14 +807,14 @@ public: " if color & date > today))" " %(ansify_if(justify(truncated(payee, payee_width), payee_width), " " bold if color & !cleared & actual))" - " %(ansify_if(justify(truncated(account, account_width, abbrev_len), " - " account_width), blue if color))" + " %(ansify_if(justify(truncated(display_account, account_width, " + " abbrev_len), account_width), blue if color))" " %(justify(scrub(display_amount), amount_width, " " 3 + meta_width + date_width + payee_width + account_width" - " + amount_width, true, color))" + " + amount_width + prepend_width, true, color))" " %(justify(scrub(display_total), total_width, " " 4 + meta_width + date_width + payee_width + account_width" - " + amount_width + total_width, true, color))\n%/" + " + amount_width + total_width + prepend_width, true, color))\n%/" "%(justify(\" \", 2 + date_width + payee_width))" "%$3 %$4 %$5\n"); }); diff --git a/src/scope.h b/src/scope.h index 30ba6823..1e6f24a1 100644 --- a/src/scope.h +++ b/src/scope.h @@ -354,6 +354,30 @@ inline T& find_scope(child_scope_t& scope, bool skip_this = true) return reinterpret_cast<T&>(scope); // never executed } +class value_scope_t : public scope_t +{ + value_t value; + + value_t get_value(call_scope_t&) { + return value; + } + +public: + value_scope_t(const value_t& _value) : value(_value) {} + + virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind, + const string& name) + { + if (kind != symbol_t::FUNCTION) + return NULL; + + if (name == "value") + return MAKE_FUNCTOR(value_scope_t::get_value); + + return NULL; + } +}; + } // namespace ledger #endif // _SCOPE_H diff --git a/src/session.cc b/src/session.cc index 8e5536b0..f8befde4 100644 --- a/src/session.cc +++ b/src/session.cc @@ -196,9 +196,7 @@ option_t<session_t> * session_t::lookup_option(const char * p) break; case 'd': OPT(download); // -Q - break; - case 'e': - OPT(european); + else OPT(decimal_comma); break; case 'f': OPT_(file_); // -f diff --git a/src/session.h b/src/session.h index de1771ad..10f636bb 100644 --- a/src/session.h +++ b/src/session.h @@ -80,7 +80,7 @@ public: { HANDLER(cache_).report(out); HANDLER(download).report(out); - HANDLER(european).report(out); + HANDLER(decimal_comma).report(out); HANDLER(file_).report(out); HANDLER(input_date_format_).report(out); HANDLER(master_account_).report(out); @@ -101,8 +101,8 @@ public: OPTION(session_t, cache_); OPTION(session_t, download); // -Q - OPTION_(session_t, european, DO() { - commodity_t::european_by_default = true; + OPTION_(session_t, decimal_comma, DO() { + commodity_t::decimal_comma_by_default = true; }); OPTION__ diff --git a/src/temps.cc b/src/temps.cc index 68b9ffa0..7a630176 100644 --- a/src/temps.cc +++ b/src/temps.cc @@ -38,26 +38,6 @@ namespace ledger { -temporaries_t::~temporaries_t() -{ - if (post_temps) { - foreach (post_t& post, *post_temps) { - if (! post.xact->has_flags(ITEM_TEMP)) - post.xact->remove_post(&post); - - if (post.account && ! post.account->has_flags(ACCOUNT_TEMP)) - post.account->remove_post(&post); - } - } - - if (acct_temps) { - foreach (account_t& acct, *acct_temps) { - if (acct.parent && ! acct.parent->has_flags(ACCOUNT_TEMP)) - acct.parent->remove_account(&acct); - } - } -} - xact_t& temporaries_t::copy_xact(xact_t& origin) { if (! xact_temps) @@ -91,9 +71,9 @@ post_t& temporaries_t::copy_post(post_t& origin, xact_t& xact, post_temps->push_back(origin); post_t& temp(post_temps->back()); + temp.add_flags(ITEM_TEMP); if (account) temp.account = account; - temp.add_flags(ITEM_TEMP); temp.account->add_post(&temp); xact.add_post(&temp); @@ -109,8 +89,8 @@ post_t& temporaries_t::create_post(xact_t& xact, account_t * account) post_temps->push_back(post_t(account)); post_t& temp(post_temps->back()); - temp.account = account; temp.add_flags(ITEM_TEMP); + temp.account = account; temp.account->add_post(&temp); xact.add_post(&temp); @@ -127,11 +107,36 @@ account_t& temporaries_t::create_account(const string& name, acct_temps->push_back(account_t(parent, name)); account_t& temp(acct_temps->back()); + temp.add_flags(ACCOUNT_TEMP); if (parent) parent->add_account(&temp); - temp.add_flags(ACCOUNT_TEMP); return temp; } +void temporaries_t::clear() +{ + if (post_temps) { + foreach (post_t& post, *post_temps) { + if (! post.xact->has_flags(ITEM_TEMP)) + post.xact->remove_post(&post); + + if (post.account && ! post.account->has_flags(ACCOUNT_TEMP)) + post.account->remove_post(&post); + } + post_temps->clear(); + } + + if (xact_temps) + xact_temps->clear(); + + if (acct_temps) { + foreach (account_t& acct, *acct_temps) { + if (acct.parent && ! acct.parent->has_flags(ACCOUNT_TEMP)) + acct.parent->remove_account(&acct); + } + acct_temps->clear(); + } +} + } // namespace ledger diff --git a/src/temps.h b/src/temps.h index ac6d08cd..210bbf63 100644 --- a/src/temps.h +++ b/src/temps.h @@ -51,7 +51,9 @@ class temporaries_t optional<std::list<account_t> > acct_temps; public: - ~temporaries_t(); + ~temporaries_t() { + clear(); + } xact_t& copy_xact(xact_t& origin); xact_t& create_xact(); @@ -69,6 +71,8 @@ public: account_t& last_account() { return acct_temps->back(); } + + void clear(); }; } // namespace ledger diff --git a/src/textual.cc b/src/textual.cc index dfca7943..9a49edd4 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -95,7 +95,6 @@ namespace { public: parse_context_t& context; instance_t * parent; - account_t * master; accounts_map account_aliases; const path * original_file; path pathname; @@ -109,7 +108,6 @@ namespace { instance_t(parse_context_t& _context, std::istream& _in, - account_t * _master = NULL, const path * _original_file = NULL, instance_t * _parent = NULL); @@ -200,30 +198,17 @@ namespace { instance_t::instance_t(parse_context_t& _context, std::istream& _in, - account_t * _master, const path * _original_file, instance_t * _parent) - : context(_context), parent(_parent), master(_master), - original_file(_original_file), in(_in) + : context(_context), parent(_parent), original_file(_original_file), + pathname(original_file ? *original_file : "/dev/stdin"), in(_in) { TRACE_CTOR(instance_t, "..."); - - if (! master) - master = context.journal.master; - context.state_stack.push_front(master); - - if (_original_file) - pathname = *_original_file; - else - pathname = "/dev/stdin"; } instance_t::~instance_t() { TRACE_DTOR(instance_t); - - assert(! context.state_stack.empty()); - context.state_stack.pop_front(); } void instance_t::parse() @@ -411,8 +396,7 @@ void instance_t::read_next_directive() #if defined(TIMELOG_SUPPORT) -void instance_t::clock_in_directive(char * line, - bool /*capitalized*/) +void instance_t::clock_in_directive(char * line, bool /*capitalized*/) { string datetime(line, 2, 19); @@ -441,8 +425,7 @@ void instance_t::clock_in_directive(char * line, context.timelog.clock_in(event); } -void instance_t::clock_out_directive(char * line, - bool /*capitalized*/) +void instance_t::clock_out_directive(char * line, bool /*capitalized*/) { string datetime(line, 2, 19); @@ -543,7 +526,7 @@ void instance_t::automated_xact_directive(char * line) std::auto_ptr<auto_xact_t> ae (new auto_xact_t(query_t(string(skip_ws(line + 1)), - keep_details_t(true, true, true)))); + keep_details_t(true, true, true), false))); ae->pos = position_t(); ae->pos->pathname = pathname; ae->pos->beg_pos = line_beg_pos; @@ -680,7 +663,12 @@ void instance_t::include_directive(char * line) for (filesystem::directory_iterator iter(parent_path); iter != end; ++iter) { - if (is_regular_file(*iter)) { +#if BOOST_VERSION <= 103500 + if (is_regular(*iter)) +#else + if (is_regular_file(*iter)) +#endif + { #if BOOST_VERSION >= 103700 string base = (*iter).filename(); #else // BOOST_VERSION >= 103700 @@ -689,7 +677,7 @@ void instance_t::include_directive(char * line) if (glob.match(base)) { path inner_file(*iter); ifstream stream(inner_file); - instance_t instance(context, stream, master, &inner_file, this); + instance_t instance(context, stream, &inner_file, this); instance.parse(); files_found = true; } @@ -713,7 +701,7 @@ void instance_t::master_account_directive(char * line) void instance_t::end_directive(char * kind) { - string name(kind); + string name(kind ? kind : ""); if ((name.empty() || name == "account") && ! context.front_is_account()) throw_(std::runtime_error, @@ -1451,8 +1439,10 @@ std::size_t journal_t::parse(std::istream& in, parse_context_t context(*this, scope); context.strict = strict; + if (master || this->master) + context.state_stack.push_front(master ? master : this->master); - instance_t instance(context, in, master, original_file); + instance_t instance(context, in, original_file); instance.parse(); TRACE_STOP(parsing_total, 1); diff --git a/src/times.cc b/src/times.cc index be488baf..a7906aee 100644 --- a/src/times.cc +++ b/src/times.cc @@ -197,25 +197,33 @@ namespace { optional_year year, date_traits_t * traits = NULL) { - date_t when; + VERIFY(std::strlen(date_str) < 127); - if (std::strchr(date_str, '/')) { - when = io.parse(date_str); - } else { - char buf[128]; - VERIFY(std::strlen(date_str) < 127); - std::strcpy(buf, date_str); + char buf[128]; + std::strcpy(buf, date_str); - for (char * p = buf; *p; p++) - if (*p == '.' || *p == '-') - *p = '/'; + for (char * p = buf; *p; p++) + if (*p == '.' || *p == '-') + *p = '/'; - when = io.parse(buf); - } + date_t when = io.parse(buf); if (! when.is_not_a_date()) { - DEBUG("times.parse", "Parsed date string: " << date_str); - DEBUG("times.parse", "Parsed result is: " << when); + DEBUG("times.parse", "Passed date string: " << date_str); + DEBUG("times.parse", "Parsed date string: " << buf); + DEBUG("times.parse", "Parsed result is: " << when); + DEBUG("times.parse", "Formatted result is: " << io.format(when)); + + string when_str = io.format(when); + + const char * p = when_str.c_str(); + const char * q = buf; + for (; *p && *q; p++, q++) { + if (*p != *q && *p == '0') p++; + if (! *p || *p != *q) break; + } + if (*p != '\0' || *q != '\0') + throw_(date_error, _("Invalid date: %1") << date_str); if (traits) *traits = io.traits; @@ -1299,14 +1307,14 @@ date_parser_t::lexer_t::token_t date_parser_t::lexer_t::next_token() // "2009/08/01", but also dates that fit the user's --input-date-format, // assuming their format fits in one argument and begins with a digit. if (std::isdigit(*begin)) { - try { - string::const_iterator i = begin; - for (i = begin; i != end && ! std::isspace(*i); i++) {} - assert(i != begin); + string::const_iterator i = begin; + for (i = begin; i != end && ! std::isspace(*i); i++) {} + assert(i != begin); - string possible_date(start, i); - date_traits_t traits; + string possible_date(start, i); + try { + date_traits_t traits; date_t when = parse_date_mask(possible_date.c_str(), none, &traits); if (! when.is_not_a_date()) { begin = i; @@ -1314,7 +1322,12 @@ date_parser_t::lexer_t::token_t date_parser_t::lexer_t::next_token() token_t::content_t(date_specifier_t(when, traits))); } } - catch (...) {} + catch (date_error&) { + if (contains(possible_date, "/") || + contains(possible_date, "-") || + contains(possible_date, ".")) + throw; + } } start = begin; @@ -1457,7 +1470,7 @@ std::string format_datetime(const datetime_t& when, if (format_type == FMT_WRITTEN) { return written_datetime_io->format(when); } - else if (format_type == FMT_CUSTOM || format) { + else if (format_type == FMT_CUSTOM && format) { datetime_io_map::iterator i = temp_datetime_io.find(*format); if (i != temp_datetime_io.end()) { return (*i).second->format(when); @@ -1483,7 +1496,7 @@ std::string format_date(const date_t& when, if (format_type == FMT_WRITTEN) { return written_date_io->format(when); } - else if (format_type == FMT_CUSTOM || format) { + else if (format_type == FMT_CUSTOM && format) { date_io_map::iterator i = temp_date_io.find(*format); if (i != temp_date_io.end()) { return (*i).second->format(when); diff --git a/src/token.cc b/src/token.cc index a34cdcd0..add97b8b 100644 --- a/src/token.cc +++ b/src/token.cc @@ -138,7 +138,8 @@ void expr_t::token_t::parse_ident(std::istream& in) value.set_string(buf); } -void expr_t::token_t::next(std::istream& in, const parse_flags_t& pflags) +void expr_t::token_t::next(std::istream& in, const parse_flags_t& pflags, + const char expecting) { if (in.eof()) { kind = TOK_EOF; @@ -423,6 +424,13 @@ void expr_t::token_t::next(std::istream& in, const parse_flags_t& pflags) expected('\0', c); parse_ident(in); + + if (value.as_string().length() == 0) { + kind = ERROR; + symbol[0] = c; + symbol[1] = '\0'; + unexpected(expecting); + } } else { kind = VALUE; value = temp; @@ -447,21 +455,38 @@ void expr_t::token_t::rewind(std::istream& in) } -void expr_t::token_t::unexpected() +void expr_t::token_t::unexpected(const char wanted) { kind_t prev_kind = kind; kind = ERROR; - switch (prev_kind) { - case TOK_EOF: - throw_(parse_error, _("Unexpected end of expression")); - case IDENT: - throw_(parse_error, _("Unexpected symbol '%1'") << value); - case VALUE: - throw_(parse_error, _("Unexpected value '%1'") << value); - default: - throw_(parse_error, _("Unexpected token '%1'") << symbol); + if (wanted == '\0') { + switch (prev_kind) { + case TOK_EOF: + throw_(parse_error, _("Unexpected end of expression")); + case IDENT: + throw_(parse_error, _("Unexpected symbol '%1'") << value); + case VALUE: + throw_(parse_error, _("Unexpected value '%1'") << value); + default: + throw_(parse_error, _("Unexpected expression token '%1'") << symbol); + } + } else { + switch (prev_kind) { + case TOK_EOF: + throw_(parse_error, + _("Unexpected end of expression (wanted '%1')" << wanted)); + case IDENT: + throw_(parse_error, + _("Unexpected symbol '%1' (wanted '%2')") << value << wanted); + case VALUE: + throw_(parse_error, + _("Unexpected value '%1' (wanted '%2')") << value << wanted); + default: + throw_(parse_error, _("Unexpected expression token '%1' (wanted '%2')") + << symbol << wanted); + } } } diff --git a/src/token.h b/src/token.h index 8d70996b..582373cb 100644 --- a/src/token.h +++ b/src/token.h @@ -124,10 +124,11 @@ struct expr_t::token_t : public noncopyable int parse_reserved_word(std::istream& in); void parse_ident(std::istream& in); - void next(std::istream& in, const parse_flags_t& flags); + void next(std::istream& in, const parse_flags_t& flags, + const char expecting = '\0'); void rewind(std::istream& in); - void unexpected(); - void expected(char wanted, char c = '\0'); + void unexpected(const char wanted = '\0'); + void expected(const char wanted, char c = '\0'); }; } // namespace ledger diff --git a/src/value.cc b/src/value.cc index ce9b0e6a..a967eeb8 100644 --- a/src/value.cc +++ b/src/value.cc @@ -832,7 +832,7 @@ bool value_t::is_equal_to(const value_t& val) const break; } - throw_(value_error, _("Cannot compare %1 by %2") << label() << val.label()); + throw_(value_error, _("Cannot compare %1 to %2") << label() << val.label()); return *this; } @@ -840,6 +840,23 @@ bool value_t::is_equal_to(const value_t& val) const bool value_t::is_less_than(const value_t& val) const { switch (type()) { + case BOOLEAN: + if (val.is_boolean()) { + if (as_boolean()) { + if (! val.as_boolean()) + return false; + else + return false; + } + else if (! as_boolean()) { + if (! val.as_boolean()) + return false; + else + return true; + } + } + break; + case DATETIME: if (val.is_datetime()) return as_datetime() < val.as_datetime(); @@ -935,6 +952,22 @@ bool value_t::is_less_than(const value_t& val) const bool value_t::is_greater_than(const value_t& val) const { switch (type()) { + if (val.is_boolean()) { + if (as_boolean()) { + if (! val.as_boolean()) + return true; + else + return false; + } + else if (! as_boolean()) { + if (! val.as_boolean()) + return false; + else + return false; + } + } + break; + case DATETIME: if (val.is_datetime()) return as_datetime() > val.as_datetime(); @@ -1042,8 +1075,27 @@ void value_t::in_place_cast(type_t cast_type) } switch (type()) { + case VOID: + switch (cast_type) { + case INTEGER: + set_long(0L); + return; + case AMOUNT: + set_amount(0L); + return; + case STRING: + set_string(""); + return; + default: + break; + } + break; + case BOOLEAN: switch (cast_type) { + case INTEGER: + set_long(as_boolean() ? 1L : 0L); + return; case AMOUNT: set_amount(as_boolean() ? 1L : 0L); return; diff --git a/src/xact.cc b/src/xact.cc index 344f66ea..569e5869 100644 --- a/src/xact.cc +++ b/src/xact.cc @@ -172,44 +172,8 @@ bool xact_base_t::finalize() add_post(null_post); } - if (null_post != NULL) { - // If one post has no value at all, its value will become the inverse of - // the rest. If multiple commodities are involved, multiple posts are - // generated to balance them all. - - DEBUG("xact.finalize", "there was a null posting"); - - if (balance.is_balance()) { - bool first = true; - const balance_t& bal(balance.as_balance()); - foreach (const balance_t::amounts_map::value_type& pair, bal.amounts) { - if (first) { - null_post->amount = pair.second.negated(); - null_post->add_flags(POST_CALCULATED); - first = false; - } else { - post_t * p = new post_t(null_post->account, pair.second.negated(), - ITEM_GENERATED | POST_CALCULATED); - p->set_state(null_post->state()); - add_post(p); - } - } - } - else if (balance.is_amount()) { - null_post->amount = balance.as_amount().negated(); - null_post->add_flags(POST_CALCULATED); - } - else if (balance.is_long()) { - null_post->amount = amount_t(- balance.as_long()); - null_post->add_flags(POST_CALCULATED); - } - else if (! balance.is_null() && ! balance.is_realzero()) { - throw_(balance_error, _("Transaction does not balance")); - } - balance = NULL_VALUE; - } - else if (balance.is_balance() && - balance.as_balance().amounts.size() == 2) { + if (balance.is_balance() && + balance.as_balance().amounts.size() == 2) { // When an xact involves two different commodities (regardless of how // many posts there are) determine the conversion ratio by dividing the // total value of one commodity by the total value of the other. This @@ -293,12 +257,6 @@ bool xact_base_t::finalize() } } - // Now that the post list has its final form, calculate the balance once - // more in terms of total cost, accounting for any possible gain/loss - // amounts. - - DEBUG("xact.finalize", "resolved balance = " << balance); - posts_list copy(posts); foreach (post_t * post, copy) { @@ -340,7 +298,44 @@ bool xact_base_t::finalize() } } - DEBUG("xact.finalize", "final balance = " << balance); + if (null_post != NULL) { + // If one post has no value at all, its value will become the inverse of + // the rest. If multiple commodities are involved, multiple posts are + // generated to balance them all. + + DEBUG("xact.finalize", "there was a null posting"); + + if (balance.is_balance()) { + bool first = true; + const balance_t& bal(balance.as_balance()); + foreach (const balance_t::amounts_map::value_type& pair, bal.amounts) { + if (first) { + null_post->amount = pair.second.negated(); + null_post->add_flags(POST_CALCULATED); + first = false; + } else { + post_t * p = new post_t(null_post->account, pair.second.negated(), + ITEM_GENERATED | POST_CALCULATED); + p->set_state(null_post->state()); + add_post(p); + } + } + } + else if (balance.is_amount()) { + null_post->amount = balance.as_amount().negated(); + null_post->add_flags(POST_CALCULATED); + } + else if (balance.is_long()) { + null_post->amount = amount_t(- balance.as_long()); + null_post->add_flags(POST_CALCULATED); + } + else if (! balance.is_null() && ! balance.is_realzero()) { + throw_(balance_error, _("Transaction does not balance")); + } + balance = NULL_VALUE; + + } + DEBUG("xact.finalize", "resolved balance = " << balance); if (! balance.is_null() && ! balance.is_zero()) { add_error_context(item_context(*this, _("While balancing transaction"))); @@ -473,7 +468,7 @@ namespace { if (xact.code) return string_value(*xact.code); else - return string_value(empty_string); + return NULL_VALUE; } value_t get_payee(xact_t& xact) { @@ -716,8 +711,14 @@ void auto_xact_t::extend_xact(xact_base_t& xact) account_t * account = post->account; string fullname = account->fullname(); assert(! fullname.empty()); - if (fullname == "$account" || fullname == "@account") - account = initial_post->account; + + if (contains(fullname, "$account")) { + fullname = regex_replace(fullname, regex("\\$account\\>"), + initial_post->account->fullname()); + while (account->parent) + account = account->parent; + account = account->find_account(fullname); + } // Copy over details so that the resulting post is a mirror of // the automated xact's one. @@ -83,6 +83,14 @@ public: virtual void flush(); virtual void operator()(post_t& post); + + virtual void clear() { + commodities.clear(); + transactions_set.clear(); + transactions.clear(); + + item_handler<post_t>::clear(); + } }; } // namespace ledger diff --git a/test/ConfirmTests.py b/test/ConfirmTests.py index 901ae7cd..6fc04336 100755 --- a/test/ConfirmTests.py +++ b/test/ConfirmTests.py @@ -84,7 +84,7 @@ def confirm_report(command): return not failure for cmd in commands: - if confirm_report('$ledger $cmd ' + re.sub('\$tests', tests, cmd)): + if confirm_report('$ledger --rounding $cmd ' + re.sub('\$tests', tests, cmd)): harness.success() else: harness.failure() diff --git a/test/baseline/cmd-print.test b/test/baseline/cmd-print.test new file mode 100644 index 00000000..6099b39f --- /dev/null +++ b/test/baseline/cmd-print.test @@ -0,0 +1,11 @@ +print --decimal-comma +<<< +2008/12/31 Market + Expenses:Food ($10,00 + $2,50) + Assets:Cash +>>>1 +2008/12/31 Market + Expenses:Food ($10,00 + $2,50) + Assets:Cash +>>>2 +=== 0 diff --git a/test/baseline/opt-code-as-payee.test b/test/baseline/opt-code-as-payee.test index c2988626..99aa182e 100644 --- a/test/baseline/opt-code-as-payee.test +++ b/test/baseline/opt-code-as-payee.test @@ -28,7 +28,7 @@ reg --payee=code 08-Feb-01 102 Assets:Cash $-20.00 0 08-Feb-28 103 Expenses:Books $20.00 $20.00 08-Feb-28 103 Assets:Cash $-20.00 0 -08-Mar-01 Expenses:Books $30.00 $30.00 -08-Mar-01 Assets:Cash $-30.00 0 +08-Mar-01 March Expenses:Books $30.00 $30.00 +08-Mar-01 March Assets:Cash $-30.00 0 >>>2 === 0 diff --git a/test/baseline/opt-gain.test b/test/baseline/opt-gain.test index 8aeb8bab..63055d5f 100644 --- a/test/baseline/opt-gain.test +++ b/test/baseline/opt-gain.test @@ -50,7 +50,8 @@ P 2010/03/01 00:00:00 S 8 P P 2010/04/01 00:00:00 S 16 P >>>1 09-Jan-01 Sample 1a As:Brokerage:Stocks 0 0 -09-Feb-01 Commodities revalued <Revalued> 300 P 300 P +09-Jan-15 Commodities revalued <Revalued> 100 P 100 P +09-Feb-01 Commodities revalued <Revalued> 200 P 300 P 09-Feb-01 Sample 2a As:Brokerage:Stocks 300 P 600 P 09-Mar-01 Commodities revalued <Revalued> 800 P 1400 P 09-Mar-01 Sample 3a As:Brokerage:Stocks 700 P 2100 P diff --git a/test/baseline/opt-price.test b/test/baseline/opt-price.test index 06cc7751..133b2155 100644 --- a/test/baseline/opt-price.test +++ b/test/baseline/opt-price.test @@ -28,7 +28,8 @@ reg --end 2009/06/26 -V equities 08-Jan-01 Purchase Apple shares Equities $2000 $2000 08-Jun-30 Commodities revalued <Revalued> $500 $2500 08-Jun-30 Sell some Apple sha.. Equities $-1250 $1250 -09-Jun-26 Commodities revalued <Revalued> $750 $2000 +09-Jan-31 Commodities revalued <Revalued> $250 $1500 +09-Jun-26 Commodities revalued <Revalued> $500 $2000 >>>2 === 0 reg --end 2009/06/26 -G equities @@ -36,7 +37,8 @@ reg --end 2009/06/26 -G equities 08-Jan-01 Purchase Apple shares Equities 0 0 08-Jun-30 Commodities revalued <Revalued> $500 $500 08-Jun-30 Sell some Apple sha.. Equities 0 $500 -09-Jun-26 Commodities revalued <Revalued> $750 $1250 +09-Jan-31 Commodities revalued <Revalued> $250 $750 +09-Jun-26 Commodities revalued <Revalued> $500 $1250 >>>2 === 0 reg -I equities diff --git a/test/regress/25A099C9.test b/test/regress/25A099C9.test index 4067d005..a74b0601 100644 --- a/test/regress/25A099C9.test +++ b/test/regress/25A099C9.test @@ -4,16 +4,16 @@ >>>2 While parsing file "$sourcepath/src/amount.h", line 67: Error: No quantity specified for amount -While parsing file "$sourcepath/src/amount.h", line 720: +While parsing file "$sourcepath/src/amount.h", line 718: Error: Invalid date/time: line amount_t amoun -While parsing file "$sourcepath/src/amount.h", line 726: +While parsing file "$sourcepath/src/amount.h", line 724: Error: Invalid date/time: line string amount_ -While parsing file "$sourcepath/src/amount.h", line 732: +While parsing file "$sourcepath/src/amount.h", line 730: Error: Invalid date/time: line string amount_ -While parsing file "$sourcepath/src/amount.h", line 738: +While parsing file "$sourcepath/src/amount.h", line 736: Error: Invalid date/time: line string amount_ -While parsing file "$sourcepath/src/amount.h", line 744: +While parsing file "$sourcepath/src/amount.h", line 742: Error: Invalid date/time: line std::ostream& -While parsing file "$sourcepath/src/amount.h", line 751: +While parsing file "$sourcepath/src/amount.h", line 749: Error: Invalid date/time: line std::istream& === 7 diff --git a/test/regress/D943AE0F.test b/test/regress/D943AE0F.test index 94a26df5..7a2e14d8 100644 --- a/test/regress/D943AE0F.test +++ b/test/regress/D943AE0F.test @@ -1,4 +1,4 @@ -reg -V --end=2009/06/16 +reg -V <<< D 1000.00 EUR @@ -10,6 +10,6 @@ P 2008/04/20 00:00:00 CAD 1.20 EUR >>>1 08-Apr-15 Paid expenses back .. Ex:Cie-Reimbursements 2200.00 EUR 2200.00 EUR Assets:Checking -2200.00 EUR 0 -09-Jun-16 Commodities revalued <Revalued> 200.00 EUR 200.00 EUR +08-Apr-20 Commodities revalued <Revalued> 200.00 EUR 200.00 EUR >>>2 === 0 diff --git a/test/unit/t_amount.cc b/test/unit/t_amount.cc index 2c91ee98..63d82675 100644 --- a/test/unit/t_amount.cc +++ b/test/unit/t_amount.cc @@ -64,9 +64,9 @@ void AmountTestCase::testParser() x16.parse("$2000,00"); assertEqual(string("$2.000,00"), x16.to_string()); - // Since European-ness is an additive quality, we must switch back - // to American-ness manually - x15.commodity().drop_flags(COMMODITY_STYLE_EUROPEAN); + // Since use of a decimal-comma is an additive quality, we must switch back + // to decimal-period manually. + x15.commodity().drop_flags(COMMODITY_STYLE_DECIMAL_COMMA); amount_t x17("$1,000,000.00"); // parsing this switches back to American diff --git a/test/unit/t_expr.cc b/test/unit/t_expr.cc index b5865948..0d88be9e 100644 --- a/test/unit/t_expr.cc +++ b/test/unit/t_expr.cc @@ -158,8 +158,6 @@ void ValueExprTestCase::testPredicateTokenizer7() assertEqual(query_t::lexer_t::token_t::TOK_EQ, tokens.next_token().kind); assertEqual(query_t::lexer_t::token_t::TERM, tokens.next_token().kind); - assertEqual(query_t::lexer_t::token_t::TOK_AND, tokens.next_token().kind); - assertEqual(query_t::lexer_t::token_t::TERM, tokens.next_token().kind); assertEqual(query_t::lexer_t::token_t::END_REACHED, tokens.next_token().kind); #endif } @@ -167,7 +165,7 @@ void ValueExprTestCase::testPredicateTokenizer7() void ValueExprTestCase::testPredicateTokenizer8() { value_t args; - args.push_back(string_value("expr foo and bar")); + args.push_back(string_value("expr 'foo and bar'")); #ifndef NOT_FOR_PYTHON query_t::lexer_t tokens(args.begin(), args.end()); @@ -182,7 +180,7 @@ void ValueExprTestCase::testPredicateTokenizer9() { value_t args; args.push_back(string_value("expr")); - args.push_back(string_value("foo and bar")); + args.push_back(string_value("'foo and bar'")); #ifndef NOT_FOR_PYTHON query_t::lexer_t tokens(args.begin(), args.end()); diff --git a/tools/Makefile.am b/tools/Makefile.am index e821f150..b08d489d 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -249,11 +249,7 @@ endif ###################################################################### -TESTS = - -if HAVE_PYTHON -TESTS += RegressTests BaselineTests ManualTests ConfirmTests GenerateTests -endif +TESTS = RegressTests BaselineTests ManualTests ConfirmTests GenerateTests if HAVE_CPPUNIT TESTS += \ |