From 47bfe58ab39aa8d7ed4dc804409f1f78f485a5a6 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Tue, 18 May 2010 17:37:27 -0400 Subject: Added account_id and xact_id valexpr vars for posts account_id is the "whicheth" number for that posting within its account. The xact_id is within its transaction. --- src/post.cc | 36 ++++++++++++++++++++++++++++++++++++ src/post.h | 3 +++ 2 files changed, 39 insertions(+) diff --git a/src/post.cc b/src/post.cc index 183fb901..fb3281a7 100644 --- a/src/post.cc +++ b/src/post.cc @@ -142,6 +142,10 @@ namespace { return value_t(static_cast(post.xact)); } + value_t get_xact_id(post_t& post) { + return static_cast(post.xact_id()); + } + value_t get_code(post_t& post) { if (post.xact->code) return string_value(*post.xact->code); @@ -275,6 +279,10 @@ namespace { return string_value(name); } + value_t get_account_id(post_t& post) { + return static_cast(post.account_id()); + } + value_t get_account_base(post_t& post) { return string_value(post.reported_account()->name); } @@ -351,6 +359,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") @@ -444,6 +454,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 +491,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) { diff --git a/src/post.h b/src/post.h index 272cd9d8..226d6289 100644 --- a/src/post.h +++ b/src/post.h @@ -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 -- cgit v1.2.3 From 9061db8e47d06db8502febafd202e44b2f9213ba Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 22 May 2010 13:08:30 -0400 Subject: phase_patch in acprep is no longer needed This is because AM_SILENT_RULES is now used in configure.ac. --- acprep | 39 --------------------------------------- 1 file changed, 39 deletions(-) diff --git a/acprep b/acprep index c6baceca..d6df675e 100755 --- a/acprep +++ b/acprep @@ -1183,38 +1183,6 @@ class PrepareBuild(CommandLineApp): self.LDFLAGS.append('-g') 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 +1258,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: -- cgit v1.2.3 From 57abfd7ef8b4500b7a0c14d136d397ecf974163b Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 22 May 2010 13:08:53 -0400 Subject: Temporary accounts were referenced after deletion Fixes D53C98E5-506D-4CE5-91A3-7666FD33B65B --- src/account.cc | 18 +++++++++++++++++- src/temps.cc | 6 +++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/account.cc b/src/account.cc index e02d21d7..46f37091 100644 --- a/src/account.cc +++ b/src/account.cc @@ -43,8 +43,16 @@ account_t::~account_t() TRACE_DTOR(account_t); foreach (accounts_map::value_type& pair, accounts) - if (! pair.second->has_flags(ACCOUNT_TEMP)) + if (! pair.second->has_flags(ACCOUNT_TEMP) || + has_flags(ACCOUNT_TEMP)) checked_delete(pair.second); + + foreach (post_t * post, posts) { + if (post->account) { + assert(post->account == this); + post->account = NULL; + } + } } account_t * account_t::find_account(const string& name, @@ -79,6 +87,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 result = accounts.insert(accounts_map::value_type(first, account)); assert(result.second); diff --git a/src/temps.cc b/src/temps.cc index 68b9ffa0..dcaa9101 100644 --- a/src/temps.cc +++ b/src/temps.cc @@ -91,9 +91,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 +109,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,10 +127,10 @@ 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; } -- cgit v1.2.3 From 8d4de7783666e901538a0cbaa716400ef95e408d Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 22 May 2010 13:18:25 -0400 Subject: emacs command was referencing uninitialized values Fixes 7B54CF80-45A4-4D50-A8D3-63272D60FA1B --- src/emacs.cc | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) 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 << "\""; -- cgit v1.2.3 From 68056c194895f9943a8b8cbdccf52fa369322612 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 22 May 2010 13:23:38 -0400 Subject: Guard against NULL value passed to "end" directive Fixes 89233B6D-CB21-4162-98E3-BE38B9336070 --- src/textual.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual.cc b/src/textual.cc index dfca7943..d953da26 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -713,7 +713,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, -- cgit v1.2.3 From a596727d3d2cef76e7347f3695d5d04067b24d19 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 22 May 2010 14:49:46 -0400 Subject: Interpolate uses of $account in automated postings Fixes 5CB52887-408E-48F0-8798-3C640D0295B3 --- src/xact.cc | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/xact.cc b/src/xact.cc index 344f66ea..3c2505af 100644 --- a/src/xact.cc +++ b/src/xact.cc @@ -716,8 +716,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. -- cgit v1.2.3 From e3ba0117a39a3665743df5a1d5ca3b77ca2f00ec Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 22 May 2010 15:38:51 -0400 Subject: Updated NEWS file that there will be less news --- doc/NEWS | 37 ++++++++----------------------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/doc/NEWS b/doc/NEWS index b07fe2ec..d640f445 100644 --- a/doc/NEWS +++ b/doc/NEWS @@ -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 -- cgit v1.2.3 From de3803d0277353520116f05c7b2357196a8cfe48 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 22 May 2010 15:40:38 -0400 Subject: Added new commands: acounts, payees, commodities These three reports simply dump an unordered list (with the exception of payees) shows all accounts, payees, and commodities represented in a given report. This can be used to easily generate per-entity report, for example: ledger payees | \ while read payee; do \ echo ; echo $payee ; \ ledger reg payee "$payee" ; \ done --- src/output.cc | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/output.h | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/report.cc | 16 ++++++++++++++- 3 files changed, 144 insertions(+), 1 deletion(-) diff --git a/src/output.cc b/src/output.cc index 30775310..e3aa9f4a 100644 --- a/src/output.cc +++ b/src/output.cc @@ -232,4 +232,70 @@ 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) + out << *entry.first << '\n'; +} + +void report_accounts::operator()(post_t& post) +{ + std::map::iterator i = accounts.find(post.account); + if (i == accounts.end()) + accounts.insert(accounts_pair(post.account, true)); +} + +void report_payees::flush() +{ + std::ostream& out(report.output_stream); + + foreach (payees_pair& entry, payees) + out << entry.first << '\n'; +} + +void report_payees::operator()(post_t& post) +{ + std::map::iterator i = payees.find(post.xact->payee); + if (i == payees.end()) + payees.insert(payees_pair(post.xact->payee, true)); +} + +void report_commodities::flush() +{ + std::ostream& out(report.output_stream); + + foreach (commodities_pair& entry, commodities) + 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::iterator i = commodities.find(&comm); + if (i == commodities.end()) + commodities.insert(commodities_pair(&comm, true)); + + if (comm.has_annotation()) { + annotated_commodity_t& ann_comm(as_annotated_commodity(comm)); + if (ann_comm.details.price) { + std::map::iterator i = + commodities.find(&ann_comm.details.price->commodity()); + if (i == commodities.end()) + commodities.insert + (commodities_pair(&ann_comm.details.price->commodity(), true)); + } + } + + 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(), true)); + } +} + } // namespace ledger diff --git a/src/output.h b/src/output.h index 7618e567..3e70d9fe 100644 --- a/src/output.h +++ b/src/output.h @@ -103,6 +103,69 @@ public: virtual void operator()(account_t& account); }; +class report_accounts : public item_handler +{ +protected: + report_t& report; + + std::map accounts; + + typedef std::map::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); +}; + +class report_payees : public item_handler +{ +protected: + report_t& report; + + std::map payees; + + typedef std::map::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); +}; + +class report_commodities : public item_handler +{ +protected: + report_t& report; + + std::map commodities; + + typedef std::map::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); +}; + } // namespace ledger #endif // _OUTPUT_H diff --git a/src/report.cc b/src/report.cc index 1180c019..509be8b1 100644 --- a/src/report.cc +++ b/src/report.cc @@ -1222,6 +1222,12 @@ 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 @@ -1262,8 +1268,13 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, maybe_format(HANDLER(prepend_format_))), *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': @@ -1296,6 +1307,9 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, (new format_posts(*this, report_format(HANDLER(pricedb_format_)), maybe_format(HANDLER(prepend_format_))), *this, "#pricedb")); + else if (is_eq(p, "payees")) + return WRAP_FUNCTOR(reporter<>(new report_payees(*this), *this, + "#payees")); break; case 'r': -- cgit v1.2.3 From 02e78255166b17bff08a408166cc56ff9269c925 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 22 May 2010 17:04:38 -0400 Subject: Option --count sums payees, account, commodities --- src/output.cc | 43 +++++++++++++++++++++++++++++++------------ src/output.h | 12 ++++++------ src/report.cc | 1 + src/report.h | 1 + 4 files changed, 39 insertions(+), 18 deletions(-) diff --git a/src/output.cc b/src/output.cc index e3aa9f4a..ec1faba6 100644 --- a/src/output.cc +++ b/src/output.cc @@ -236,38 +236,51 @@ void report_accounts::flush() { std::ostream& out(report.output_stream); - foreach (accounts_pair& entry, accounts) + 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::iterator i = accounts.find(post.account); + std::map::iterator i = accounts.find(post.account); if (i == accounts.end()) - accounts.insert(accounts_pair(post.account, true)); + 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) + 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::iterator i = payees.find(post.xact->payee); + std::map::iterator i = payees.find(post.xact->payee); if (i == payees.end()) - payees.insert(payees_pair(post.xact->payee, true)); + 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) + foreach (commodities_pair& entry, commodities) { + if (report.HANDLED(count)) + out << entry.second << ' '; out << *entry.first << '\n'; + } } void report_commodities::operator()(post_t& post) @@ -275,18 +288,22 @@ 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::iterator i = commodities.find(&comm); + std::map::iterator i = commodities.find(&comm); if (i == commodities.end()) - commodities.insert(commodities_pair(&comm, true)); + 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::iterator i = + std::map::iterator i = commodities.find(&ann_comm.details.price->commodity()); if (i == commodities.end()) commodities.insert - (commodities_pair(&ann_comm.details.price->commodity(), true)); + (commodities_pair(&ann_comm.details.price->commodity(), 1)); + else + (*i).second++; } } @@ -294,7 +311,9 @@ void report_commodities::operator()(post_t& post) 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(), true)); + commodities.insert(commodities_pair(&temp_cost.commodity(), 1)); + else + (*i).second++; } } diff --git a/src/output.h b/src/output.h index 3e70d9fe..3a7b68df 100644 --- a/src/output.h +++ b/src/output.h @@ -108,9 +108,9 @@ class report_accounts : public item_handler protected: report_t& report; - std::map accounts; + std::map accounts; - typedef std::map::value_type accounts_pair; + typedef std::map::value_type accounts_pair; public: report_accounts(report_t& _report) : report(_report) { @@ -129,9 +129,9 @@ class report_payees : public item_handler protected: report_t& report; - std::map payees; + std::map payees; - typedef std::map::value_type payees_pair; + typedef std::map::value_type payees_pair; public: report_payees(report_t& _report) : report(_report) { @@ -150,9 +150,9 @@ class report_commodities : public item_handler protected: report_t& report; - std::map commodities; + std::map commodities; - typedef std::map::value_type commodities_pair; + typedef std::map::value_type commodities_pair; public: report_commodities(report_t& _report) : report(_report) { diff --git a/src/report.cc b/src/report.cc index 509be8b1..d2db87b0 100644 --- a/src/report.cc +++ b/src/report.cc @@ -853,6 +853,7 @@ option_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); diff --git a/src/report.h b/src/report.h index 6b10dbcc..783f0026 100644 --- a/src/report.h +++ b/src/report.h @@ -454,6 +454,7 @@ public: }); OPTION(report_t, columns_); + OPTION(report_t, count); OPTION__(report_t, csv_format_, CTOR(report_t, csv_format_) { on(none, -- cgit v1.2.3 From a158dc123b01d19a1f1cd8488a0b93067dd65ffd Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 22 May 2010 17:06:38 -0400 Subject: Minor change to setup_for_johnw in acprep --- acprep | 3 --- 1 file changed, 3 deletions(-) diff --git a/acprep b/acprep index d6df675e..347b217a 100755 --- a/acprep +++ b/acprep @@ -908,9 +908,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): -- cgit v1.2.3 From 33aa0cc3a6ad9485198c0e5abe694822811483b4 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 22 May 2010 17:08:16 -0400 Subject: Changed the report generated by the csv command Fields are now: Date,Code,Payee,Account,Commodity,Total,State,Note Instead of outputting amounts potentially as $1,000.00 (which was an error anyway), the output is now: $,1000.00. This makes the commodity available in a separate field, and removes display of thousands markers. Also, european formatting is always off. --- src/post.cc | 14 ++++++++++++-- src/report.h | 5 +++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/post.cc b/src/post.cc index fb3281a7..18c566c4 100644 --- a/src/post.cc +++ b/src/post.cc @@ -187,11 +187,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) { diff --git a/src/report.h b/src/report.h index 783f0026..df2f3469 100644 --- a/src/report.h +++ b/src/report.h @@ -459,11 +459,12 @@ public: 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(commodity))," + "%(quoted(quantity(scrub(display_amount))))," "%(quoted(cleared ? \"*\" : (pending ? \"!\" : \"\")))," - "%(quoted(code))," "%(quoted(join(note | xact.note)))\n"); }); -- cgit v1.2.3 From d397d5a2fc1cae219f8f040a5be99cc21df55d32 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 22 May 2010 17:50:13 -0400 Subject: Made the output from --options better looking --- src/option.h | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/option.h b/src/option.h index 9688171e..cd3cd594 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; } } -- cgit v1.2.3 From 2034434653177bc46bf22b4abbfa34b2acfa5187 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 22 May 2010 17:54:36 -0400 Subject: The regular tests are not be dependent on Python --- tools/Makefile.am | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) 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 += \ -- cgit v1.2.3 From 3e1ec40551184010d6ff3272d68b3ff17ff1ad26 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 22 May 2010 18:19:21 -0400 Subject: Report an error for incorrect dates like 2010/04/32 Fixes EF57C685-2C18-49A1-9A8C-FB3BE6F99C41 --- src/times.cc | 54 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/times.cc b/src/times.cc index be488baf..00be1d3b 100644 --- a/src/times.cc +++ b/src/times.cc @@ -199,23 +199,32 @@ namespace { { date_t when; - 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); + VERIFY(std::strlen(date_str) < 127); - for (char * p = buf; *p; p++) - if (*p == '.' || *p == '-') - *p = '/'; + char buf[128]; + std::strcpy(buf, date_str); - when = io.parse(buf); - } + for (char * p = buf; *p; p++) + if (*p == '.' || *p == '-') + *p = '/'; + + 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)); + + const char * p = io.format(when).c_str(); + const char * q = buf; + for (; *p != '\0' && *q != '\0'; + p++, q++) { + if (*p != *q && *p == '0') p++; + if (*p != *q) break; + } + if (*p != '\0' || *q != '\0') + throw_(date_error, _("Invalid date: %1") << date_str); if (traits) *traits = io.traits; @@ -1299,14 +1308,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 +1323,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; -- cgit v1.2.3 From a3482606dc33b88d2ae661e49c6b15b902497421 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 22 May 2010 18:45:42 -0400 Subject: Improved error reporting in the expression parser Fixes 15A80F68-F233-49D9-AF0C-9908BB6903BA --- src/parser.cc | 9 ++------- src/parser.h | 5 +++-- src/token.cc | 47 ++++++++++++++++++++++++++++++++++++----------- src/token.h | 7 ++++--- 4 files changed, 45 insertions(+), 23 deletions(-) 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/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 -- cgit v1.2.3 From 925b70d5c60849a2ca6857fcc5727f0d87d14757 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 22 May 2010 18:50:35 -0400 Subject: Ommitting args to the entry command print nothing --- src/draft.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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; } -- cgit v1.2.3 From db5418c8386e4a1a1b72e0bcdba2eeb22f99b96f Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 22 May 2010 19:16:11 -0400 Subject: Made a peculiar error slightly more verbose --- src/filters.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/filters.cc b/src/filters.cc index 0c45d356..13e188b1 100644 --- a/src/filters.cc +++ b/src/filters.cc @@ -869,7 +869,8 @@ void budget_posts::report_budget_items(const date_t& date) optional 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); -- cgit v1.2.3 From a7c28aa20057525a9247d0ae69eb063b53b21811 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 22 May 2010 19:17:13 -0400 Subject: Added new option --prepend-width This is useful for making sure that the column containing the results of --prepend-format is a consistent width throughout the report (including those lines where it is not applied). Fixes 64F9D913-75E1-4830-A3D9-29B72442E68B --- src/output.cc | 23 ++++++++++++++++------- src/output.h | 22 +++++++++++++--------- src/report.cc | 24 +++++++++++++++++------- src/report.h | 15 ++++++++++----- 4 files changed, 56 insertions(+), 28 deletions(-) diff --git a/src/output.cc b/src/output.cc index ec1faba6..183d80b3 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& _prepend_format) - : report(_report), last_xact(NULL), last_post(NULL) + const optional& _prepend_format, + std::size_t _prepend_width) + : report(_report), prepend_width(_prepend_width), + last_xact(NULL), last_post(NULL) { TRACE_CTOR(format_posts, "report&, const string&, bool"); @@ -82,8 +84,10 @@ void format_posts::operator()(post_t& post) ! post.xdata().has_flags(POST_EXT_DISPLAYED)) { bind_scope_t bound_scope(report, post); - if (prepend_format) + if (prepend_format) { + out.width(prepend_width); out << prepend_format(bound_scope); + } if (last_xact != post.xact) { if (last_xact) { @@ -107,8 +111,9 @@ void format_posts::operator()(post_t& post) format_accounts::format_accounts(report_t& _report, const string& format, - const optional& _prepend_format) - : report(_report), disp_pred() + const optional& _prepend_format, + std::size_t _prepend_width) + : report(_report), prepend_width(_prepend_width), disp_pred() { TRACE_CTOR(format_accounts, "report&, const string&"); @@ -144,9 +149,11 @@ std::size_t format_accounts::post_account(account_t& account, const bool flat) bind_scope_t bound_scope(report, account); - if (prepend_format) + if (prepend_format) { + static_cast(report.output_stream).width(prepend_width); static_cast(report.output_stream) << prepend_format(bound_scope); + } static_cast(report.output_stream) << account_line_format(bound_scope); @@ -216,9 +223,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(report.output_stream).width(prepend_width); static_cast(report.output_stream) << prepend_format(bound_scope); + } out << total_line_format(bound_scope); } diff --git a/src/output.h b/src/output.h index 3a7b68df..00c664c1 100644 --- a/src/output.h +++ b/src/output.h @@ -56,17 +56,19 @@ class report_t; class format_posts : public item_handler { 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; public: format_posts(report_t& _report, const string& format, - const optional& _prepend_format = none); + const optional& _prepend_format = none, + std::size_t _prepend_width = 0); virtual ~format_posts() { TRACE_DTOR(format_posts); } @@ -83,13 +85,15 @@ protected: format_t total_line_format; format_t separator_format; format_t prepend_format; + std::size_t prepend_width; predicate_t disp_pred; std::list posted_accounts; public: format_accounts(report_t& _report, const string& _format, - const optional& _prepend_format = none); + const optional& _prepend_format = none, + std::size_t _prepend_width = 0); virtual ~format_accounts() { TRACE_DTOR(format_accounts); } diff --git a/src/report.cc b/src/report.cc index d2db87b0..4c8f4060 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(0)); if (verb == "print" || verb == "xact" || verb == "dump") { HANDLER(related).on_only(string("?normalize")); @@ -937,6 +939,7 @@ option_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': @@ -1234,7 +1237,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return expr_t::op_t::wrap_functor (reporter (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")) { @@ -1247,7 +1251,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return expr_t::op_t::wrap_functor (reporter (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; @@ -1257,7 +1262,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")) { @@ -1266,7 +1272,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return expr_t::op_t::wrap_functor (reporter (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")) { @@ -1300,13 +1307,15 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return expr_t::op_t::wrap_functor (reporter (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 (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, @@ -1318,7 +1327,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 df2f3469..64c14858 100644 --- a/src/report.h +++ b/src/report.h @@ -280,6 +280,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 +373,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 +433,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%/" @@ -737,6 +739,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_) @@ -784,10 +789,10 @@ public: " 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"); }); -- cgit v1.2.3 From 9557a9d955f4272ac4ad7b727af18ab388b1f92e Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 22 May 2010 20:50:43 -0400 Subject: A minor fix to transaction auto-balancing It allows transactions like the following to auto-balance: 1999/08/16 Sell AAPL Assets:Broker $585 Expense:Broker:Commissions $15 Assets:Broker -10 AAPL {$30} @ $60 Income:Capital Gains --- src/xact.cc | 85 +++++++++++++++++++++++++++++-------------------------------- 1 file changed, 40 insertions(+), 45 deletions(-) diff --git a/src/xact.cc b/src/xact.cc index 3c2505af..f63835c9 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"))); -- cgit v1.2.3 From b5c9be4d29fab1388e195600d659602ae19b8f4e Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 22 May 2010 21:05:30 -0400 Subject: Created new valexpr variable display_account Where display_account might be '(Expenses:Food)', account will always be 'Expenses:Food'. account is now used by all matching and query operations, while display_account is used in the various report outputs (besides balance, which never distinguished virtual accounts). Fixes F2832452-4521-49A3-B854-F4E12CC4D82E --- src/post.cc | 32 ++++++++++++++++++++++++-------- src/report.h | 10 +++++----- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/post.cc b/src/post.cc index 18c566c4..f1f3e96a 100644 --- a/src/post.cc +++ b/src/post.cc @@ -236,7 +236,7 @@ namespace { return 1L; } - value_t get_account(call_scope_t& scope) + value_t account_name(call_scope_t& scope) { in_context_t env(scope, "&v"); @@ -279,14 +279,28 @@ 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 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) { @@ -398,7 +412,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>); diff --git a/src/report.h b/src/report.h index 64c14858..aff4af91 100644 --- a/src/report.h +++ b/src/report.h @@ -463,7 +463,7 @@ public: "%(quoted(date))," "%(quoted(code))," "%(quoted(payee))," - "%(quoted(account))," + "%(quoted(display_account))," "%(quoted(commodity))," "%(quoted(quantity(scrub(display_amount))))," "%(quoted(cleared ? \"*\" : (pending ? \"!\" : \"\")))," @@ -752,13 +752,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); @@ -785,8 +785,8 @@ 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 + prepend_width, true, color))" -- cgit v1.2.3 From 37a3f27ef2897a0d2bf129126f45c4ab0b5000bd Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 22 May 2010 21:32:50 -0400 Subject: Corrected handling of context stack in the parser Fixes F4A477E6-C4F6-43B1-ABCC-4DC325C2869A --- src/textual.cc | 31 ++++++++----------------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/src/textual.cc b/src/textual.cc index d953da26..16a6e228 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); @@ -689,7 +672,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; } @@ -1451,8 +1434,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); -- cgit v1.2.3 From 449d62d81269edf674256298a32962c6ee580d60 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 22 May 2010 21:46:56 -0400 Subject: acprep --no-python now disables Python support --- acprep | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/acprep b/acprep index 347b217a..3c5a631b 100755 --- a/acprep +++ b/acprep @@ -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:] -- cgit v1.2.3 From c645ac1de7b7b492eb9096e1a16937c9d3539795 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 22 May 2010 22:04:26 -0400 Subject: If FMT_CUSTOM is given, and no format, use FMT_PRINTED --- src/times.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/times.cc b/src/times.cc index 00be1d3b..35082f51 100644 --- a/src/times.cc +++ b/src/times.cc @@ -1471,7 +1471,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); @@ -1497,7 +1497,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); -- cgit v1.2.3 From 0f3e7e3e7fcba2b9f99dcc101b1c937ffa0fd56e Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 22 May 2010 22:05:03 -0400 Subject: The print command now honors use of --date-format --- src/print.cc | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/print.cc b/src/print.cc index 34f5af51..7964b001 100644 --- a/src/print.cc +++ b/src/print.cc @@ -71,11 +71,18 @@ namespace { void print_xact(report_t& report, std::ostream& out, xact_t& xact) { + format_type_t format_type = FMT_WRITTEN; + optional format; + + if (report.HANDLED(date_format_)) { + format_type = FMT_CUSTOM; + format = report.HANDLER(date_format_).str().c_str(); + } + out << format_date(item_t::use_effective_date ? - xact.date() : xact.actual_date(), - FMT_WRITTEN); + xact.date() : xact.actual_date(), format_type, format); if (! item_t::use_effective_date && xact.effective_date()) - out << '=' << format_date(*xact.effective_date(), FMT_WRITTEN); + out << '=' << format_date(*xact.effective_date(), format_type, format); out << ' '; out << (xact.state() == item_t::CLEARED ? "* " : -- cgit v1.2.3 From 7bd0170d51dd3cf20764b6748a9ea4884f9ee7bc Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 22 May 2010 23:22:26 -0600 Subject: print honors --columns, --account,amount-width --- src/print.cc | 81 +++++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 59 insertions(+), 22 deletions(-) diff --git a/src/print.cc b/src/print.cc index 7964b001..5c46f4e7 100644 --- a/src/print.cc +++ b/src/print.cc @@ -48,9 +48,14 @@ print_xacts::print_xacts(report_t& _report, } 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 << " ;"; @@ -79,22 +84,32 @@ namespace { format = report.HANDLER(date_format_).str().c_str(); } - out << format_date(item_t::use_effective_date ? - xact.date() : xact.actual_date(), format_type, format); + std::ostringstream buf; + + buf << format_date(item_t::use_effective_date ? + xact.date() : xact.actual_date(), + format_type, format); if (! item_t::use_effective_date && xact.effective_date()) - out << '=' << format_date(*xact.effective_date(), format_type, format); - 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; + + string leader = buf.str(); + out << leader; - out << xact.payee; + 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) { @@ -139,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(name.length()); - if (slip > 0) - out << string(slip, ' '); + int slip = (static_cast(account_width) - + static_cast(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(amount_width), -1, true); amt = amt_str.str(); } @@ -161,24 +193,29 @@ namespace { int amt_slip = (static_cast(amt.length()) - static_cast(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'; } } -- cgit v1.2.3 From 3fab2e1333dc0da4614900f7e231579a12d2e1c8 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sun, 23 May 2010 00:34:26 -0600 Subject: For Boost 1.35 and earlier, use is_regular Fixes 0F17CB7F-A000-4F99-8471-739948AD575F --- src/textual.cc | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/textual.cc b/src/textual.cc index 16a6e228..2b204df0 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -663,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 -- cgit v1.2.3 From 834d223c27f8240122dcc0de0e614f1b2e62c08e Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sun, 23 May 2010 00:37:45 -0600 Subject: Do not generate names with double underscores Fixes 6A4AFDC2-DE87-48A5-A17D-B04120EE1F62 --- src/option.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/option.h b/src/option.h index cd3cd594..f11497a4 100644 --- a/src/option.h +++ b/src/option.h @@ -205,20 +205,20 @@ public: }; #define BEGIN(type, name) \ - struct name ## _option_t : public option_t + struct name ## option_t : public option_t #define CTOR(type, name) \ - name ## _option_t() : option_t(#name) + name ## option_t() : option_t(#name) #define DECL1(type, name, vartype, var, value) \ vartype var ; \ - name ## _option_t() : option_t(#name), var(value) + name ## option_t() : option_t(#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::handler_wrapper, x, _1)) @@ -238,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) \ -- cgit v1.2.3 From efcede3ca5ce31603ef8454a0bd6c19ef67b2aeb Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sun, 23 May 2010 01:11:16 -0600 Subject: Fix to an interaction between --period and --sort Fixes 3AAB00ED-9904-4380-8988-16506B0AFE08 --- src/report.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/report.cc b/src/report.cc index 4c8f4060..dfdc77cc 100644 --- a/src/report.cc +++ b/src/report.cc @@ -145,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 begin = interval.begin(session.current_year); @@ -164,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 -- cgit v1.2.3 From 847a5e4e73afd1c959f7211ceb67d6b9ab0f95d0 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sun, 23 May 2010 15:23:19 -0600 Subject: Optimized several "in_place" function in balance_t --- src/balance.h | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) 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 { -- cgit v1.2.3 From 7bddcd676bc53c6caad7dd283be362fcd53d5721 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sun, 23 May 2010 15:24:02 -0600 Subject: Added --rounding option, which is off by default The purpose of this option is to add special "" postings, to ensure that a regiter's running total is *always* the sum of its postings. Within --rounding, these adjustment postings are missing, which was the behavior in Ledger 2.x. It can be orders of magnitude slower to turn it on for large reports with many commodities. --- src/chain.cc | 3 ++- src/filters.cc | 13 ++++++++----- src/filters.h | 4 +++- src/report.cc | 1 + src/report.h | 2 ++ test/ConfirmTests.py | 2 +- 6 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/chain.cc b/src/chain.cc index 86f639ad..44133391 100644 --- a/src/chain.cc +++ b/src/chain.cc @@ -86,7 +86,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(rounding))); // calc_posts computes the running total. When this appears will determine, // for example, whether filtered posts are included or excluded from the diff --git a/src/filters.cc b/src/filters.cc index 13e188b1..57c95cd3 100644 --- a/src/filters.cc +++ b/src/filters.cc @@ -420,10 +420,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(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(_(""))), rounding_account(temps.create_account(_(""))) { @@ -505,9 +507,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 + /* functor= */ (show_rounding ? + optional (bind(&changed_value_posts::output_rounding, - this, _1)))); + this, _1)) : none)); } else if (show_unrealized) { handle_value @@ -572,7 +575,7 @@ void changed_value_posts::operator()(post_t& post) 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::operator()(post); diff --git a/src/filters.h b/src/filters.h index 82fbf687..ced51f3f 100644 --- a/src/filters.h +++ b/src/filters.h @@ -381,6 +381,7 @@ class changed_value_posts : public item_handler 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; @@ -396,7 +397,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); diff --git a/src/report.cc b/src/report.cc index dfdc77cc..486c0fdf 100644 --- a/src/report.cc +++ b/src/report.cc @@ -954,6 +954,7 @@ option_t * report_t::lookup_option(const char * p) else OPT(revalued); else OPT(revalued_only); else OPT(revalued_total_); + else OPT(rounding); break; case 's': OPT(sort_); diff --git a/src/report.h b/src/report.h index aff4af91..aa72832c 100644 --- a/src/report.h +++ b/src/report.h @@ -295,6 +295,7 @@ public: HANDLER(revalued).report(out); HANDLER(revalued_only).report(out); HANDLER(revalued_total_).report(out); + HANDLER(rounding).report(out); HANDLER(seed_).report(out); HANDLER(sort_).report(out); HANDLER(sort_all_).report(out); @@ -818,6 +819,7 @@ public: set_expr(args[0].to_string(), args[1].to_string()); }); + OPTION(report_t, rounding); OPTION(report_t, seed_); OPTION_(report_t, sort_, DO_(args) { // -S 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() -- cgit v1.2.3 From dd8f4ce88f22f7ec6712738af7cac635e388fd69 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sun, 23 May 2010 17:04:00 -0600 Subject: Added a new baseline test for the print command --- test/baseline/cmd-print.test | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 test/baseline/cmd-print.test diff --git a/test/baseline/cmd-print.test b/test/baseline/cmd-print.test new file mode 100644 index 00000000..759a2334 --- /dev/null +++ b/test/baseline/cmd-print.test @@ -0,0 +1,11 @@ +print --european +<<< +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 -- cgit v1.2.3 From 04461f49fdc0f70d74172c77843be3e0a9fe313f Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 26 May 2010 00:58:04 -0600 Subject: Optimized amount_t::in_place_truncate --- src/amount.cc | 38 ++++++++++++++++++++++++++++++++++++++ src/amount.h | 4 +--- test/regress/25A099C9.test | 12 ++++++------ 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/amount.cc b/src/amount.cc index 3a64577f..a16d287e 100644 --- a/src/amount.cc +++ b/src/amount.cc @@ -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 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) 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/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 -- cgit v1.2.3 From 2fa3e50f081970b43b550f4c986eefb1298fd502 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Thu, 27 May 2010 16:05:13 -0600 Subject: Fixed a sequencing problem in acprep --- acprep | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/acprep b/acprep index 3c5a631b..a611e3e9 100755 --- a/acprep +++ b/acprep @@ -746,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', @@ -767,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'): @@ -784,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', @@ -805,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', @@ -829,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) -- cgit v1.2.3 From 51115f0a91b81453fc0f7b055df5a4ea13ee70a7 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Thu, 27 May 2010 21:23:50 -0600 Subject: Updated to Boost 1.43 --- acprep | 24 ++++++++++++------------ lib/Makefile | 2 +- src/py_account.cc | 6 +++--- src/py_commodity.cc | 13 ++++++++----- src/py_journal.cc | 19 ++++++++++--------- src/py_xact.cc | 4 ++-- 6 files changed, 36 insertions(+), 32 deletions(-) diff --git a/acprep b/acprep index a611e3e9..b7c5e177 100755 --- a/acprep +++ b/acprep @@ -1122,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 @@ -1136,13 +1136,13 @@ 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): 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/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 > + .def("__iter__", python::range > (&account_t::accounts_begin, &account_t::accounts_end)) - .def("accounts", range > + .def("accounts", python::range > (&account_t::accounts_begin, &account_t::accounts_end)) - .def("posts", range > + .def("posts", python::range > (&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..d89a7151 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 > + .def("__iter__", + python::range > (py_pool_commodities_begin, py_pool_commodities_end)) - .def("iteritems", range > + .def("iteritems", + python::range > (py_pool_commodities_begin, py_pool_commodities_end)) - .def("iterkeys", range<>(py_pool_commodities_keys_begin, - py_pool_commodities_keys_end)) - .def("itervalues", range > + .def("iterkeys", python::range<>(py_pool_commodities_keys_begin, + py_pool_commodities_keys_end)) + .def("itervalues", + python::range > (py_pool_commodities_values_begin, py_pool_commodities_values_end)) ; 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 >, shared_ptr, boost::noncopyable >("PostCollector") .def("__len__", &collect_posts::length) - .def("__iter__", range > > + .def("__iter__", python::range > > (&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 > > + .def("__iter__", + python::range > > (&collector_wrapper::begin, &collector_wrapper::end)) ; @@ -296,15 +297,15 @@ void export_journal() with_custodian_and_ward_postcall<0, 1> >()) #endif - .def("__iter__", range > + .def("__iter__", python::range > (&journal_t::xacts_begin, &journal_t::xacts_end)) - .def("xacts", range > + .def("xacts", python::range > (&journal_t::xacts_begin, &journal_t::xacts_end)) - .def("auto_xacts", range > + .def("auto_xacts", python::range > (&journal_t::auto_xacts_begin, &journal_t::auto_xacts_end)) - .def("period_xacts", range > + .def("period_xacts", python::range > (&journal_t::period_xacts_begin, &journal_t::period_xacts_end)) - .def("sources", range > + .def("sources", python::range > (&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 > + .def("__iter__", python::range > (&xact_t::posts_begin, &xact_t::posts_end)) - .def("posts", range > + .def("posts", python::range > (&xact_t::posts_begin, &xact_t::posts_end)) .def("valid", &xact_base_t::valid) -- cgit v1.2.3 From 1a276543d04dc1c012165bd6c90a070ffa12eb3a Mon Sep 17 00:00:00 2001 From: Michael Norrish Date: Thu, 8 Apr 2010 11:06:44 +1000 Subject: Documentation fixes. --- doc/ledger.texi | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/doc/ledger.texi b/doc/ledger.texi index 10e80a60..f79ffbac 100644 --- a/doc/ledger.texi +++ b/doc/ledger.texi @@ -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 @@ -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 @@ -2581,7 +2581,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 +3507,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 +3762,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. -- cgit v1.2.3 From 9c976c1185ba93be698fce1441b7a64152b147f0 Mon Sep 17 00:00:00 2001 From: Martin Michlmayr Date: Fri, 28 May 2010 01:10:18 -0600 Subject: Add a missing word to the manual --- doc/ledger.texi | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/ledger.texi b/doc/ledger.texi index f79ffbac..0efe90f5 100644 --- a/doc/ledger.texi +++ b/doc/ledger.texi @@ -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 -- cgit v1.2.3 From 38c119c0363d6ab123358aa8e6c3ea3e20126905 Mon Sep 17 00:00:00 2001 From: Martin Michlmayr Date: Fri, 28 May 2010 01:11:08 -0600 Subject: Fix a cosmetic problem in the manual --- doc/ledger.texi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ledger.texi b/doc/ledger.texi index 0efe90f5..a82d920d 100644 --- a/doc/ledger.texi +++ b/doc/ledger.texi @@ -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 -- cgit v1.2.3 From 6bd2fa2d52bc9ec1c23dd98bbeb72be3e96547e6 Mon Sep 17 00:00:00 2001 From: Martin Michlmayr Date: Fri, 28 May 2010 01:11:59 -0600 Subject: Simplify introductory section of the manual I found the first example confusing because it includes "(100") without any explanation. Transaction codes are only explained later on and this one isn't needed, so remove it. --- doc/ledger.texi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/ledger.texi b/doc/ledger.texi index a82d920d..fef1d422 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 -- cgit v1.2.3 From 33d119d3ea8acc73a93fd45fa6eae6b4f79d2976 Mon Sep 17 00:00:00 2001 From: Martin Michlmayr Date: Fri, 28 May 2010 01:12:56 -0600 Subject: Fixed a typo in the manual --- doc/ledger.texi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ledger.texi b/doc/ledger.texi index fef1d422..556c2c45 100644 --- a/doc/ledger.texi +++ b/doc/ledger.texi @@ -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 -- cgit v1.2.3 From 66a26252df84909224ec18b4ca2adaaca3b01268 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sun, 30 May 2010 02:15:45 -0600 Subject: Corrected error message text to be consistent --- src/value.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/value.cc b/src/value.cc index ce9b0e6a..2888c335 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; } -- cgit v1.2.3 From 7ec52d2b395bf4cfc656eef52d72b9d83c1c1523 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sun, 30 May 2010 02:16:32 -0600 Subject: Comparison of boolean values is now allowed true > false --- src/value.cc | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/value.cc b/src/value.cc index 2888c335..9515de46 100644 --- a/src/value.cc +++ b/src/value.cc @@ -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(); -- cgit v1.2.3 From 8f17d01f5e48ae5097f4cb38d481b00577329b8c Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sun, 30 May 2010 02:20:34 -0600 Subject: Added new required item_handler_t::clear() method --- src/chain.h | 5 ++ src/filters.h | 168 +++++++++++++++++++++++++++++++++++++++++++++++++++++----- src/output.h | 29 ++++++++++ src/temps.cc | 45 +++++++++------- src/temps.h | 6 ++- src/xml.h | 8 +++ 6 files changed, 227 insertions(+), 34 deletions(-) diff --git a/src/chain.h b/src/chain.h index 94d54317..fbde9f0a 100644 --- a/src/chain.h +++ b/src/chain.h @@ -75,6 +75,11 @@ public: (*handler.get())(item); } } + + virtual void clear() { + if (handler) + handler->clear(); + } }; typedef shared_ptr > post_handler_ptr; diff --git a/src/filters.h b/src/filters.h index ced51f3f..3f3e3d34 100644 --- a/src/filters.h +++ b/src/filters.h @@ -88,6 +88,11 @@ public: virtual void operator()(post_t& post) { posts.push_back(&post); } + + virtual void clear() { + posts.clear(); + item_handler::clear(); + } }; class posts_iterator; @@ -149,27 +154,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::clear(); + } }; class sort_posts : public item_handler { typedef std::deque 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(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(handler), sort_order(_sort_order) { TRACE_CTOR(sort_posts, @@ -189,6 +201,13 @@ public: virtual void operator()(post_t& post) { posts.push_back(&post); } + + virtual void clear() { + posts.clear(); + sort_order.mark_uncompiled(); + + item_handler::clear(); + } }; class sort_xacts : public item_handler @@ -199,14 +218,12 @@ class sort_xacts : public item_handler 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 +245,13 @@ public: last_xact = post.xact; } + + virtual void clear() { + sorter.clear(); + last_xact = NULL; + + item_handler::clear(); + } }; class filter_posts : public item_handler @@ -255,6 +279,11 @@ public: (*handler)(post); } } + + virtual void clear() { + pred.mark_uncompiled(); + item_handler::clear(); + } }; class anonymize_posts : public item_handler @@ -274,6 +303,13 @@ public: } virtual void operator()(post_t& post); + + virtual void clear() { + temps.clear(); + last_xact = NULL; + + item_handler::clear(); + } }; class calc_posts : public item_handler @@ -297,6 +333,13 @@ public: } virtual void operator()(post_t& post); + + virtual void clear() { + last_post = NULL; + amount_expr.mark_uncompiled(); + + item_handler::clear(); + } }; class collapse_posts : public item_handler @@ -334,13 +377,29 @@ public: } virtual void flush() { - report_subtotal(); + report_subtotal(); item_handler::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::clear(); + } }; class related_posts : public item_handler @@ -367,6 +426,11 @@ public: post.xdata().add_flags(POST_EXT_RECEIVED); posts.push_back(&post); } + + virtual void clear() { + posts.clear(); + item_handler::clear(); + } }; class changed_value_posts : public item_handler @@ -410,6 +474,20 @@ public: 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::clear(); + } }; class subtotal_posts : public item_handler @@ -471,10 +549,20 @@ public: item_handler::flush(); } virtual void operator()(post_t& post); + + virtual void clear() { + amount_expr.mark_uncompiled(); + values.clear(); + temps.clear(); + component_posts.clear(); + + item_handler::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; @@ -491,8 +579,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(_(""))), + : subtotal_posts(_handler, amount_expr), start_interval(_interval), + interval(start_interval), last_post(NULL), + empty_account(temps.create_account(_(""))), exact_periods(_exact_periods), generate_empty_posts(_generate_empty_posts) { TRACE_CTOR(interval_posts, @@ -512,6 +601,14 @@ public: } } virtual void operator()(post_t& post); + + virtual void clear() { + interval = start_interval; + last_interval = date_interval_t(); + last_post = NULL; + + item_handler::clear(); + } }; class posts_as_equity : public subtotal_posts @@ -539,6 +636,11 @@ public: report_subtotal(); subtotal_posts::flush(); } + + virtual void clear() { + last_post = NULL; + item_handler::clear(); + } }; class by_payee_posts : public item_handler @@ -562,6 +664,13 @@ class by_payee_posts : public item_handler virtual void flush(); virtual void operator()(post_t& post); + + virtual void clear() { + amount_expr.mark_uncompiled(); + payee_subtotals.clear(); + + item_handler::clear(); + } }; class transfer_details : public item_handler @@ -595,6 +704,13 @@ public: } virtual void operator()(post_t& post); + + virtual void clear() { + expr.mark_uncompiled(); + temps.clear(); + + item_handler::clear(); + } }; class dow_posts : public subtotal_posts @@ -616,6 +732,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::clear(); + } }; class generate_posts : public item_handler @@ -642,6 +765,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::clear(); + } }; class budget_posts : public generate_posts @@ -692,6 +822,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::clear(); + } }; ////////////////////////////////////////////////////////////////////// @@ -717,6 +852,13 @@ public: virtual ~pass_down_accounts() { TRACE_DTOR(pass_down_accounts); } + + virtual void clear() { + if (pred) + pred->mark_uncompiled(); + + item_handler::clear(); + } }; } // namespace ledger diff --git a/src/output.h b/src/output.h index 00c664c1..f0e7f9a5 100644 --- a/src/output.h +++ b/src/output.h @@ -75,6 +75,13 @@ public: virtual void flush(); virtual void operator()(post_t& post); + + virtual void clear() { + last_xact = NULL; + last_post = NULL; + + item_handler::clear(); + } }; class format_accounts : public item_handler @@ -105,6 +112,13 @@ public: virtual void flush(); virtual void operator()(account_t& account); + + virtual void clear() { + disp_pred.mark_uncompiled(); + posted_accounts.clear(); + + item_handler::clear(); + } }; class report_accounts : public item_handler @@ -126,6 +140,11 @@ public: virtual void flush(); virtual void operator()(post_t& post); + + virtual void clear() { + accounts.clear(); + item_handler::clear(); + } }; class report_payees : public item_handler @@ -147,6 +166,11 @@ public: virtual void flush(); virtual void operator()(post_t& post); + + virtual void clear() { + payees.clear(); + item_handler::clear(); + } }; class report_commodities : public item_handler @@ -168,6 +192,11 @@ public: virtual void flush(); virtual void operator()(post_t& post); + + virtual void clear() { + commodities.clear(); + item_handler::clear(); + } }; } // namespace ledger diff --git a/src/temps.cc b/src/temps.cc index dcaa9101..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) @@ -134,4 +114,29 @@ account_t& temporaries_t::create_account(const string& name, 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 > 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/xml.h b/src/xml.h index 320096f8..5d14dab3 100644 --- a/src/xml.h +++ b/src/xml.h @@ -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::clear(); + } }; } // namespace ledger -- cgit v1.2.3 From 4d372a8e1ecd4203fe8d9de24e945ef793e4f039 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sun, 30 May 2010 02:26:17 -0600 Subject: Added value_scope_t, for wrapping a value in a scope The value expression "value" may be used to extract the wrapped value. This is currently only used by the upcoming --group-title-format option. --- src/scope.h | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) 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(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 -- cgit v1.2.3 From 3215fd71a499648f5ff8e992bd1ff66e963858a0 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sun, 30 May 2010 02:27:25 -0600 Subject: Added optional item_handler::title() method --- src/chain.h | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/chain.h b/src/chain.h index fbde9f0a..935f2935 100644 --- a/src/chain.h +++ b/src/chain.h @@ -65,14 +65,19 @@ 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); } } -- cgit v1.2.3 From f491979d5547742aae70b3f6dd5b4aa0eac36605 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sun, 30 May 2010 02:41:06 -0600 Subject: Added new option: --no-titles --- src/report.cc | 1 + src/report.h | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/report.cc b/src/report.cc index 486c0fdf..4dee4bb2 100644 --- a/src/report.cc +++ b/src/report.cc @@ -916,6 +916,7 @@ option_t * report_t::lookup_option(const char * p) case 'n': OPT_CH(collapse); else OPT(no_color); + else OPT(no_titles); else OPT(no_total); else OPT(now_); break; diff --git a/src/report.h b/src/report.h index aa72832c..104cfc73 100644 --- a/src/report.h +++ b/src/report.h @@ -267,6 +267,7 @@ public: HANDLER(market).report(out); HANDLER(meta_).report(out); HANDLER(monthly).report(out); + HANDLER(no_titles).report(out); HANDLER(no_total).report(out); HANDLER(now_).report(out); HANDLER(only_).report(out); @@ -637,6 +638,7 @@ public: parent->HANDLER(color).off(); }); + OPTION(report_t, no_titles); OPTION(report_t, no_total); OPTION_(report_t, now_, DO_(args) { -- cgit v1.2.3 From e86a4767bc93664893faa4d0f03619f57302c7d1 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sun, 30 May 2010 02:27:46 -0600 Subject: Added new post_splitter posting handler --- src/filters.cc | 43 ++++++++++++++++++++++++++++++++++++++++++- src/filters.h | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/src/filters.cc b/src/filters.cc index 57c95cd3..6915144d 100644 --- a/src/filters.cc +++ b/src/filters.cc @@ -39,8 +39,49 @@ 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)); + + value_to_posts_map::iterator i = posts_map.find(result); + if (i != posts_map.end()) { + (*i).second.push_back(&post); + } else { + std::pair 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(handler) { TRACE_CTOR(pass_down_posts, "post_handler_ptr, posts_iterator"); diff --git a/src/filters.h b/src/filters.h index 3f3e3d34..dd6b3b1a 100644 --- a/src/filters.h +++ b/src/filters.h @@ -50,6 +50,56 @@ namespace ledger { +////////////////////////////////////////////////////////////////////// +// +// Posting collector +// + +class post_splitter : public item_handler +{ +public: + typedef std::map value_to_posts_map; + typedef function 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 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 -- cgit v1.2.3 From a41d33fba37460587f59ea0349ac4947a4de9f3c Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sun, 30 May 2010 02:35:25 -0600 Subject: Option --rounding inverted to --no-rounding --- src/chain.cc | 2 +- src/report.cc | 2 +- src/report.h | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/chain.cc b/src/chain.cc index 44133391..1103fe42 100644 --- a/src/chain.cc +++ b/src/chain.cc @@ -87,7 +87,7 @@ post_handler_ptr chain_post_handlers(report_t& report, handler.reset(new changed_value_posts(handler, report, for_accounts_report, report.HANDLED(unrealized), - report.HANDLED(rounding))); + ! 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 diff --git a/src/report.cc b/src/report.cc index 4dee4bb2..f3186e0b 100644 --- a/src/report.cc +++ b/src/report.cc @@ -916,6 +916,7 @@ option_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_); @@ -955,7 +956,6 @@ option_t * report_t::lookup_option(const char * p) else OPT(revalued); else OPT(revalued_only); else OPT(revalued_total_); - else OPT(rounding); break; case 's': OPT(sort_); diff --git a/src/report.h b/src/report.h index 104cfc73..78dc2165 100644 --- a/src/report.h +++ b/src/report.h @@ -267,6 +267,7 @@ 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); @@ -296,7 +297,6 @@ public: HANDLER(revalued).report(out); HANDLER(revalued_only).report(out); HANDLER(revalued_total_).report(out); - HANDLER(rounding).report(out); HANDLER(seed_).report(out); HANDLER(sort_).report(out); HANDLER(sort_all_).report(out); @@ -638,6 +638,7 @@ public: parent->HANDLER(color).off(); }); + OPTION(report_t, no_rounding); OPTION(report_t, no_titles); OPTION(report_t, no_total); @@ -821,7 +822,6 @@ public: set_expr(args[0].to_string(), args[1].to_string()); }); - OPTION(report_t, rounding); OPTION(report_t, seed_); OPTION_(report_t, sort_, DO_(args) { // -S -- cgit v1.2.3 From 647d4aac2fa474085d01f7ea1cebdc34fafd64a6 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sun, 30 May 2010 02:28:58 -0600 Subject: New: --group-by=EXPR and --group-title-format=FMT The --group-by option allows for most reports to be split up into sections based on the varying value of EXPR. For example, to see register subtotals by payee, use: ledger reg --group-by=payee -s This works for separated balances too: ledger bal --group-by=payee Another interesting possibility is seeing a register of all the accounts affected by a related account: ledger reg -r --group-by=payee The option --group-title-format can be used to add a separator bar to the group titles. The option --no-titles can be used to drop titles altogether. --- src/chain.cc | 126 +++++++++++++++++++++++++----------------------- src/chain.h | 14 ++++++ src/output.cc | 55 +++++++++++++++++---- src/output.h | 16 +++++++ src/print.cc | 12 ++++- src/print.h | 10 ++++ src/report.cc | 150 +++++++++++++++++++++++++++++++++++++++++++++------------- src/report.h | 18 +++++++ 8 files changed, 298 insertions(+), 103 deletions(-) diff --git a/src/chain.cc b/src/chain.cc index 1103fe42..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 + (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) @@ -189,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 - (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 935f2935..59b04eb8 100644 --- a/src/chain.h +++ b/src/chain.h @@ -91,11 +91,25 @@ typedef shared_ptr > post_handler_ptr; typedef shared_ptr > 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/output.cc b/src/output.cc index 183d80b3..f697dee4 100644 --- a/src/output.cc +++ b/src/output.cc @@ -45,7 +45,7 @@ format_posts::format_posts(report_t& _report, const optional& _prepend_format, std::size_t _prepend_width) : report(_report), prepend_width(_prepend_width), - last_xact(NULL), last_post(NULL) + last_xact(NULL), last_post(NULL), first_report_title(true) { TRACE_CTOR(format_posts, "report&, const string&, bool"); @@ -78,12 +78,30 @@ 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 (! 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); @@ -113,7 +131,8 @@ format_accounts::format_accounts(report_t& _report, const string& format, const optional& _prepend_format, std::size_t _prepend_width) - : report(_report), prepend_width(_prepend_width), disp_pred() + : report(_report), prepend_width(_prepend_width), disp_pred(), + first_report_title(true) { TRACE_CTOR(format_accounts, "report&, const string&"); @@ -144,19 +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 (! 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) { - static_cast(report.output_stream).width(prepend_width); - static_cast(report.output_stream) - << prepend_format(bound_scope); + out.width(prepend_width); + out << prepend_format(bound_scope); } - static_cast(report.output_stream) - << account_line_format(bound_scope); + out << account_line_format(bound_scope); return 1; } diff --git a/src/output.h b/src/output.h index f0e7f9a5..a19c6235 100644 --- a/src/output.h +++ b/src/output.h @@ -64,6 +64,8 @@ protected: 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, @@ -73,6 +75,10 @@ public: TRACE_DTOR(format_posts); } + virtual void title(const string& str) { + report_title = str; + } + virtual void flush(); virtual void operator()(post_t& post); @@ -80,6 +86,8 @@ public: last_xact = NULL; last_post = NULL; + report_title = ""; + item_handler::clear(); } }; @@ -94,6 +102,8 @@ protected: format_t prepend_format; std::size_t prepend_width; predicate_t disp_pred; + bool first_report_title; + string report_title; std::list posted_accounts; @@ -108,6 +118,10 @@ public: std::pair 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(); @@ -117,6 +131,8 @@ public: disp_pred.mark_uncompiled(); posted_accounts.clear(); + report_title = ""; + item_handler::clear(); } }; diff --git a/src/print.cc b/src/print.cc index 5c46f4e7..a8aa5872 100644 --- a/src/print.cc +++ b/src/print.cc @@ -42,7 +42,7 @@ 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"); } @@ -221,6 +221,16 @@ namespace { } } +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::clear(); + } }; diff --git a/src/report.cc b/src/report.cc index f3186e0b..dcb56319 100644 --- a/src/report.cc +++ b/src/report.cc @@ -274,10 +274,35 @@ 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 + 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); + pass_down_posts(handler, walker); + session.journal->clear_xdata(); } @@ -285,66 +310,121 @@ 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(HANDLER(seed_).value.to_long()) : 0, HANDLED(head_) ? static_cast(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 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 + 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 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(); } @@ -888,6 +968,8 @@ option_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_); diff --git a/src/report.h b/src/report.h index 78dc2165..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); @@ -596,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& 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() { -- cgit v1.2.3 From 4f3b39e22c7a7743132ead79b1e092929679de44 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sun, 30 May 2010 02:55:02 -0600 Subject: Empty notes and tags now return null values --- src/filters.cc | 84 +++++++++++++++++++----------------- src/item.cc | 9 ++-- src/post.cc | 12 ++++-- src/xact.cc | 2 +- test/baseline/opt-code-as-payee.test | 4 +- 5 files changed, 60 insertions(+), 51 deletions(-) diff --git a/src/filters.cc b/src/filters.cc index 6915144d..6e4aeee3 100644 --- a/src/filters.cc +++ b/src/filters.cc @@ -69,14 +69,16 @@ void post_splitter::operator()(post_t& post) bind_scope_t bound_scope(report, post); value_t result(group_by_expr.calc(bound_scope)); - value_to_posts_map::iterator i = posts_map.find(result); - if (i != posts_map.end()) { - (*i).second.push_back(&post); - } else { - std::pair inserted - = posts_map.insert(value_to_posts_map::value_type(result, posts_list())); - assert(inserted.second); - (*inserted.first).second.push_back(&post); + 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 inserted + = posts_map.insert(value_to_posts_map::value_type(result, posts_list())); + assert(inserted.second); + (*inserted.first).second.push_back(&post); + } } } @@ -837,41 +839,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; - - 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 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()); + 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 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::operator()(temp); 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(args)); optional 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/post.cc b/src/post.cc index f1f3e96a..7dc15830 100644 --- a/src/post.cc +++ b/src/post.cc @@ -150,7 +150,7 @@ namespace { 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) { @@ -158,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) { diff --git a/src/xact.cc b/src/xact.cc index f63835c9..569e5869 100644 --- a/src/xact.cc +++ b/src/xact.cc @@ -468,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) { 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 -- cgit v1.2.3 From d5ea3080a7322730a28009bb3190631f371234db Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sun, 30 May 2010 03:19:09 -0600 Subject: Allow null values to be cast to int and string --- src/value.cc | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/value.cc b/src/value.cc index 9515de46..a967eeb8 100644 --- a/src/value.cc +++ b/src/value.cc @@ -1075,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; -- cgit v1.2.3 From 58621a96a22de5c1453b7fac624e00dc7f3c402e Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sun, 30 May 2010 20:43:45 -0600 Subject: Made several debug categories more consistent --- src/filters.cc | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/filters.cc b/src/filters.cc index 6e4aeee3..eabf9834 100644 --- a/src/filters.cc +++ b/src/filters.cc @@ -355,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); @@ -398,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); } @@ -525,14 +525,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(); @@ -578,24 +578,24 @@ 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(); -- cgit v1.2.3 From 5a2644c1b7220dc96e4de9cfb82f6d829cf34321 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sun, 30 May 2010 20:49:50 -0600 Subject: -V/-X options now take price history into account --- src/filters.cc | 80 ++++++++++++++++++++++++++++++++++++++++++-- src/filters.h | 2 ++ test/baseline/opt-price.test | 3 +- test/regress/D943AE0F.test | 4 +-- 4 files changed, 84 insertions(+), 5 deletions(-) diff --git a/src/filters.cc b/src/filters.cc index eabf9834..bca8516e 100644 --- a/src/filters.cc +++ b/src/filters.cc @@ -503,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; } @@ -514,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); @@ -573,6 +574,78 @@ 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. + assert(! last_total.is_null()); + + switch (last_total.type()) { + case value_t::INTEGER: + case value_t::SEQUENCE: + break; + case value_t::AMOUNT: + last_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, + last_total.as_balance().amounts) { + if (optional 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 > history_by_date_map; + history_by_date_map all_prices_by_date; + + 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()); + all_prices_by_date.insert(history_by_date_map::value_type + (price.first.date(), price)); + } + + // Go through the time-sorted prices list, outputting a revaluation for + // each price difference. + foreach (const history_by_date_map::value_type& price, all_prices_by_date) { + 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); @@ -612,8 +685,11 @@ 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); diff --git a/src/filters.h b/src/filters.h index dd6b3b1a..a66d8c47 100644 --- a/src/filters.h +++ b/src/filters.h @@ -499,6 +499,7 @@ class changed_value_posts : public item_handler 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; @@ -521,6 +522,7 @@ 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); diff --git a/test/baseline/opt-price.test b/test/baseline/opt-price.test index 06cc7751..bf302264 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 $500 $2500 08-Jun-30 Sell some Apple sha.. Equities $-1250 $1250 -09-Jun-26 Commodities revalued $750 $2000 +09-Jan-31 Commodities revalued $250 $1500 +09-Jun-26 Commodities revalued $500 $2000 >>>2 === 0 reg --end 2009/06/26 -G equities 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 200.00 EUR 200.00 EUR +08-Apr-20 Commodities revalued 200.00 EUR 200.00 EUR >>>2 === 0 -- cgit v1.2.3 From 654c842348c570a086f31cb79b7b3b4810463707 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sun, 30 May 2010 21:58:05 -0600 Subject: -G option now takes price history into account --- src/filters.cc | 56 ++++++++++++++++++++++++++++++++++++++++---- test/baseline/opt-gain.test | 3 ++- test/baseline/opt-price.test | 3 ++- 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/filters.cc b/src/filters.cc index bca8516e..41e8c16b 100644 --- a/src/filters.cc +++ b/src/filters.cc @@ -582,20 +582,68 @@ void changed_value_posts::output_intermediate_prices(post_t& post, // 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. - assert(! last_total.is_null()); - switch (last_total.type()) { + 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: - last_total.in_place_cast(value_t::BALANCE); + 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, - last_total.as_balance().amounts) { + display_total.as_balance().amounts) { if (optional hist = amt_comm.first->varied_history()) { foreach 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 300 P 300 P +09-Jan-15 Commodities revalued 100 P 100 P +09-Feb-01 Commodities revalued 200 P 300 P 09-Feb-01 Sample 2a As:Brokerage:Stocks 300 P 600 P 09-Mar-01 Commodities 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 bf302264..133b2155 100644 --- a/test/baseline/opt-price.test +++ b/test/baseline/opt-price.test @@ -37,7 +37,8 @@ reg --end 2009/06/26 -G equities 08-Jan-01 Purchase Apple shares Equities 0 0 08-Jun-30 Commodities revalued $500 $500 08-Jun-30 Sell some Apple sha.. Equities 0 $500 -09-Jun-26 Commodities revalued $750 $1250 +09-Jan-31 Commodities revalued $250 $750 +09-Jun-26 Commodities revalued $500 $1250 >>>2 === 0 reg -I equities -- cgit v1.2.3 From df0edbd2dc416281bffb1fc519aaa3532496d045 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Mon, 31 May 2010 15:13:04 -0600 Subject: Minor optimization --- src/report.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/report.cc b/src/report.cc index dcb56319..90de9a3f 100644 --- a/src/report.cc +++ b/src/report.cc @@ -303,7 +303,8 @@ void report_t::posts_report(post_handler_ptr handler) journal_posts_iterator walker(*session.journal.get()); pass_down_posts(handler, walker); - session.journal->clear_xdata(); + if (! HANDLED(group_by_)) + posts_flusher(*this, handler)(value_t()); } void report_t::generate_report(post_handler_ptr handler) -- cgit v1.2.3 From 651220129219d5581597d0c312ef9fee7b47a358 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Tue, 1 Jun 2010 16:55:24 -0400 Subject: Changed a comment --- src/commodity.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commodity.cc b/src/commodity.cc index 836a4269..b4220354 100644 --- a/src/commodity.cc +++ b/src/commodity.cc @@ -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] = { -- cgit v1.2.3 From d19745afded63276449bb56b7d24c38c7e32d0a7 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Tue, 1 Jun 2010 17:32:41 -0400 Subject: Improve parsing of 'expr' query terms Fixes #157 / 9DF85DF2-4BF5-4931-A30C-2592A10BB5C0 --- src/query.cc | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/query.cc b/src/query.cc index 1f086df8..c79fe1c2 100644 --- a/src/query.cc +++ b/src/query.cc @@ -55,8 +55,9 @@ query_t::lexer_t::token_t query_t::lexer_t::next_token() if (consume_next_arg) { consume_next_arg = false; + token_t tok(token_t::TERM, string(arg_i, arg_end)); arg_i = arg_end; - return token_t(token_t::TERM, (*begin).as_string()); + return tok; } resume: @@ -70,23 +71,25 @@ query_t::lexer_t::token_t query_t::lexer_t::next_token() return next_token(); goto resume; + 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")); -- cgit v1.2.3 From d513c71236b3e91bb999158829250e7194a9d56e Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Tue, 1 Jun 2010 17:40:27 -0400 Subject: Minor optimization --- src/filters.cc | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/filters.cc b/src/filters.cc index 41e8c16b..ad4b88a0 100644 --- a/src/filters.cc +++ b/src/filters.cc @@ -665,10 +665,8 @@ void changed_value_posts::output_intermediate_prices(post_t& post, } // Choose the last price from each day as the price to use - typedef std::map > history_by_date_map; - history_by_date_map all_prices_by_date; + typedef std::map date_map; + date_map pricing_dates; BOOST_REVERSE_FOREACH (const commodity_t::history_map::value_type& price, all_prices) { @@ -676,13 +674,12 @@ void changed_value_posts::output_intermediate_prices(post_t& post, // for that date. DEBUG("filters.revalued", "re-inserting " << price.second << " at " << price.first.date()); - all_prices_by_date.insert(history_by_date_map::value_type - (price.first.date(), price)); + 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 history_by_date_map::value_type& price, all_prices_by_date) { + foreach (const date_map::value_type& price, pricing_dates) { output_revaluation(post, price.first); last_total = repriced_total; } -- cgit v1.2.3 From 038c24357e85b3b33460f3b8d41b1ef4ab0ed901 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Tue, 1 Jun 2010 17:44:28 -0400 Subject: Changed --european option to --decimal-comma Fixes #211 / 1736ACA5-5DE6-4826-AEB4-DB5B2A2217AC --- src/amount.cc | 39 ++++++++++++++++++++------------------- src/commodity.cc | 10 +++++----- src/commodity.h | 26 +++++++++++++------------- src/py_commodity.cc | 26 +++++++++++++------------- src/session.cc | 4 +--- src/session.h | 6 +++--- test/baseline/cmd-print.test | 2 +- test/unit/t_amount.cc | 6 +++--- 8 files changed, 59 insertions(+), 60 deletions(-) diff --git a/src/amount.cc b/src/amount.cc index a16d287e..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 << ','; @@ -1031,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; @@ -1043,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; @@ -1067,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; @@ -1079,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; @@ -1100,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/commodity.cc b/src/commodity.cc index b4220354..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, @@ -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 { 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 - (commodity_t::european_by_default ? - static_cast(COMMODITY_STYLE_EUROPEAN) : + (commodity_t::decimal_comma_by_default ? + static_cast(COMMODITY_STYLE_DECIMAL_COMMA) : static_cast(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/py_commodity.cc b/src/py_commodity.cc index d89a7151..fc7e8c3e 100644 --- a/src/py_commodity.cc +++ b/src/py_commodity.cc @@ -312,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 @@ -334,9 +334,9 @@ void export_commodity() .def("drop_flags", &delegates_flags::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/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::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/test/baseline/cmd-print.test b/test/baseline/cmd-print.test index 759a2334..6099b39f 100644 --- a/test/baseline/cmd-print.test +++ b/test/baseline/cmd-print.test @@ -1,4 +1,4 @@ -print --european +print --decimal-comma <<< 2008/12/31 Market Expenses:Food ($10,00 + $2,50) 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 -- cgit v1.2.3 From 5cdd36f358dcafddd29e7a6c5e0d8210b65bfd79 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Tue, 1 Jun 2010 18:32:27 -0400 Subject: Further improved parsing of query expressions Fixes #210 / D4C2DD6F-8967-4FFC-BBBC-A941F9C53475 --- src/query.cc | 42 ++++++++++++++++++++++++------------------ src/query.h | 27 ++++++++++++++++----------- src/textual.cc | 2 +- test/unit/t_expr.cc | 6 ++---- 4 files changed, 43 insertions(+), 34 deletions(-) diff --git a/src/query.cc b/src/query.cc index c79fe1c2..363c6f73 100644 --- a/src/query.cc +++ b/src/query.cc @@ -53,25 +53,9 @@ query_t::lexer_t::token_t query_t::lexer_t::next_token() } } - if (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 '\'': + case '"': case '/': { string pat; char closing = *arg_i; @@ -95,6 +79,25 @@ query_t::lexer_t::token_t query_t::lexer_t::next_token() 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); @@ -104,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/textual.cc b/src/textual.cc index 2b204df0..9a49edd4 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -526,7 +526,7 @@ void instance_t::automated_xact_directive(char * line) std::auto_ptr 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; 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()); -- cgit v1.2.3 From 281225db1380d7ab3a741820c27c0e20beddc977 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 2 Jun 2010 00:18:50 -0400 Subject: Disable PCH for 'default' builds --- acprep | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/acprep b/acprep index b7c5e177..5959e99a 100755 --- a/acprep +++ b/acprep @@ -1146,7 +1146,8 @@ class PrepareBuild(CommandLineApp): 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') -- cgit v1.2.3 From e0c5f6db30382960fa60739c368e3700e5f71b67 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 2 Jun 2010 00:19:07 -0400 Subject: Fixed two memory reference errors --- src/account.cc | 11 +++-------- src/times.cc | 13 ++++++------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/account.cc b/src/account.cc index 46f37091..8d4341e7 100644 --- a/src/account.cc +++ b/src/account.cc @@ -42,16 +42,11 @@ account_t::~account_t() { TRACE_DTOR(account_t); - foreach (accounts_map::value_type& pair, accounts) + foreach (accounts_map::value_type& pair, accounts) { if (! pair.second->has_flags(ACCOUNT_TEMP) || - has_flags(ACCOUNT_TEMP)) + has_flags(ACCOUNT_TEMP)) { checked_delete(pair.second); - - foreach (post_t * post, posts) { - if (post->account) { - assert(post->account == this); - post->account = NULL; - } + } } } diff --git a/src/times.cc b/src/times.cc index 35082f51..a7906aee 100644 --- a/src/times.cc +++ b/src/times.cc @@ -197,8 +197,6 @@ namespace { optional_year year, date_traits_t * traits = NULL) { - date_t when; - VERIFY(std::strlen(date_str) < 127); char buf[128]; @@ -208,7 +206,7 @@ namespace { if (*p == '.' || *p == '-') *p = '/'; - when = io.parse(buf); + date_t when = io.parse(buf); if (! when.is_not_a_date()) { DEBUG("times.parse", "Passed date string: " << date_str); @@ -216,12 +214,13 @@ namespace { DEBUG("times.parse", "Parsed result is: " << when); DEBUG("times.parse", "Formatted result is: " << io.format(when)); - const char * p = io.format(when).c_str(); + string when_str = io.format(when); + + const char * p = when_str.c_str(); const char * q = buf; - for (; *p != '\0' && *q != '\0'; - p++, q++) { + for (; *p && *q; p++, q++) { if (*p != *q && *p == '0') p++; - if (*p != *q) break; + if (! *p || *p != *q) break; } if (*p != '\0' || *q != '\0') throw_(date_error, _("Invalid date: %1") << date_str); -- cgit v1.2.3 From 8c61ba013f676b60cc016464363edfea0c70c7e3 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 2 Jun 2010 00:27:59 -0400 Subject: Changed two uses of delete to checked_delete() --- src/convert.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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)")); } -- cgit v1.2.3 From fb7cafa8965c89bbd66b09f827bd5989a87c983b Mon Sep 17 00:00:00 2001 From: Martin Michlmayr Date: Wed, 2 Jun 2010 01:15:01 -0400 Subject: @ characters in the manual need to be escaped. --- doc/ledger.texi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/ledger.texi b/doc/ledger.texi index 556c2c45..0a0224dc 100644 --- a/doc/ledger.texi +++ b/doc/ledger.texi @@ -3964,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 -- cgit v1.2.3