From 80058dfe8fb384f6e1529afa810c6a9bd08cdf88 Mon Sep 17 00:00:00 2001 From: "Manuel Amador (Rudd-O)" Date: Sat, 6 Feb 2016 00:45:49 +0000 Subject: Ensure that parse errors produce useful RuntimeErrors for Python code. --- src/context.h | 1 + src/error.h | 5 +++-- src/global.h | 2 +- src/textual.cc | 7 ++++++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/context.h b/src/context.h index 61797dc4..603898f4 100644 --- a/src/context.h +++ b/src/context.h @@ -70,6 +70,7 @@ public: std::size_t errors; std::size_t count; std::size_t sequence; + std::string last; explicit parse_context_t(const path& cwd) : current_directory(cwd), master(NULL), scope(NULL), diff --git a/src/error.h b/src/error.h index 73a0e395..26cbfafb 100644 --- a/src/error.h +++ b/src/error.h @@ -95,8 +95,9 @@ string source_context(const path& file, struct error_count { std::size_t count; - explicit error_count(std::size_t _count) : count(_count) {} - const char * what() const { return ""; } + std::string message; + explicit error_count(std::size_t _count, std::string _msg) : count(_count), message(_msg) {} + const char * what() const { return message.c_str(); } }; } // namespace ledger diff --git a/src/global.h b/src/global.h index 1a4a3076..42464e35 100644 --- a/src/global.h +++ b/src/global.h @@ -166,7 +166,7 @@ See LICENSE file included with the distribution for details and disclaimer."); OPTION_(global_scope_t, version, DO() { // -v parent->show_version_info(std::cout); - throw error_count(0); // exit immediately + throw error_count(0, ""); // exit immediately }); }; diff --git a/src/textual.cc b/src/textual.cc index ccd87ca0..c97b8b01 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -282,6 +282,10 @@ void instance_t::parse() std::cerr << _("Error: ") << err.what() << std::endl; context.errors++; + if (! current_context.empty()) + context.last = current_context + "\n" + err.what(); + else + context.last = err.what(); } } @@ -1998,7 +2002,8 @@ std::size_t journal_t::read_textual(parse_context_stack_t& context_stack) TRACE_FINISH(parsing_total, 1); if (context_stack.get_current().errors > 0) - throw error_count(context_stack.get_current().errors); + throw error_count(context_stack.get_current().errors, + context_stack.get_current().last); return context_stack.get_current().count; } -- cgit v1.2.3 From d300dfefec6210a326bdd12f12be1a93db305dab Mon Sep 17 00:00:00 2001 From: "Manuel Amador (Rudd-O)" Date: Mon, 8 Feb 2016 04:58:01 +0000 Subject: Add parse error test --- test/python/JournalTest.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/python/JournalTest.py b/test/python/JournalTest.py index e65c671d..2565ede8 100644 --- a/test/python/JournalTest.py +++ b/test/python/JournalTest.py @@ -22,6 +22,24 @@ class JournalTestCase(unittest.TestCase): for post in journal.query("food"): self.assertEqual(str(post.account), "Expenses:Food") self.assertEqual(post.amount, Amount("$21.34")) + + def testParseError(self): + # TODO: ledger spits out parse errors to standard out. + # This should not happen, especially when the error + # has already been captured by a Python exception. + def fun(): + read_journal_from_string(""" +2012-03-01 KFC + Expenses:Food rsnetnirsnti + Assets:Cash +""") + self.assertRaises(RuntimeError, fun) + try: + fun() + except RuntimeError as e: + self.assertEquals(str(e).splitlines()[-1], + "No quantity specified for amount") + def suite(): return unittest.TestLoader().loadTestsFromTestCase(JournalTestCase) -- cgit v1.2.3 From 6cbe04ca85453291326a8db5a53a5eb4f1665b68 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Thu, 7 Apr 2016 00:15:09 +0200 Subject: Require correct feature In `ledger-matching.el' require `ledger-report' instead of `ldg-report'. That library was renamed like all the others. --- contrib/raw/ledger-matching.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/raw/ledger-matching.el b/contrib/raw/ledger-matching.el index b8e62dd9..36006a69 100644 --- a/contrib/raw/ledger-matching.el +++ b/contrib/raw/ledger-matching.el @@ -1,6 +1,6 @@ ;; This library is intended to allow me to view a receipt on one panel, and tie it to ledger transactions in another -(require 'ldg-report) +(require 'ledger-report) (defgroup ledger-matching nil "Ledger image matching") -- cgit v1.2.3 From 097abf2be5bb1260b27ce98d66d813f279acf48f Mon Sep 17 00:00:00 2001 From: Rémi Vanicat Date: Sat, 28 May 2016 15:35:09 +0200 Subject: inhibit read only when inserting in *Ledger Error* When ledger-exec-handle-error is called a second time, it try to insert the error in a buffer that is already read-only. inhibit-read-only permit the insertion. --- lisp/ledger-exec.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lisp/ledger-exec.el b/lisp/ledger-exec.el index 4ba9134d..75a05262 100644 --- a/lisp/ledger-exec.el +++ b/lisp/ledger-exec.el @@ -48,7 +48,8 @@ (defun ledger-exec-handle-error (ledger-output) "Deal with ledger errors contained in LEDGER-OUTPUT." (with-current-buffer (get-buffer-create "*Ledger Error*") - (insert-buffer-substring ledger-output) + (let ((inhibit-read-only t)) + (insert-buffer-substring ledger-output)) (view-mode) (setq buffer-read-only t))) -- cgit v1.2.3 From 600cf52fec866d1583a0cb1203556b8058b554ca Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sun, 28 Jan 2018 11:49:26 -0800 Subject: Remove empty .gitmodules file --- .gitmodules | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .gitmodules diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29b..00000000 -- cgit v1.2.3 From 38d7deeac1954fbecf27ceafc27e954b170df1bb Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Tue, 30 Jan 2018 13:53:22 -0800 Subject: Add Gitter integration --- .travis.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.travis.yml b/.travis.yml index 4def903d..def366f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -116,3 +116,10 @@ notifications: channels: [ "chat.freenode.net#ledger" ] on_success: change on_failure: change + webhooks: + urls: + - https://webhooks.gitter.im/e/14c934f7d05d41d63859 + on_success: change # options: [always|never|change] default: always + on_failure: always # options: [always|never|change] default: always + on_start: never # options: [always|never|change] default: always + -- cgit v1.2.3 From 017ed89fc4d618f2db091ae047edc12cb446a4d3 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Tue, 30 Jan 2018 14:03:13 -0800 Subject: Add Gitter badge to README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cb7ba18c..3498a2d6 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ +[![Join the chat at https://gitter.im/use-package/Lobby](https://badges.gitter.im/use-package/Lobby.svg)](https://gitter.im/use-package/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status master](https://img.shields.io/travis/ledger/ledger/master.svg?label=master&style=flat)](https://travis-ci.org/ledger/ledger) [![Build Status next](https://img.shields.io/travis/ledger/ledger/next.svg?label=next&style=flat)](https://travis-ci.org/ledger/ledger) [![Status](https://img.shields.io/badge/status-active-brightgreen.svg?style=flat)](https://github.com/ledger/ledger/pulse/monthly) [![License](https://img.shields.io/badge/license-BSD-blue.svg?style=flat)](http://opensource.org/licenses/BSD-3-Clause) [![GitHub release](https://img.shields.io/github/release/ledger/ledger.svg?style=flat)](https://github.com/ledger/ledger/releases) - # Ledger: Command-Line Accounting Ledger is a powerful, double-entry accounting system that is accessed from the -- cgit v1.2.3 From a7698247740727f0d18049e53d900984880c8ded Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Tue, 30 Jan 2018 14:05:36 -0800 Subject: Correct webhook URL --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index def366f2..94489bdc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -118,7 +118,7 @@ notifications: on_failure: change webhooks: urls: - - https://webhooks.gitter.im/e/14c934f7d05d41d63859 + - https://webhooks.gitter.im/e/0050d91909a8cde39e35 on_success: change # options: [always|never|change] default: always on_failure: always # options: [always|never|change] default: always on_start: never # options: [always|never|change] default: always -- cgit v1.2.3 From cd5afb60f79b2263f415098cf279eacef442cf7c Mon Sep 17 00:00:00 2001 From: Eduardo Elias Date: Wed, 31 Jan 2018 23:56:21 +0000 Subject: Remove unused imports --- acprep | 4 ---- 1 file changed, 4 deletions(-) diff --git a/acprep b/acprep index d1c24388..8d9d67f7 100755 --- a/acprep +++ b/acprep @@ -12,11 +12,7 @@ import optparse import os import re import shutil -import string import sys -import time -import tempfile -import datetime try: import hashlib -- cgit v1.2.3 From 7097e1a1e7ea4f4890b89ab7164e0fa10a3ca586 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Fri, 16 Feb 2018 16:23:56 -0800 Subject: Update documentation --- CONTRIBUTING.md | 5 ++--- INSTALL.md | 8 ++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 79e97296..80a413f3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ Tips for contributors branch and is subsequently merged into `master` for releases. * If you're making **changes to files for which the Travis build is not relevant**, please **add `[ci skip]` to the end of the commit message**. -* Report bugs using [Bugzilla]. If you want, you can access the bug system using the lite (free) version of [Deskzilla]. +* Report bugs using [GitHub Issues]. GLOSSARY ---- @@ -119,8 +119,7 @@ cores: [Boost]: http://boost.org [Boost.Python]: http://www.boost.org/libs/python/ -[Bugzilla]: http://bugs.ledger-cli.org/ -[Deskzilla]: http://almworks.com/deskzilla/download.html +[GitHub Issues]: https://github.com/ledger/ledger/issues [GMP]: http://gmplib.org/ [MPFR]: http://www.mpfr.org/ [Cheetah]: http://www.cheetahtemplate.org diff --git a/INSTALL.md b/INSTALL.md index 62de2a97..23d0566b 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -96,10 +96,10 @@ A: Actually, the real segfault is in libstdc++'s facet code. It's being Q: Something else fails, or Ledger crashes on startup -A: This, I am most interested in hearing about. Please file a bug - at the Ledger Bugzilla, http://bugs.ledger-cli.org/. The more - details you can provide, the better. Also, if Ledger is crashing, - try running it under gdb like so: +A: This, I am most interested in hearing about. Please file a bug at the + Ledger Issue Tracker, https://github.com/ledger/ledger/issues. The more + details you can provide, the better. Also, if Ledger is crashing, try + running it under gdb like so: $ gdb ledger (gdb) run -- cgit v1.2.3 From fbccb7149eae348182bef7ac9dcba756fe72381d Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Fri, 16 Feb 2018 23:03:44 -0800 Subject: Add (currently undocumented) function 'clear_commodity' --- src/report.cc | 9 +++++++++ src/report.h | 1 + 2 files changed, 10 insertions(+) diff --git a/src/report.cc b/src/report.cc index cb7f09dc..13e6a61f 100644 --- a/src/report.cc +++ b/src/report.cc @@ -841,6 +841,13 @@ value_t report_t::fn_commodity(call_scope_t& args) return string_value(args.get(0).commodity().symbol()); } +value_t report_t::fn_clear_commodity(call_scope_t& args) +{ + amount_t amt(args.get(0)); + amt.clear_commodity(); + return amt; +} + value_t report_t::fn_nail_down(call_scope_t& args) { value_t arg0(args[0]); @@ -1384,6 +1391,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return MAKE_FUNCTOR(report_t::fn_commodity); else if (is_eq(p, "ceiling")) return MAKE_FUNCTOR(report_t::fn_ceiling); + else if (is_eq(p, "clear_commodity")) + return MAKE_FUNCTOR(report_t::fn_clear_commodity); break; case 'd': diff --git a/src/report.h b/src/report.h index 635c3887..1bda0407 100644 --- a/src/report.h +++ b/src/report.h @@ -175,6 +175,7 @@ public: value_t fn_truncated(call_scope_t& scope); value_t fn_floor(call_scope_t& scope); value_t fn_ceiling(call_scope_t& scope); + value_t fn_clear_commodity(call_scope_t& scope); value_t fn_round(call_scope_t& scope); value_t fn_roundto(call_scope_t& scope); value_t fn_unround(call_scope_t& scope); -- cgit v1.2.3 From 4bc6db4abc83a65c822a83588189fa9db39754f1 Mon Sep 17 00:00:00 2001 From: Tim Landscheidt Date: Mon, 19 Feb 2018 21:38:39 +0000 Subject: Fix warnings for -Wimplicit-fallthrough --- src/error.h | 2 +- src/mask.cc | 3 +-- src/query.cc | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/error.h b/src/error.h index 88e09329..a628c1e9 100644 --- a/src/error.h +++ b/src/error.h @@ -47,7 +47,7 @@ namespace ledger { extern std::ostringstream _desc_buffer; template -inline void throw_func(const string& message) { +[[ noreturn ]] inline void throw_func(const string& message) { _desc_buffer.clear(); _desc_buffer.str(""); throw T(message); diff --git a/src/mask.cc b/src/mask.cc index 434acad6..35e690de 100644 --- a/src/mask.cc +++ b/src/mask.cc @@ -75,9 +75,8 @@ mask_t& mask_t::assign_glob(const string& pat) if (i + 1 < len) { re_pat += pat[++i]; break; - } else { - // fallthrough... } + // fallthrough... default: re_pat += pat[i]; break; diff --git a/src/query.cc b/src/query.cc index fc1d4ff0..883bea40 100644 --- a/src/query.cc +++ b/src/query.cc @@ -155,6 +155,7 @@ query_t::lexer_t::next_token(query_t::lexer_t::token_t::kind_t tok_context) case ')': if (! consume_next && tok_context == token_t::TOK_EXPR) goto test_ident; + // fall through... case '(': case '&': case '|': -- cgit v1.2.3 From a534869733efd57dffeb19a3d52b23eca75ee80c Mon Sep 17 00:00:00 2001 From: DJ Edmonson Date: Fri, 23 Mar 2018 15:04:30 -0700 Subject: Fix auto xact posts not getting applied to account total durring journal parse --- src/xact.cc | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/xact.cc b/src/xact.cc index d29072d4..c62974df 100644 --- a/src/xact.cc +++ b/src/xact.cc @@ -396,9 +396,9 @@ bool xact_base_t::finalize() } if (post->has_flags(POST_DEFERRED)) - post->account->add_deferred_post(id(), post); - else - post->account->add_post(post); + post->account->add_deferred_post(id(), post); + else + post->account->add_post(post); post->xdata().add_flags(POST_EXT_VISITED); post->account->xdata().add_flags(ACCOUNT_EXT_VISITED); @@ -806,6 +806,9 @@ void auto_xact_t::extend_xact(xact_base_t& xact, parse_context_t& context) xact.add_post(new_post); new_post->account->add_post(new_post); + // Add flag so this post updates the account balance + new_post->xdata().add_flags(POST_EXT_VISITED); + if (new_post->must_balance()) needs_further_verification = true; } -- cgit v1.2.3 From 8d38190409a68f2c8da572d33eaaf93a17c0f550 Mon Sep 17 00:00:00 2001 From: Martin Michlmayr Date: Sun, 25 Mar 2018 10:11:56 +0200 Subject: Fix documentation about the "end" directive "tag" is not a block command. This was probably supposed to be "apply" which can be used to apply tags to several transactions. --- doc/ledger3.texi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ledger3.texi b/doc/ledger3.texi index a955be9a..b91a4214 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -2436,7 +2436,7 @@ The posting will have a cost of $400. @item end @findex end @c instance_t::end_directive in textual.cc -Closes block commands like @code{tag} or @code{comment}. +Closes block commands like @code{apply} or @code{comment}. @item expr @findex expr -- cgit v1.2.3 From 44a885c5221253ee5809f076ebbe5f17d0c86e7c Mon Sep 17 00:00:00 2001 From: Martin Michlmayr Date: Sun, 25 Mar 2018 10:18:48 +0200 Subject: Fix Python example Thanks to Brian Carlson for pointing this out. Fixes #547 --- doc/ledger3.texi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ledger3.texi b/doc/ledger3.texi index b91a4214..0151f267 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -9218,7 +9218,7 @@ looking at its @code{xact} member: last_xact = None for post in ledger.read_journal("sample.dat").query(""): if post.xact != last_xact: - for post in post.xact.posts: + for post in post.xact.posts(): print "Transferring %s to/from %s" % (post.amount, post.account) last_xact = post.xact -- cgit v1.2.3 From a9859bc6d131afe564adaf0b1628cee75c4bead3 Mon Sep 17 00:00:00 2001 From: Martin Michlmayr Date: Thu, 29 Mar 2018 17:33:30 +0200 Subject: Update Debian releases in README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3498a2d6..0aa634b4 100644 --- a/README.md +++ b/README.md @@ -145,9 +145,9 @@ Or, for Ubuntu 12.04: Debian squeeze (6.0): the version of boost in squeeze is too old for ledger and unfortunately no backport is available at the moment. -Debian 7 (wheezy), Debian 8 (jessie), Debian testing (stretch) and Debian -unstable (sid) contain all components needed to build ledger. You can -install all required build dependencies using the following command: +Debian 7 (wheezy), Debian 8 (jessie), Debian 9 (stretch), Debian testing +and Debian unstable (sid) contain all components needed to build ledger. +You can install all required build dependencies using the following command: $ sudo apt-get install build-essential cmake autopoint texinfo python-dev \ zlib1g-dev libbz2-dev libgmp3-dev gettext libmpfr-dev \ -- cgit v1.2.3 From 48edf1eb5c33e75e9db4ed50d16abe1e57d61ed0 Mon Sep 17 00:00:00 2001 From: smr894 Date: Wed, 4 Apr 2018 23:30:38 -0400 Subject: budget_posts: Keep pending items until the last day they apply --- src/filters.cc | 25 ++++--- .../fix-missing-trans-in-last-budget-period.test | 79 ++++++++++++++++++++++ 2 files changed, 96 insertions(+), 8 deletions(-) create mode 100644 test/regress/fix-missing-trans-in-last-budget-period.test diff --git a/src/filters.cc b/src/filters.cc index 4e9e633a..3dfd2327 100644 --- a/src/filters.cc +++ b/src/filters.cc @@ -1245,19 +1245,34 @@ void generate_posts::add_post(const date_interval_t& period, post_t& post) void budget_posts::report_budget_items(const date_t& date) { + { // Cleanup pending items that finished before date + // We have to keep them until the last day they apply because operator() needs them to see if a + // posting is budgeted or not + std::list posts_to_erase; + for (pending_posts_list::iterator i = pending_posts.begin(); i != pending_posts.end(); i++) { + pending_posts_list::value_type& pair(*i); + if (pair.first.finish && ! pair.first.start && pair.first.finish < date) { + posts_to_erase.push_back(i); + } + } + foreach (pending_posts_list::iterator& i, posts_to_erase) + pending_posts.erase(i); + } + if (pending_posts.size() == 0) return; bool reported; do { - std::list posts_to_erase; - reported = false; for (pending_posts_list::iterator i = pending_posts.begin(); i != pending_posts.end(); i++) { pending_posts_list::value_type& pair(*i); + if (pair.first.finish && ! pair.first.start) + continue; // skip expired posts + optional begin = pair.first.start; if (! begin) { optional range_begin; @@ -1285,9 +1300,6 @@ void budget_posts::report_budget_items(const date_t& date) post_t& post = *pair.second; ++pair.first; - if (! pair.first.start) - posts_to_erase.push_back(i); - DEBUG("budget.generate", "Reporting budget for " << post.reported_account()->fullname()); @@ -1312,9 +1324,6 @@ void budget_posts::report_budget_items(const date_t& date) reported = true; } } - - foreach (pending_posts_list::iterator& i, posts_to_erase) - pending_posts.erase(i); } while (reported); } diff --git a/test/regress/fix-missing-trans-in-last-budget-period.test b/test/regress/fix-missing-trans-in-last-budget-period.test new file mode 100644 index 00000000..163a0540 --- /dev/null +++ b/test/regress/fix-missing-trans-in-last-budget-period.test @@ -0,0 +1,79 @@ += ~ ^A + [Balance] 1 + [Budget:$account] -1 + +~ Monthly from 2014/01 to 2014/12/31 + [Budget:A] 100.00 USD + [Balance] + +~ Monthly from 2014/01 to 2014/12/31 + [Budget:Z] 100.00 USD + [Balance] + +2014/10/01 toto0 + [Budget:A:B] 0.01 USD + [Balance] + +2014/11/01 toto1 + A:B 51.00 USD + Cash + +2014/11/02 toto2 + A:B 52.00 USD + Cash + +2014/11/03 toto3 + A:B 53.00 USD + Cash + +2014/11/04 toto4 + A:B 54.00 USD + Cash + +2014/12/08 toto5 + A:B 55.00 USD + Cash + +2014/12/09 toto6 + A:B 56.00 USD + Cash + +2014/12/10 toto7 + A:B 57.00 USD + Cash + +2014/12/11 toto8 + A:B 58.00 USD + Cash + +2014/12/12 toto9 + A:B 59.00 USD + Cash + +2014/12/12 toto9 + C 59.00 USD + Cash + +2015/01/12 toto10 + A:B 59.00 USD + Cash + +test reg --budget -b 2014/10 -e 2015/02 --columns 80 --date-format "%F" reg ^Bu +2014-10-01 Budget transaction [Budget:A] -100.00 USD -100.00 USD +2014-10-01 Budget transaction [Budget:Z] -100.00 USD -200.00 USD +2014-10-01 toto0 [Budget:A] 0.01 USD -199.99 USD +2014-11-01 Budget transaction [Budget:A] -100.00 USD -299.99 USD +2014-11-01 Budget transaction [Budget:Z] -100.00 USD -399.99 USD +2014-11-01 toto1 [Budget:A] -51.00 USD -450.99 USD +2014-11-02 toto2 [Budget:A] -52.00 USD -502.99 USD +2014-11-03 toto3 [Budget:A] -53.00 USD -555.99 USD +2014-11-04 toto4 [Budget:A] -54.00 USD -609.99 USD +2014-12-01 Budget transaction [Budget:A] -100.00 USD -709.99 USD +2014-12-01 Budget transaction [Budget:Z] -100.00 USD -809.99 USD +2014-12-08 toto5 [Budget:A] -55.00 USD -864.99 USD +2014-12-09 toto6 [Budget:A] -56.00 USD -920.99 USD +2014-12-10 toto7 [Budget:A] -57.00 USD -977.99 USD +2014-12-11 toto8 [Budget:A] -58.00 USD -1035.99 USD +2014-12-12 toto9 [Budget:A] -59.00 USD -1094.99 USD +2015-01-12 toto10 [Budget:A] -59.00 USD -1153.99 USD +end test -- cgit v1.2.3 From 15f888f83014d41709a5b490f9df1e77fbb8b8c6 Mon Sep 17 00:00:00 2001 From: Vishesh Handa Date: Sun, 8 Apr 2018 01:45:15 +0200 Subject: Add documentation on how to sort in reverse order --- doc/ledger.1 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/ledger.1 b/doc/ledger.1 index 85a9937b..fc4dd3c0 100644 --- a/doc/ledger.1 +++ b/doc/ledger.1 @@ -214,6 +214,9 @@ Note that a comma-separated list of expressions is allowed, in which case each sorting term is used in order to determine the final ordering. For example, to search by date and then amount, one would use: .Dl ledger reg --sort 'date, amount' +The sort order may be controlled with the '-' sign. For example, to sort in +reverse chronological order: +.Dl ledger reg --sort '-date' .It Fl \-tail Ar number Only show the last .Ar number -- cgit v1.2.3 From 882937ece23a129814b2d8b7b89c2db74e34f39e Mon Sep 17 00:00:00 2001 From: Martin Michlmayr Date: Fri, 1 Jun 2018 13:27:36 +0200 Subject: Remove references to Bugzilla --- .dir-locals.el | 2 +- .../cash-receipts-and-disbursments-journals.plx | 2 +- doc/ledger3.texi | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.dir-locals.el b/.dir-locals.el index de699515..34761815 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -4,7 +4,7 @@ ((nil (tab-width . 2) (sentence-end-double-space . t) - (bug-reference-url-format . "http://bugs.ledger-cli.org/show_bug.cgi?id=%s")) + (bug-reference-url-format . "https://github.com/ledger/ledger/issues/%s")) (c-mode (c-file-style . "ledger") (c-style-alist diff --git a/contrib/non-profit-audit-reports/cash-receipts-and-disbursments-journals.plx b/contrib/non-profit-audit-reports/cash-receipts-and-disbursments-journals.plx index 937d2a45..f2055a62 100755 --- a/contrib/non-profit-audit-reports/cash-receipts-and-disbursments-journals.plx +++ b/contrib/non-profit-audit-reports/cash-receipts-and-disbursments-journals.plx @@ -127,7 +127,7 @@ foreach my $typeData ({ name => 'disbursements', query => 'a<=0' }, # I thought '--sort', 'd', '--sort-xact', 'a', should # have worked below for a good sort. Then I tried # rather than '--sort', "d,n,a", which didn't work either. - # I opened a bug: http://bugs.ledger-cli.org/show_bug.cgi?id=901 + # I opened a bug: https://github.com/ledger/ledger/issues/901 my @csvRegLedgerOpts = ('-f', $tempFile, '-V', '-F', $formatString, '-w', '--sort', 'd', '-b', $beginDate, '-e', $endDate, 'reg'); diff --git a/doc/ledger3.texi b/doc/ledger3.texi index 0151f267..9eaa6846 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -2482,7 +2482,7 @@ directives, as @code{fixed} is closed with an @code{endfixed} (i.e., there is @emph{no space} between @code{end} and @code{fixed}). For the moment, users may wish to study -@uref{http://bugs.ledger-cli.org/show_bug.cgi?id=789, Bug Report 789} +@uref{https://github.com/ledger/ledger/issues/789, Bug Report 789} before using the @code{fixed} directive in production. @item include -- cgit v1.2.3 From e3fa7e9d89c17c69a163dfb031436ca8830f6562 Mon Sep 17 00:00:00 2001 From: Martin Michlmayr Date: Fri, 1 Jun 2018 17:36:49 +0200 Subject: Fix documentation for apply fixed The old "fixed" and "endfixed" are no longer used (and afaict don't work). You have to use "apply fixed" and "end apply" instead. Fixes issue #789 Signed-off-by: Martin Michlmayr --- doc/ledger3.texi | 78 +++++++++++++++++++++++++------------------------------- 1 file changed, 35 insertions(+), 43 deletions(-) diff --git a/doc/ledger3.texi b/doc/ledger3.texi index 9eaa6846..f3c2045c 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -2255,6 +2255,41 @@ Would result in all postings going into @samp{Personal:Expenses:Groceries} and @samp{Personal:Assets:Checking} until an @samp{end apply account} directive was found. +@item apply fixed +@findex fixed +@cindex fixated prices +@c instance_t::fixed_directive in textual.cc + +A fixed block is used to set fixated prices (@pxref{Fixated prices and +costs}) for a series of transactions. It's purely a typing saver, for +use when entering many transactions with fixated prices. + +Thus, the following: + +@smallexample @c input:validate +apply fixed CAD $0.90 +2012-04-10 Lunch in Canada + Assets:Wallet -15.50 CAD + Expenses:Food 15.50 CAD + +2012-04-11 Second day Dinner in Canada + Assets:Wallet -25.75 CAD + Expenses:Food 25.75 CAD +end apply fixed +@end smallexample + +is equivalent to this: + +@smallexample @c input:validate +2012-04-10 Lunch in Canada + Assets:Wallet -15.50 CAD @{=$0.90@} + Expenses:Food 15.50 CAD @{=$0.90@} + +2012-04-11 Second day Dinner in Canada + Assets:Wallet -25.75 CAD @{=$0.90@} + Expenses:Food 25.75 CAD @{=$0.90@} +@end smallexample + @item alias @findex alias @cindex account, alias @@ -2442,49 +2477,6 @@ Closes block commands like @code{apply} or @code{comment}. @findex expr @c instance_t::expr_directive in textual.cc -@item fixed -@findex fixed -@cindex fixated prices -@c instance_t::fixed_directive in textual.cc - -A fixed block is used to set fixated prices (@pxref{Fixated prices and -costs}) for a series of transactions. It's purely a typing saver, for -use when entering many transactions with fixated prices. - -Thus, the following: - -@smallexample @c input:validate -fixed CAD $0.90 -2012-04-10 Lunch in Canada - Assets:Wallet -15.50 CAD - Expenses:Food 15.50 CAD - -2012-04-11 Second day Dinner in Canada - Assets:Wallet -25.75 CAD - Expenses:Food 25.75 CAD -endfixed CAD -@end smallexample - -is equivalent to this: - -@smallexample @c input:validate -2012-04-10 Lunch in Canada - Assets:Wallet -15.50 CAD @{=$0.90@} - Expenses:Food 15.50 CAD @{=$0.90@} - -2012-04-11 Second day Dinner in Canada - Assets:Wallet -25.75 CAD @{=$0.90@} - Expenses:Food 25.75 CAD @{=$0.90@} -@end smallexample - -Note that ending a @code{fixed} is done differently than other -directives, as @code{fixed} is closed with an @code{endfixed} (i.e., -there is @emph{no space} between @code{end} and @code{fixed}). - -For the moment, users may wish to study -@uref{https://github.com/ledger/ledger/issues/789, Bug Report 789} -before using the @code{fixed} directive in production. - @item include @findex include @c instance_t::include_directive in textual.cc -- cgit v1.2.3 From 6853464fd0c4a0428540edc635f1257cfb534bcd Mon Sep 17 00:00:00 2001 From: Christoph Dittmann Date: Sat, 9 Jun 2018 21:57:39 +0100 Subject: Make automated transactions work with assertions This fixes issue #1127. In my understanding, PR #552 was meant to fix this, but was incomplete. Without this patch, automated transactions are invisible to assertions. This patch fixes this by adding a flag to the account to tell it that there is a new posting, analogous to the behavior of finalize(). I dug up issue #1127 too late to find that this is the same solution proposed by @tbm. Although I wrote this independently, credit goes to Martin Michlmayr (@tbm). --- src/xact.cc | 3 ++- test/regress/1127.test | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 test/regress/1127.test diff --git a/src/xact.cc b/src/xact.cc index c62974df..5df9ebc5 100644 --- a/src/xact.cc +++ b/src/xact.cc @@ -806,8 +806,9 @@ void auto_xact_t::extend_xact(xact_base_t& xact, parse_context_t& context) xact.add_post(new_post); new_post->account->add_post(new_post); - // Add flag so this post updates the account balance + // Add flags so this post updates the account balance new_post->xdata().add_flags(POST_EXT_VISITED); + new_post->account->xdata().add_flags(ACCOUNT_EXT_VISITED); if (new_post->must_balance()) needs_further_verification = true; diff --git a/test/regress/1127.test b/test/regress/1127.test new file mode 100644 index 00000000..905401f8 --- /dev/null +++ b/test/regress/1127.test @@ -0,0 +1,15 @@ +; Test that automated transactions are added to accounts soon enough +; for assertions to work. + += expr account =~ /^Assets/ + (Foo) 1 + +2018-06-09 Something + Assets $100 + Equity + +2018-06-09 Assert amount added by automated transaction + [Foo] = $100 +test bal Foo + $100 Foo +end test -- cgit v1.2.3 From d4bd42791e1666c4079361f458bfd38dcbbfc9bd Mon Sep 17 00:00:00 2001 From: Colin Dean Date: Sun, 10 Jun 2018 00:18:34 -0400 Subject: Documents commodity directive's alias sub-directive --- doc/ledger3.texi | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/ledger3.texi b/doc/ledger3.texi index f3c2045c..0248844c 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -2437,6 +2437,7 @@ commodity $ note American Dollars format $1,000.00 nomarket + alias USD default @end smallexample @@ -2451,6 +2452,9 @@ provide the ``canonical'' representation. The @code{nomarket} sub-directive states that the commodity's price should never be auto-downloaded. +The @code{alias} sub-directive states that any commodity matching this +symbol is to use the commodity declared in this block. + The @code{default} sub-directive marks this as the ``default'' commodity. @item define -- cgit v1.2.3 From 86a23cd263a2e67351a3d748ffc69d65f4746184 Mon Sep 17 00:00:00 2001 From: Christoph Dittmann Date: Sun, 10 Jun 2018 13:03:53 +0100 Subject: Remove TOK_A_YEAR token This fixes #1626. The tokenizer eagerly classifies 4-digit integers as TOK_A_YEAR tokens. In some contexts such as "every 1000 years", this causes errors. I think the tokenizer does not have enough information available to distinguish between integers and years. After this patch, the tokenizer will always classify integers as TOK_INT tokens. The "has 4 digits" heuristic to determine if an integer is a year is moved to the place where it's actually needed (and it can be slightly more generic there, too). --- src/times.cc | 31 ++++++++----------------------- test/regress/1626.test | 28 ++++++++++++++++++++++++++++ test/regress/7F3650FD.test | 2 +- test/regress/BBFA1759.test | 2 +- 4 files changed, 38 insertions(+), 25 deletions(-) create mode 100644 test/regress/1626.test diff --git a/src/times.cc b/src/times.cc index 8e4df020..eda71ae7 100644 --- a/src/times.cc +++ b/src/times.cc @@ -420,7 +420,6 @@ class date_parser_t TOK_DASH, TOK_DOT, - TOK_A_YEAR, TOK_A_MONTH, TOK_A_WDAY, @@ -512,9 +511,6 @@ class date_parser_t case TOK_SLASH: return "/"; case TOK_DASH: return "-"; case TOK_DOT: return "."; - case TOK_A_YEAR: - out << boost::get(*value); - break; case TOK_A_MONTH: out << date_specifier_t::month_type (boost::get(*value)); @@ -566,7 +562,6 @@ class date_parser_t case TOK_SLASH: out << "TOK_SLASH"; break; case TOK_DASH: out << "TOK_DASH"; break; case TOK_DOT: out << "TOK_DOT"; break; - case TOK_A_YEAR: out << "TOK_A_YEAR"; break; case TOK_A_MONTH: out << "TOK_A_MONTH"; break; case TOK_A_WDAY: out << "TOK_A_WDAY"; break; case TOK_AGO: out << "TOK_AGO"; break; @@ -727,7 +722,11 @@ void date_parser_t::determine_when(date_parser_t::lexer_t::token_t& tok, when += gregorian::days(amount * adjust); break; default: - specifier.day = date_specifier_t::day_type(amount); + if (amount > 31) { + specifier.year = date_specifier_t::year_type(amount); + } else { + specifier.day = date_specifier_t::day_type(amount); + } break; } @@ -832,16 +831,13 @@ void date_parser_t::determine_when(date_parser_t::lexer_t::token_t& tok, break; } - case lexer_t::token_t::TOK_A_YEAR: - specifier.year = boost::get(*tok.value); - break; case lexer_t::token_t::TOK_A_MONTH: specifier.month = date_specifier_t::month_type (boost::get(*tok.value)); tok = lexer.peek_token(); switch (tok.kind) { - case lexer_t::token_t::TOK_A_YEAR: + case lexer_t::token_t::TOK_INT: specifier.year = boost::get(*tok.value); break; case lexer_t::token_t::END_REACHED: @@ -898,12 +894,6 @@ date_interval_t date_parser_t::parse() determine_when(tok, *inclusion_specifier); break; - case lexer_t::token_t::TOK_A_YEAR: - if (! inclusion_specifier) - inclusion_specifier = date_specifier_t(); - determine_when(tok, *inclusion_specifier); - break; - case lexer_t::token_t::TOK_A_MONTH: if (! inclusion_specifier) inclusion_specifier = date_specifier_t(); @@ -1612,13 +1602,8 @@ date_parser_t::lexer_t::token_t date_parser_t::lexer_t::next_token() if (! term.empty()) { if (std::isdigit(term[0])) { - if (term.length() == 4) - return token_t(token_t::TOK_A_YEAR, - token_t::content_t - (lexical_cast(term))); - else - return token_t(token_t::TOK_INT, - token_t::content_t(lexical_cast(term))); + return token_t(token_t::TOK_INT, + token_t::content_t(lexical_cast(term))); } else if (std::isalpha(term[0])) { to_lower(term); diff --git a/test/regress/1626.test b/test/regress/1626.test new file mode 100644 index 00000000..89ae80f8 --- /dev/null +++ b/test/regress/1626.test @@ -0,0 +1,28 @@ +test period every 1000 years from 1 Sep 2011 to 30 May 2012 --now=2018-06-10 +--- Period expression tokens --- +TOK_EVERY: every +TOK_INT: 1000 +TOK_YEARS: years +TOK_SINCE: since +TOK_INT: 1 +TOK_A_MONTH: Sep +TOK_INT: 2011 +TOK_UNTIL: until +TOK_INT: 30 +TOK_A_MONTH: May +TOK_INT: 2012 +END_REACHED: + +--- Before stabilization --- + range: from day 1 to day 30 +duration: 1000 years + +--- After stabilization --- + range: from day 1 to day 30 + start: 18-Jan-01 + finish: 18-Jan-30 +duration: 1000 years + +--- Sample dates in range (max. 20) --- + 1: 18-Jan-01 -- 18-Jan-29 +end test diff --git a/test/regress/7F3650FD.test b/test/regress/7F3650FD.test index f0498ddb..0ccfe644 100644 --- a/test/regress/7F3650FD.test +++ b/test/regress/7F3650FD.test @@ -68,7 +68,7 @@ end test test period --now=2010/11/01 2009 --- Period expression tokens --- -TOK_A_YEAR: 2009 +TOK_INT: 2009 END_REACHED: --- Before stabilization --- diff --git a/test/regress/BBFA1759.test b/test/regress/BBFA1759.test index 7a402d0c..5df7ecb2 100644 --- a/test/regress/BBFA1759.test +++ b/test/regress/BBFA1759.test @@ -2,7 +2,7 @@ test period june 2008 --- Period expression tokens --- TOK_A_MONTH: Jun -TOK_A_YEAR: 2008 +TOK_INT: 2008 END_REACHED: --- Before stabilization --- -- cgit v1.2.3 From 850a2cec794ef0d98dbaaf83e5ddce4ecb819e27 Mon Sep 17 00:00:00 2001 From: Tommi Komulainen Date: Sat, 26 May 2018 09:36:45 +0200 Subject: Fix garbled dates when using --date-format Capture the `std::string` value from options in a local variable that lives as long as the `c_str()` taken from it to ensure it does not get freed prematurely. Fixes: #546 --- src/print.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/print.cc b/src/print.cc index 9fa75eab..92323777 100644 --- a/src/print.cc +++ b/src/print.cc @@ -103,11 +103,13 @@ namespace { void print_xact(report_t& report, std::ostream& out, xact_t& xact) { format_type_t format_type = FMT_WRITTEN; + string format_str; optional format; if (report.HANDLED(date_format_)) { format_type = FMT_CUSTOM; - format = report.HANDLER(date_format_).str().c_str(); + format_str = report.HANDLER(date_format_).str(); + format = format_str.c_str(); } std::ostringstream buf; -- cgit v1.2.3 From dfd807f3a491dbcb386ff4b7a9b6bfe2bd7fc6c6 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Mon, 18 Jun 2018 10:55:40 -0700 Subject: Revert "Use an std::set instead of an std::list to store the the journal file paths" This reverts commit 3364850cc7cf73fc67af1ea6cd9a65a32e336623. Fixes #559 --- src/session.cc | 4 ++-- src/session.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/session.cc b/src/session.cc index e95123e8..427850d9 100644 --- a/src/session.cc +++ b/src/session.cc @@ -76,7 +76,7 @@ std::size_t session_t::read_data(const string& master_account) file = path(home_var) / ".ledger"; if (! file.empty() && exists(file)) - HANDLER(file_).data_files.insert(file); + HANDLER(file_).data_files.push_back(file); else throw_(parse_error, "No journal file was specified (please use -f)"); @@ -214,7 +214,7 @@ journal_t * session_t::read_journal_files() journal_t * session_t::read_journal(const path& pathname) { HANDLER(file_).data_files.clear(); - HANDLER(file_).data_files.insert(pathname); + HANDLER(file_).data_files.push_back(pathname); return read_journal_files(); } diff --git a/src/session.h b/src/session.h index 47732a3d..4dce3816 100644 --- a/src/session.h +++ b/src/session.h @@ -153,14 +153,14 @@ public: OPTION__ (session_t, file_, // -f - std::set data_files; + std::list data_files; CTOR(session_t, file_) {} DO_(str) { if (parent->flush_on_next_data_file) { data_files.clear(); parent->flush_on_next_data_file = false; } - data_files.insert(str); + data_files.push_back(str); }); OPTION_(session_t, input_date_format_, DO_(str) { -- cgit v1.2.3 From 67578afa21630b19410a7e729005bf2b17fb9200 Mon Sep 17 00:00:00 2001 From: Naga Kiran Date: Mon, 25 Jun 2018 16:06:11 +0530 Subject: Timelog: Not able to check-in to multiple accounts at a time Checking-in to multiple accounts at a time throws the following error "When multiple check-ins are active, checking out requires an account" Issue is that the acount name was sent as 3rd parameter to time_xact_t constructor whereas it is supposed to be sent as 4th parameter Corrected the argument position of account name in constructor call to time_xact_t --- src/timelog.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/timelog.cc b/src/timelog.cc index 2a618afd..5b289a34 100644 --- a/src/timelog.cc +++ b/src/timelog.cc @@ -170,7 +170,7 @@ void time_log_t::close() foreach (account_t * account, accounts) { DEBUG("timelog", "Clocking out from account " << account->fullname()); context.count += clock_out_from_timelog - (time_xacts, time_xact_t(none, CURRENT_TIME(), account), context); + (time_xacts, time_xact_t(none, CURRENT_TIME(), false, account), context); } assert(time_xacts.empty()); } -- cgit v1.2.3 From 34a4929e2efb01eb5cd50c825cb2ea0d96b569c3 Mon Sep 17 00:00:00 2001 From: Martin Michlmayr Date: Thu, 28 Jun 2018 20:34:33 +0200 Subject: Document the use of wildcards for !include Fixes #1658 --- doc/ledger3.texi | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/ledger3.texi b/doc/ledger3.texi index 0248844c..c63ee218 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -2484,7 +2484,9 @@ Closes block commands like @code{apply} or @code{comment}. @item include @findex include @c instance_t::include_directive in textual.cc -Include the stated file as if it were part of the current file. +Include the stated file as if it were part of the current file. The file +name can contain a wildcard (@samp{*}) to refer to multiple files (e.g. +@samp{bank/*.ledger}). @item payee @findex payee -- cgit v1.2.3 From 9123a3261d864c0fd2f9516f69f55d32e1561368 Mon Sep 17 00:00:00 2001 From: Martin Michlmayr Date: Fri, 6 Jul 2018 20:16:35 +0200 Subject: Document keywords "any" and "all" Fixes #1192 --- doc/ledger3.texi | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/doc/ledger3.texi b/doc/ledger3.texi index c63ee218..484b5ef1 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -8159,6 +8159,19 @@ posting. A regular expression that matches against the transaction code (the text that occurs between parentheses before the payee). +@item expr any(KEYWORD =~ /REGEX/) +The @command{any} keyword is used to specify that at least one posting of +the transaction must match the expression in brackets. For example, +@samp{ledger -f d reg expr "any(account =~ /Assets:/)"} can be used to +display all transactions which involve at least one @samp{Assets:} +account. + +@item expr all(KEYWORD =~ /REGEX/) +The @command{all} keyword is used to specify that all postings of a +transactions must match the expression in brackets. For example, +@samp{ledger -f d reg expr "all(account =~ /Assets:/)"} can be used to +display all transactions where all accounts are @samp{Assets:}. + @end table The @command{query} command can be used to see how Ledger interprets -- cgit v1.2.3 From 6abbb0043692688a80762608de25b3116e2f2a50 Mon Sep 17 00:00:00 2001 From: Scott Carpenter Date: Sun, 8 Jul 2018 17:07:45 -0500 Subject: Fix order of transaction state in txn header The transaction state comes before (not after) the code. --- doc/ledger3.texi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ledger3.texi b/doc/ledger3.texi index 484b5ef1..a094d977 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -2912,7 +2912,7 @@ you a place to put those codes: A transaction can have a ``state'': cleared, pending, or uncleared. The default is uncleared. To mark a transaction cleared, put an asterisk -@samp{*} before the payee, after the date or code: +@samp{*} after the date, before the code or payee: @smallexample @c input:validate 2012-03-10 * KFC -- cgit v1.2.3 From c18a55f9ef9969b38e5cf7f0f319b8ec5df34809 Mon Sep 17 00:00:00 2001 From: Jan Beich Date: Mon, 9 Jul 2018 12:07:30 +0000 Subject: Unbreak with boost 1.68 In file included from src/main.cc:34: In file included from src/global.h:41: In file included from src/option.h:45: In file included from src/scope.h:45: In file included from src/op.h:45: In file included from src/expr.h:45: In file included from src/exprbase.h:57: src/utils.h:47:10: fatal error: 'boost/uuid/sha1.hpp' file not found #include ^ --- src/utils.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/utils.h b/src/utils.h index b21dff7a..857b8289 100644 --- a/src/utils.h +++ b/src/utils.h @@ -44,7 +44,11 @@ #ifndef _UTILS_H #define _UTILS_H +#if (BOOST_VERSION >= 106600) +#include +#else #include +#endif /** * @name Default values -- cgit v1.2.3 From 3186ff514a19851266b5be63773a54086e2ae2d9 Mon Sep 17 00:00:00 2001 From: Pascal Fleury Date: Tue, 17 Jul 2018 21:59:01 +0200 Subject: amount_t -> balance_t. --- src/textual.cc | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/src/textual.cc b/src/textual.cc index 8fbc5c08..5d7706ec 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -1644,9 +1644,9 @@ post_t * instance_t::parse_post(char * line, } DEBUG("textual.parse", "line " << context.linenum << ": " - << "POST assign: parsed amt = " << *post->assigned_amount); + << "POST assign: parsed balance amount = " << *post->assigned_amount); - amount_t& amt(*post->assigned_amount); + const amount_t& amt(*post->assigned_amount); value_t account_total (post->account->amount().strip_annotations(keep_details_t())); @@ -1655,18 +1655,19 @@ post_t * instance_t::parse_post(char * line, DEBUG("post.assign", "line " << context.linenum << ": " << "post amount = " << amt); - amount_t diff = amt; + balance_t diff = amt; switch (account_total.type()) { case value_t::AMOUNT: - if (account_total.as_amount().commodity_ptr() == diff.commodity_ptr()) - diff -= account_total.as_amount(); + diff -= account_total.as_amount(); + DEBUG("textual.parse", "line " << context.linenum << ": " + << "Subtracting amount " << account_total.as_amount() << " from diff, yielding " << diff); break; case value_t::BALANCE: - if (optional comm_bal = - account_total.as_balance().commodity_amount(amt.commodity())) - diff -= *comm_bal; + diff -= account_total.as_balance(); + DEBUG("textual.parse", "line " << context.linenum << ": " + << "Subtracting balance " << account_total.as_balance() << " from diff, yielding " << diff); break; default: @@ -1680,26 +1681,42 @@ post_t * instance_t::parse_post(char * line, // Subtract amounts from previous posts to this account in the xact. for (post_t* p : xact->posts) { - if (p->account == post->account && - p->amount.commodity_ptr() == diff.commodity_ptr()) { + if (p->account == post->account) { diff -= p->amount; DEBUG("textual.parse", "line " << context.linenum << ": " - << "Subtract " << p->amount << ", diff = " << diff); + << "Subtracting " << p->amount << ", diff = " << diff); } } if (post->amount.is_null()) { // balance assignment if (! diff.is_zero()) { - post->amount = diff; + // This will fail if there are more than 1 commodity in diff, which is wanted, + // as amount cannot store more than 1 commodity. + post->amount = diff.to_amount(); DEBUG("textual.parse", "line " << context.linenum << ": " << "Overwrite null posting"); } } else { // balance assertion diff -= post->amount; + // If amt has a commodity, restrict balancing to that. Otherwise, it's the blanket '0' and + // check that all of them are zero. + if (amt.has_commodity()) { + DEBUG("textual.parse", "line " << context.linenum << ": " + << "Finding commodity " << amt.commodity() << " (" << amt << ") in balance " << diff); + optional wanted_commodity = diff.commodity_amount(amt.commodity()); + if (!wanted_commodity) { + diff = amt - amt; // this is '0' with the correct commodity. + } else { + diff = *wanted_commodity; + } + DEBUG("textual.parse", "line " << context.linenum << ": " + << "Diff is now " << diff); + } if (! no_assertions && ! diff.is_zero()) { - amount_t tot = amt - diff; + balance_t tot = -diff + amt; + DEBUG("textual.parse", "Balance assertion: off by " << diff << " (expected to see " << tot << ")"); throw_(parse_error, _f("Balance assertion off by %1% (expected to see %2%)") % diff % tot); -- cgit v1.2.3 From 135a9e52ad0d753477aa4723f1ed23c16e8a3f32 Mon Sep 17 00:00:00 2001 From: Pascal Fleury Date: Tue, 17 Jul 2018 23:36:50 +0200 Subject: fix regression of test 1147_a --- src/textual.cc | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/textual.cc b/src/textual.cc index 5d7706ec..246db751 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -1652,8 +1652,8 @@ post_t * instance_t::parse_post(char * line, DEBUG("post.assign", "line " << context.linenum << ": " << "account balance = " << account_total); - DEBUG("post.assign", - "line " << context.linenum << ": " << "post amount = " << amt); + DEBUG("post.assign", "line " << context.linenum << ": " + << "post amount = " << amt << " (is_zero = " << amt.is_zero() << ")"); balance_t diff = amt; @@ -1688,6 +1688,21 @@ post_t * instance_t::parse_post(char * line, } } + // If amt has a commodity, restrict balancing to that. Otherwise, it's the blanket '0' and + // check that all of them are zero. + if (amt.has_commodity()) { + DEBUG("textual.parse", "line " << context.linenum << ": " + << "Finding commodity " << amt.commodity() << " (" << amt << ") in balance " << diff); + optional wanted_commodity = diff.commodity_amount(amt.commodity()); + if (!wanted_commodity) { + diff = amt - amt; // this is '0' with the correct commodity. + } else { + diff = *wanted_commodity; + } + DEBUG("textual.parse", "line " << context.linenum << ": " + << "Diff is now " << diff); + } + if (post->amount.is_null()) { // balance assignment if (! diff.is_zero()) { @@ -1700,26 +1715,12 @@ post_t * instance_t::parse_post(char * line, } else { // balance assertion diff -= post->amount; - // If amt has a commodity, restrict balancing to that. Otherwise, it's the blanket '0' and - // check that all of them are zero. - if (amt.has_commodity()) { - DEBUG("textual.parse", "line " << context.linenum << ": " - << "Finding commodity " << amt.commodity() << " (" << amt << ") in balance " << diff); - optional wanted_commodity = diff.commodity_amount(amt.commodity()); - if (!wanted_commodity) { - diff = amt - amt; // this is '0' with the correct commodity. - } else { - diff = *wanted_commodity; - } - DEBUG("textual.parse", "line " << context.linenum << ": " - << "Diff is now " << diff); - } if (! no_assertions && ! diff.is_zero()) { balance_t tot = -diff + amt; DEBUG("textual.parse", "Balance assertion: off by " << diff << " (expected to see " << tot << ")"); throw_(parse_error, _f("Balance assertion off by %1% (expected to see %2%)") - % diff % tot); + % diff.to_string() % tot.to_string()); } } -- cgit v1.2.3 From 4a54ac5cc68bcd7fda68610ca5089e3a06963c68 Mon Sep 17 00:00:00 2001 From: Pascal Fleury Date: Wed, 18 Jul 2018 00:04:02 +0200 Subject: Added more documentation about assertions. --- doc/ledger3.texi | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/doc/ledger3.texi b/doc/ledger3.texi index a094d977..45afe1ae 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -3230,6 +3230,45 @@ A balance assertion has this general form: This simply asserts that after subtracting $20.00 from Assets:Cash, that the resulting total matches $500.00. If not, it is an error. +The assertion has an effect only on the specified commodity. If an account has +multiple commodities, then only the one asserted is verified: + +@smallexample +2012-03-10 KFC New York + Expenses:Food $20.00 + Assets:Cash $-20.00 = $500.00 + +2012-03-11 KFC Montreal + Expenses:Food 15.00 CAD + Assets:Cash -15.00 CAD = $500.00 +@end smallexample + +In this case, the amount in USD of cash (which has not changed) is validated. +Nothing is asserted about the current amount of Canadian dollars in @samp{Asset:Cash}. + +@subsubsection Special assertion value 0 + +The only value that can be asserted without a commodity is @samp{0}. +This results in a cross-commodities assertion, which makes it possible to +assert that an account is totally empty. + +@smallexample +2012-03-09 Fill Wallet + Revenue $20.00 + Revenue 15.00 CAD + Assets:Cash + +2012-03-10 KFC New York + Expenses:Food $20.00 + Assets:Cash $-20.00 + +2012-03-11 KFC Montreal + Expenses:Food 15.00 CAD + Assets:Cash -15.00 CAD = 0 +@end smallexample + +The last transaction will assert that we are out of cash of any sort. + @node Balance assignments, Resetting a balance, Balance assertions, Balance verification @subsection Balance assignments -- cgit v1.2.3 From 3ee62bf84e1d67cc4bc1d151b60ddeaaeba7147c Mon Sep 17 00:00:00 2001 From: Pascal Fleury Date: Wed, 18 Jul 2018 00:08:56 +0200 Subject: Add the test to make sure it works in the future. --- test/regress/1187_5.test | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 test/regress/1187_5.test diff --git a/test/regress/1187_5.test b/test/regress/1187_5.test new file mode 100644 index 00000000..4aa5fdd8 --- /dev/null +++ b/test/regress/1187_5.test @@ -0,0 +1,36 @@ +2013/12/01 * Initial State + Crédit:Viseca:MasterCard P1 -618.50 CHF + Crédit:Viseca:MasterCard P2 -52.10 CHF + Equity:Opening Balances + +2013/12/15 * Buy Some Chocolate + Dépenses:Nourriture 19.00 EUR ; #1 + Crédit:Viseca:MasterCard P1 + +2013/12/15 * Buy Some Chocolate + Crédit:Viseca:MasterCard P1 18.00 EUR ; #2 + Recettes:Erreurs + +2013/12/23 * Facture Viseca + Crédit:Viseca:MasterCard P2 52.10 CHF = 0 ; #3 + Crédit:Viseca:MasterCard P1 618.50 CHF = 0 CHF ; #4 + Dépenses:Frais:Gestion Comptes 1.50 CHF + Crédit:Viseca -672.10 CHF + +2014/01/03 * Facture Viseca + Crédit:Viseca 672.10 CHF = 0 + Actif:Comptes:CP courant + +test bal + -672.10 CHF Actif:Comptes:CP courant + -1.00 EUR Crédit:Viseca + -1.00 EUR MasterCard P1 + 1.50 CHF + 19.00 EUR Dépenses + 1.50 CHF Frais:Gestion Comptes + 19.00 EUR Nourriture + 670.60 CHF Equity:Opening Balances + -18.00 EUR Recettes:Erreurs +-------------------- + 0 +end test -- cgit v1.2.3 From 2d8e2fd0396deb02904a9641d89a4c137f71e597 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Thu, 19 Jul 2018 22:42:41 -0700 Subject: Correct to Travis build for the new master branch version --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 94489bdc..cbb910b9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -83,7 +83,8 @@ addons: #- libboost-serialization1.55-dev before_install: - - if [ "${TRAVIS_BRANCH}" = "master" ]; then export BOOST_VERSION="${BOOST_VERSION_MIN}"; else export BOOST_VERSION="${BOOST_VERSION_MAX}"; fi + # - if [ "${TRAVIS_BRANCH}" = "master" ]; then export BOOST_VERSION="${BOOST_VERSION_MIN}"; else export BOOST_VERSION="${BOOST_VERSION_MAX}"; fi + - BOOST_VERSION="${BOOST_VERSION_MAX}" - if [ -n "${BOOST_VERSION}" ]; then export BOOST_ROOT="${TRAVIS_BUILD_DIR}/../boost-trunk"; export CMAKE_MODULE_PATH="${BOOST_ROOT}"; fi - if [ "${CXX}" = "g++" ]; then export CXX="$(which g++-4.8)"; export CC="$(which gcc-4.8)"; fi - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then export DYLD_LIBRARY_PATH="${BOOST_ROOT}/lib"; fi -- cgit v1.2.3 From bdeddff2876c82e3cb1bc5d9875ea14248c431b5 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Thu, 19 Jul 2018 23:10:42 -0700 Subject: Missing an export keyword in .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index cbb910b9..bfad754a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -84,7 +84,7 @@ addons: before_install: # - if [ "${TRAVIS_BRANCH}" = "master" ]; then export BOOST_VERSION="${BOOST_VERSION_MIN}"; else export BOOST_VERSION="${BOOST_VERSION_MAX}"; fi - - BOOST_VERSION="${BOOST_VERSION_MAX}" + - export BOOST_VERSION="${BOOST_VERSION_MAX}" - if [ -n "${BOOST_VERSION}" ]; then export BOOST_ROOT="${TRAVIS_BUILD_DIR}/../boost-trunk"; export CMAKE_MODULE_PATH="${BOOST_ROOT}"; fi - if [ "${CXX}" = "g++" ]; then export CXX="$(which g++-4.8)"; export CC="$(which gcc-4.8)"; fi - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then export DYLD_LIBRARY_PATH="${BOOST_ROOT}/lib"; fi -- cgit v1.2.3 From 6e72c36eed180379efe39513f7b289ab7a78e290 Mon Sep 17 00:00:00 2001 From: Scott Carpenter Date: Thu, 2 Aug 2018 07:36:36 -0500 Subject: Add notes to 5.7.1.1 Payee metadata tag doc --- doc/ledger3.texi | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/ledger3.texi b/doc/ledger3.texi index 45afe1ae..35528e62 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -3103,6 +3103,10 @@ it appears as: This shows that they are all in the same transaction (which is why the date is not repeated), but they have different payees now. +If using the @option{--strict} or @option{--pedantic} options, you must +declare this tag to avoid warnings and errors. The payee name used with +the tag is not enforced by the @option{--check-payees} option. + @node Metadata values, Typed metadata, Metadata tags, Metadata @subsection Metadata values -- cgit v1.2.3 From 464cfdfdaf62a97e862670d5798ba5630090e3b2 Mon Sep 17 00:00:00 2001 From: Scott Carpenter Date: Wed, 8 Aug 2018 19:36:27 -0500 Subject: Mention and point to related bug for Payee metadata tag --- doc/ledger3.texi | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/ledger3.texi b/doc/ledger3.texi index 35528e62..dd697384 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -3104,8 +3104,11 @@ This shows that they are all in the same transaction (which is why the date is not repeated), but they have different payees now. If using the @option{--strict} or @option{--pedantic} options, you must -declare this tag to avoid warnings and errors. The payee name used with -the tag is not enforced by the @option{--check-payees} option. +declare this tag to avoid warnings and errors. + +The payee name used with the tag is not enforced by the +@option{--check-payees} option, due to a bug: +@url{https://github.com/ledger/ledger/issues/556}. @node Metadata values, Typed metadata, Metadata tags, Metadata @subsection Metadata values -- cgit v1.2.3 From b002109aff7407377141a30f912983b1e353933e Mon Sep 17 00:00:00 2001 From: Bruno Sutic Date: Thu, 9 Aug 2018 11:53:13 +0200 Subject: Improve bash completion We now complete option shorthands [ci skip] --- contrib/ledger-completion.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/ledger-completion.bash b/contrib/ledger-completion.bash index d46b712d..2da44274 100644 --- a/contrib/ledger-completion.bash +++ b/contrib/ledger-completion.bash @@ -59,7 +59,7 @@ _ledger() accounts="Assets Liabilities Equity Revenue Expenses" case $prev in - --@(cache|file|init-file|output|pager|price-db|script)) + --@(cache|file|init-file|output|pager|price-db|script)|-@(f|i|o)) _filedir return 0 ;; -- cgit v1.2.3 From dcbc9c777693ee8c9bed8e0cf34889dc695a3369 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Thu, 9 Aug 2018 10:49:05 -0400 Subject: Correct some instance of @@ to @@@@ (for Texinfo escaping) --- doc/ledger3.texi | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/ledger3.texi b/doc/ledger3.texi index dd697384..7d68dfb7 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -3547,17 +3547,17 @@ Plus, it comes with dangers. This works fine: @smallexample @c input:validate 2012-04-10 My Broker - Assets:Brokerage 10 AAPL @@ $50.00 + Assets:Brokerage 10 AAPL @@@@ $50.00 Assets:Brokerage:Cash $-500.00 2012-04-10 My Broker Assets:Brokerage:Cash $375.00 - Assets:Brokerage -5 AAPL @{$50.00@} @@ $375.00 + Assets:Brokerage -5 AAPL @{$50.00@} @@@@ $375.00 Income:Capital Gains $-125.00 2012-04-10 My Broker Assets:Brokerage:Cash $375.00 - Assets:Brokerage -5 AAPL @{$50.00@} @@ $375.00 + Assets:Brokerage -5 AAPL @{$50.00@} @@@@ $375.00 Income:Capital Gains $-125.00 @end smallexample @@ -3566,17 +3566,17 @@ But this does not do what you might expect: @smallexample 2012-04-10 My Broker - Assets:Brokerage 10 AAPL @@ $50.00 + Assets:Brokerage 10 AAPL @@@@ $50.00 Assets:Brokerage:Cash $-500.00 2012-04-10 My Broker Assets:Brokerage:Cash $375.00 - Assets:Brokerage -5 AAPL @{@{$500.00@}@} @@ $375.00 + Assets:Brokerage -5 AAPL @{@{$500.00@}@} @@@@ $375.00 Income:Capital Gains $-125.00 2012-04-10 My Broker Assets:Brokerage:Cash $375.00 - Assets:Brokerage -5 AAPL @{@{$500.00@}@} @@ $375.00 + Assets:Brokerage -5 AAPL @{@{$500.00@}@} @@@@ $375.00 Income:Capital Gains $-125.00 @end smallexample @@ -3652,7 +3652,7 @@ expressions): @smallexample 2012-04-10 My Broker Assets:Brokerage:Cash $375.00 - Assets:Brokerage -5 AAPL @{$50.00@} [2012-04-10] @@ $375.00 + Assets:Brokerage -5 AAPL @{$50.00@} [2012-04-10] @@@@ $375.00 Income:Capital Gains $-125.00 @end smallexample @@ -3669,7 +3669,7 @@ indicate a virtual cost: @smallexample 2012-04-10 My Broker Assets:Brokerage:Cash $375.00 - Assets:Brokerage -5 AAPL @{$50.00@} [2012-04-10] (Oh my!) @@ $375.00 + Assets:Brokerage -5 AAPL @{$50.00@} [2012-04-10] (Oh my!) @@@@ $375.00 Income:Capital Gains $-125.00 @end smallexample -- cgit v1.2.3 From 8645add344275161109c98098a519a8e57619d21 Mon Sep 17 00:00:00 2001 From: Martin Michlmayr Date: Thu, 30 Aug 2018 11:46:18 +0200 Subject: Clarify tag() and has_tag() Fixes #1676 --- doc/ledger3.texi | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/ledger3.texi b/doc/ledger3.texi index 7d68dfb7..3fad6d7c 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -8135,8 +8135,12 @@ posting. A regular expression that matches against a transaction's payee name. @item %/REGEX/ -@itemx tag(REGEX) -A regular expression that matches against a transaction's tags. +@itemx expr has_tag(/REGEX/) +@itemx expr has_tag('TAG') +A regular expression that checks for the tags of a transaction. + +@itemx tag(REGEX) =~ /REGEX/ +A regular expression that matches a transaction's tags against its values. @item expr date =~ /REGEX/ Useful for specifying a date in plain terms. For example, you could say -- cgit v1.2.3 From 00f371958d4ee0f540767c06e490cb26c4b8b0a1 Mon Sep 17 00:00:00 2001 From: Martin Michlmayr Date: Thu, 30 Aug 2018 11:47:51 +0200 Subject: Fix texinfo syntax --- doc/ledger3.texi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ledger3.texi b/doc/ledger3.texi index 3fad6d7c..7f6f64d1 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -8139,7 +8139,7 @@ A regular expression that matches against a transaction's payee name. @itemx expr has_tag('TAG') A regular expression that checks for the tags of a transaction. -@itemx tag(REGEX) =~ /REGEX/ +@item tag(REGEX) =~ /REGEX/ A regular expression that matches a transaction's tags against its values. @item expr date =~ /REGEX/ -- cgit v1.2.3 From 851eb60ddca952d4c5e2bae564e840475f985033 Mon Sep 17 00:00:00 2001 From: Martin Michlmayr Date: Fri, 31 Aug 2018 10:24:44 +0200 Subject: Clarify regex vs exact matches with has_tag() --- doc/ledger3.texi | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/ledger3.texi b/doc/ledger3.texi index 7f6f64d1..751b91f1 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -8137,7 +8137,8 @@ A regular expression that matches against a transaction's payee name. @item %/REGEX/ @itemx expr has_tag(/REGEX/) @itemx expr has_tag('TAG') -A regular expression that checks for the tags of a transaction. +A regular expression (REGEX) or string (TAG) that checks for the tags of +a transaction. @item tag(REGEX) =~ /REGEX/ A regular expression that matches a transaction's tags against its values. -- cgit v1.2.3 From da1c3c2627fef41f7a932533e1807b4552c03610 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Mon, 3 Sep 2018 09:54:29 -0700 Subject: Add concurrent make and check to default.nix --- default.nix | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/default.nix b/default.nix index 85932da9..a60e607f 100644 --- a/default.nix +++ b/default.nix @@ -26,6 +26,11 @@ stdenv.mkDerivation { cmakeFlags = [ "-DCMAKE_INSTALL_LIBDIR=lib" ]; + buildPhase = "make -j$NIX_BUILD_CORES"; + checkPhase = "ctest -j$NIX_BUILD_CORES"; + + doCheck = true; + meta = { homepage = "http://ledger-cli.org/"; description = "A double-entry accounting system with a command-line reporting interface"; -- cgit v1.2.3 From 3bc5c4d43c20e3f61368b749cd9611684f7b97dd Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Mon, 3 Sep 2018 09:54:40 -0700 Subject: Fix two bugs in the documentation --- doc/ledger3.texi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/ledger3.texi b/doc/ledger3.texi index 751b91f1..918f03d0 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -3547,7 +3547,7 @@ Plus, it comes with dangers. This works fine: @smallexample @c input:validate 2012-04-10 My Broker - Assets:Brokerage 10 AAPL @@@@ $50.00 + Assets:Brokerage 10 AAPL @@ $50.00 Assets:Brokerage:Cash $-500.00 2012-04-10 My Broker @@ -3566,7 +3566,7 @@ But this does not do what you might expect: @smallexample 2012-04-10 My Broker - Assets:Brokerage 10 AAPL @@@@ $50.00 + Assets:Brokerage 10 AAPL @@ $50.00 Assets:Brokerage:Cash $-500.00 2012-04-10 My Broker -- cgit v1.2.3 From 89211860c2065c9e9c6b4ebfa999f75d76362791 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Thu, 20 Sep 2018 11:38:59 -0700 Subject: A minor suggested change in the documentation --- doc/ledger3.texi | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/ledger3.texi b/doc/ledger3.texi index 918f03d0..27092fbb 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -3194,9 +3194,9 @@ look ``harder'') rather than parentheses: @node Expression amounts, Balance verification, Virtual postings, Transactions @section Expression amounts -An amount is usually a numerical figure with an (optional) commodity, -but it can also be any value expression. To indicate this, surround -the amount expression with parentheses: +An amount is a numerical figure with a commodity, but it can also be +any value expression. To indicate this, surround the amount +expression with parentheses: @smallexample @c input:validate 2012-03-10 * KFC -- cgit v1.2.3 From d9e6a04a31542c3ddef940d35775ceb29ef06300 Mon Sep 17 00:00:00 2001 From: Martin Michlmayr Date: Wed, 14 Nov 2018 11:35:54 +0200 Subject: Fix broken link in manual --- doc/ledger3.texi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ledger3.texi b/doc/ledger3.texi index 27092fbb..ae3dce08 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -427,7 +427,7 @@ accounting problems. Once such tutorial, specifically designed for non-profit charities that seek to use Ledger, can be found at -@url{https://k.sfconservancy.org/npo-ledger-cli} (with a copy on GitHub also +@url{https://k.sfconservancy.org/NPO-Accounting/npo-ledger-cli} (with a copy on GitHub also available at @url{https://github.com/conservancy/npo-ledger-cli/}). If you're looking for information about how to use Ledger's tagging system to handle invoicing, track expenses by program targets, and other such concepts, -- cgit v1.2.3 From eefa015836d87b9944da2e51373aff643841d69b Mon Sep 17 00:00:00 2001 From: "Georg J.P. Link" Date: Sun, 18 Nov 2018 12:55:16 -0600 Subject: explain round brackets in section on funds Section "Working with multiple funds and accounts" introduces square brackets and explains them but does not explain round brackets in the following example. This commit adds the explanation what the round brackets do. --- doc/ledger3.texi | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/ledger3.texi b/doc/ledger3.texi index ae3dce08..63946d25 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -1384,10 +1384,11 @@ account: (Funds:School) $-100.00 @end smallexample -When reports are generated, by default they'll appear in terms of the -funds. In this case, you will likely want to mask out your -@samp{Assets} account, because otherwise the balance won't make much -sense: +The use of round brackets creates a virtual posting without ensuring +a balance to zero. When reports are generated, by default they'll +appear in terms of the funds. In this case, you will likely want to +mask out your @samp{Assets} account, because otherwise the balance +won't make much sense: @smallexample @c command:396F24E $ ledger --no-total bal not ^Assets -- cgit v1.2.3 From ec209120c4a110f24340850c704dcf466ede1919 Mon Sep 17 00:00:00 2001 From: Martin Michlmayr Date: Wed, 9 Jan 2019 17:28:14 -0200 Subject: Update Debian releases in README.md --- README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0aa634b4..d7cd8c28 100644 --- a/README.md +++ b/README.md @@ -142,12 +142,9 @@ Or, for Ubuntu 12.04: ### Debian -Debian squeeze (6.0): the version of boost in squeeze is too old -for ledger and unfortunately no backport is available at the moment. - -Debian 7 (wheezy), Debian 8 (jessie), Debian 9 (stretch), Debian testing -and Debian unstable (sid) contain all components needed to build ledger. -You can install all required build dependencies using the following command: +Debian 9 (stretch), Debian 10 (buster), Debian testing and Debian unstable +(sid) contain all components needed to build ledger. You can install all +required build dependencies using the following command: $ sudo apt-get install build-essential cmake autopoint texinfo python-dev \ zlib1g-dev libbz2-dev libgmp3-dev gettext libmpfr-dev \ -- cgit v1.2.3 From 61427262553c86edfab8f1a77486ccad803419a0 Mon Sep 17 00:00:00 2001 From: Martin Michlmayr Date: Wed, 9 Jan 2019 17:30:24 -0200 Subject: Remove trailing whitespace --- README.md | 4 ++-- acprep | 2 +- appveyor.yml | 2 +- doc/NEWS | 14 +++++++------- doc/ledger3.texi | 8 ++++---- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index d7cd8c28..2d28f52c 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ Dependency | Version ### Mac OS X -You can use [Homebrew] or [MacPorts] to install Ledger easily on OS X. +You can use [Homebrew] or [MacPorts] to install Ledger easily on OS X. #### 1. Homebrew @@ -163,7 +163,7 @@ footwork for you: # $HOME/local and build with 2 processes in parallel $ ./acprep update --boost-suffix=-mt --prefix=$HOME/local -j2 -Please read the contents of `CMakeFiles/CMakeOutput.log` and +Please read the contents of `CMakeFiles/CMakeOutput.log` and `CMakeFiles/CMakeError.log` if the configure step fails. Also, see the `help` subcommand to `acprep`, which explains some of its many options. It's pretty much the only command I run for configuring, building diff --git a/acprep b/acprep index 8d9d67f7..cef7b63f 100755 --- a/acprep +++ b/acprep @@ -692,7 +692,7 @@ class PrepareBuild(CommandLineApp): elif system.startswith('CYGWIN'): self.log.info('Looks like you are using Cygwin') - self.log.info('Please install the dependencies manually.') + self.log.info('Please install the dependencies manually.') ######################################################################### # Determine the system's basic configuration # diff --git a/appveyor.yml b/appveyor.yml index 21fec71c..3b2e7267 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -46,5 +46,5 @@ artifacts: test_script: - C:\msys64\usr\bin\bash -lc "export MINGW_PREFIX=C:/msys64/mingw32/ CTEST_OUTPUT_ON_FAILURE=1 - PATH=/mingw32/bin:$PATH && cd $APPVEYOR_BUILD_FOLDER && + PATH=/mingw32/bin:$PATH && cd $APPVEYOR_BUILD_FOLDER && make test || echo Errors from tests were ignored" diff --git a/doc/NEWS b/doc/NEWS index 0cb4b2b9..a2e3ea04 100644 --- a/doc/NEWS +++ b/doc/NEWS @@ -154,7 +154,7 @@ features, please see the manual. 2008/07/27 Starting fresh Assets:Checking $750.00 Equity:Opening Balances - + 2008/07/27 Starting fresh Assets:Checking = $1,000.00 Equity:Adjustments @@ -164,7 +164,7 @@ features, please see the manual. 2008/07/27 Starting fresh Assets:Checking $750.00 Equity:Opening Balances - + 2008/07/27 Starting fresh Assets:Checking $250.00 Equity:Adjustments @@ -183,7 +183,7 @@ features, please see the manual. 2008/07/24 Opening Balance Assets:Checking = $250.00 ; we force set it Equity:Opening Balances - + 2008/07/24 Opening Balance Assets:Checking = EC 250.00 ; we force set it again Equity:Opening Balances @@ -195,7 +195,7 @@ features, please see the manual. Assets:Checking = $250.00 ; we force set it again Assets:Checking EC 100.00 ; and add some EC's Equity:Opening Balances - + 2008/07/24 Opening Balance Assets:Checking = EC 250.00 ; we force set the EC's Equity:Opening Balances @@ -213,15 +213,15 @@ features, please see the manual. 2008/07/24 Opening Balance Assets:Checking = $100.00 Equity:Opening Balances - + 2008/07/30 We spend money, with a known balance afterward Expenses:Food $20.00 Assets:Checking = $80.00 - + 2008/07/30 Again we spend money, but this time with all the info Expenses:Food $20.00 Assets:Checking $-20.00 = $60.00 - + 2008/07/30 This entry yield an 'unbalanced' error Expenses:Food $20.00 Assets:Checking $-20.00 = $30.00 diff --git a/doc/ledger3.texi b/doc/ledger3.texi index 63946d25..9f9a1095 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -1384,10 +1384,10 @@ account: (Funds:School) $-100.00 @end smallexample -The use of round brackets creates a virtual posting without ensuring -a balance to zero. When reports are generated, by default they'll -appear in terms of the funds. In this case, you will likely want to -mask out your @samp{Assets} account, because otherwise the balance +The use of round brackets creates a virtual posting without ensuring +a balance to zero. When reports are generated, by default they'll +appear in terms of the funds. In this case, you will likely want to +mask out your @samp{Assets} account, because otherwise the balance won't make much sense: @smallexample @c command:396F24E -- cgit v1.2.3 From 863cb3bdb7bc3650873253e108b26eb3fe15862d Mon Sep 17 00:00:00 2001 From: Martin Michlmayr Date: Wed, 9 Jan 2019 20:43:39 -0200 Subject: Use "macOS" consistently macOS seems to be the current name for Mac OS X. --- .travis.yml | 4 ++-- CMakeLists.txt | 2 +- INSTALL.md | 2 +- README.md | 6 +++--- acprep | 8 ++++---- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index bfad754a..d10d4466 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,7 +38,7 @@ matrix: # either crashes clang or results in a ledger binary that crashes with SIGSEGV. - os: osx compiler: gcc - # On Mac OS X building ledger with GNU GCC 4.8 fails due to + # On macOS building ledger with GNU GCC 4.8 fails due to # undefined symbols, maybe because boost was not being built with g++-4.8. # Undefined symbols for architecture x86_64: # "boost::re_detail::perl_matcher >, boost::regex_traits > >::construct_init(boost::basic_regex > > const&, boost::regex_constants::_match_flags)", referenced from: @@ -49,7 +49,7 @@ matrix: # bool boost::regex_search > >(char const*, char const*, boost::basic_regex > > const&, boost::regex_constants::_match_flags) in global.cc.o - os: osx compiler: clang - # On Mac OS X building ledger with clang fails due to + # On macOS building ledger with clang fails due to # dyld: Library not loaded: libboost_python.dylib # Referenced from: /Users/travis/build/ledger/ledger/ledger # Reason: image not found diff --git a/CMakeLists.txt b/CMakeLists.txt index f8dbd892..e2ed3cac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -260,7 +260,7 @@ macro(add_ledger_library_dependencies _target) endif() if (HAVE_BOOST_PYTHON) if(CMAKE_SYSTEM_NAME STREQUAL Darwin) - # Don't link directly to a Python framework on OS X, to avoid segfaults + # Don't link directly to a Python framework on macOS, to avoid segfaults # when the module is imported from a different interpreter target_link_libraries(${_target} ${Boost_LIBRARIES}) set_target_properties(${_target} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") diff --git a/INSTALL.md b/INSTALL.md index 23d0566b..a366dbfd 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -115,7 +115,7 @@ Q: Whenever I try to use the Python support, I get a segfault A: Make sure that the boost_python library you linked against is using the exact same Python as the Ledger executable. In particular I see this - bug on OS X systems where boost_python is linked against the default + bug on macOS systems where boost_python is linked against the default Python, while Ledger is linked against the version provided by MacPorts. Or vice versa. diff --git a/README.md b/README.md index 2d28f52c..64855246 100644 --- a/README.md +++ b/README.md @@ -94,9 +94,9 @@ Dependency | Version [expat] | 2.0.1 _optional_ [libxml2] | 2.7.2 _optional_ -### Mac OS X +### macOS -You can use [Homebrew] or [MacPorts] to install Ledger easily on OS X. +You can use [Homebrew] or [MacPorts] to install Ledger easily on macOS. #### 1. Homebrew @@ -111,7 +111,7 @@ If you to want to startup python, use the following command: #### 2. MacPorts -If you build stuff using MacPorts on OS X, as I do, here is what you would +If you build stuff using MacPorts on macOS, as I do, here is what you would run: $ sudo port install -f cmake python26 \ diff --git a/acprep b/acprep index cef7b63f..bcbf1308 100755 --- a/acprep +++ b/acprep @@ -514,7 +514,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') + self.log.info('Looks like you are using MacPorts on macOS') packages = [ 'sudo', 'port', 'install', '-f', 'automake', 'autoconf', 'libtool', @@ -531,7 +531,7 @@ class PrepareBuild(CommandLineApp): self.log.info('Executing: ' + ' '.join(packages)) self.execute(*packages) elif exists('/usr/local/bin/brew') or exists('/opt/local/bin/brew'): - self.log.info('Looks like you are using Homebrew on OS X') + self.log.info('Looks like you are using Homebrew on macOS') packages = [ 'brew', 'install', 'cmake', 'ninja', @@ -540,7 +540,7 @@ class PrepareBuild(CommandLineApp): self.log.info('Executing: ' + ' '.join(packages)) self.execute(*packages) elif exists('/sw/bin/fink'): - self.log.info('Looks like you are using Fink on OS X') + self.log.info('Looks like you are using Fink on macOS') self.log.error("I don't know the package names for Fink yet!") sys.exit(1) @@ -1086,7 +1086,7 @@ being 'debug': debug Debugging and --verify support (default) opt Full optimizations gcov Coverage analysis - gprof Code profiling (for OS X, just use: 'shark -i ledger ...') + gprof Code profiling (for macOS, just use: 'shark -i ledger ...') Next is the optional build PHASE, with 'config' being the default: -- cgit v1.2.3 From 56025cdefc07e30b025c65146affc141888b2f83 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Fri, 11 Jan 2019 16:25:02 -0800 Subject: Don't attempt to invert a value if it's already zero (#1703) --- src/amount.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/amount.cc b/src/amount.cc index 05145f87..c6463b2b 100644 --- a/src/amount.cc +++ b/src/amount.cc @@ -604,7 +604,9 @@ void amount_t::in_place_invert() throw_(amount_error, _("Cannot invert an uninitialized amount")); _dup(); - mpq_inv(MP(quantity), MP(quantity)); + + if (sign() != 0) + mpq_inv(MP(quantity), MP(quantity)); } void amount_t::in_place_round() -- cgit v1.2.3 From 40f84faa52396633dfc6756f04d89443bfa4debb Mon Sep 17 00:00:00 2001 From: Martin Michlmayr Date: Sat, 12 Jan 2019 09:33:51 -0200 Subject: Add test case for issue #1703 Add a test case for issue #1703 which John Wiegley fixed in commit 56025cde ("Don't attempt to invert a value if it's already zero"). --- test/regress/1703.test | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 test/regress/1703.test diff --git a/test/regress/1703.test b/test/regress/1703.test new file mode 100644 index 00000000..983b79be --- /dev/null +++ b/test/regress/1703.test @@ -0,0 +1,11 @@ + +P 2018-10-31 MultifundosPlus R$0 + +2017-05-03 * Test + Assets:A 1 AAA @ R$ 3000 + Assets:B + +test reg assets:a -V --now 2018-12-31 +17-May-03 Test Assets:A R$3000 R$3000 +end test + -- cgit v1.2.3 From da9738f8ee7785724d62eb94747d7803502545a8 Mon Sep 17 00:00:00 2001 From: Tim Landscheidt Date: Sat, 12 Jan 2019 14:27:50 +0000 Subject: Remove unused macros HAVE_ACCESS and HAVE_REALPATH --- CMakeLists.txt | 2 -- src/system.hh.in | 4 +--- src/utils.h | 6 +----- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e2ed3cac..c85a5157 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,8 +96,6 @@ include(CheckCXXSourceCompiles) include(CheckCXXSourceRuns) include(CMakePushCheckState) -check_function_exists(access HAVE_ACCESS) -check_function_exists(realpath HAVE_REALPATH) check_function_exists(getpwuid HAVE_GETPWUID) check_function_exists(getpwnam HAVE_GETPWNAM) check_function_exists(ioctl HAVE_IOCTL) diff --git a/src/system.hh.in b/src/system.hh.in index 38ac1e63..799bb47c 100644 --- a/src/system.hh.in +++ b/src/system.hh.in @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -60,8 +60,6 @@ #define HAVE_EDIT @HAVE_EDIT@ #define HAVE_GETTEXT @HAVE_GETTEXT@ -#cmakedefine HAVE_ACCESS -#cmakedefine HAVE_REALPATH #cmakedefine HAVE_GETPWUID #cmakedefine HAVE_GETPWNAM #cmakedefine HAVE_IOCTL diff --git a/src/utils.h b/src/utils.h index 857b8289..c9146dd7 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -500,10 +500,6 @@ inline T& downcast(U& object) { path resolve_path(const path& pathname); -#ifdef HAVE_REALPATH -extern "C" char * realpath(const char *, char resolved_path[]); -#endif - inline const string& either_or(const string& first, const string& second) { return first.empty() ? second : first; -- cgit v1.2.3 From ccb7019c8b014b907d88bbcfb802b7f1458383b6 Mon Sep 17 00:00:00 2001 From: Tim Landscheidt Date: Sat, 12 Jan 2019 19:59:21 +0000 Subject: Use HAVE_EDIT only in main.cc --- src/main.cc | 16 ++++++++-------- src/system.hh.in | 8 ++------ 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/main.cc b/src/main.cc index c8b9ec3a..4348e8a6 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -35,6 +35,10 @@ // was moved there for the sake of clarity here #include "session.h" +#ifdef HAVE_EDIT +#include +#endif + using namespace ledger; #if HAVE_BOOST_PYTHON @@ -128,13 +132,9 @@ int main(int argc, char * argv[], char * envp[]) bool exit_loop = false; -#if HAVE_EDIT - +#ifdef HAVE_EDIT rl_readline_name = const_cast("Ledger"); -#if 0 - // jww (2009-02-05): NYI - rl_attempted_completion_function = ledger_completion; -#endif + // TODO: rl_attempted_completion_function = ledger_completion; while (char * p = readline(global_scope->prompt_string())) { char * expansion = NULL; @@ -173,7 +173,7 @@ int main(int argc, char * argv[], char * envp[]) global_scope->execute_command_wrapper(split_arguments(p), true); } -#if HAVE_EDIT +#ifdef HAVE_EDIT if (expansion) std::free(expansion); std::free(p); diff --git a/src/system.hh.in b/src/system.hh.in index 38ac1e63..3c1187d9 100644 --- a/src/system.hh.in +++ b/src/system.hh.in @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -57,10 +57,10 @@ #define Ledger_VERSION_PRERELEASE "@Ledger_VERSION_PRERELEASE@" #define Ledger_VERSION_DATE @Ledger_VERSION_DATE@ -#define HAVE_EDIT @HAVE_EDIT@ #define HAVE_GETTEXT @HAVE_GETTEXT@ #cmakedefine HAVE_ACCESS +#cmakedefine HAVE_EDIT #cmakedefine HAVE_REALPATH #cmakedefine HAVE_GETPWUID #cmakedefine HAVE_GETPWNAM @@ -167,10 +167,6 @@ typedef std::ostream::pos_type ostream_pos_type; #include #include "utf8.h" -#if HAVE_EDIT -#include -#endif - #include #include #include -- cgit v1.2.3 From aeb0bfbf58777ff31042cce83ab1ade2d97c1b3a Mon Sep 17 00:00:00 2001 From: Martin Michlmayr Date: Wed, 9 Jan 2019 20:45:58 -0200 Subject: Remove build information for ledger 2.6 --- README.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/README.md b/README.md index 64855246..0ddec293 100644 --- a/README.md +++ b/README.md @@ -84,16 +84,6 @@ Dependency | Version (or greater) [lcov] | 1.6 _optional_, for `make report`, used with `/./acprep gcov` [sloccount] | 2.26 _optional_, for `make sloc` -And for building the outdated `release/2.6.3` branch: - -Dependency | Version ------------|-------- -[GMP] | 4.2.2 -[pcre] | 7.7 -[libofx] | 0.8.3 _optional_ -[expat] | 2.0.1 _optional_ -[libxml2] | 2.7.2 _optional_ - ### macOS You can use [Homebrew] or [MacPorts] to install Ledger easily on macOS. -- cgit v1.2.3 From 2f46c6987f7071f10ec519f12c213eb35e3abf15 Mon Sep 17 00:00:00 2001 From: Martin Michlmayr Date: Sat, 12 Jan 2019 18:15:02 -0200 Subject: Add release dates to doc/NEWS --- doc/NEWS | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/NEWS b/doc/NEWS index a2e3ea04..39404e95 100644 --- a/doc/NEWS +++ b/doc/NEWS @@ -1,6 +1,6 @@ Ledger NEWS -* 3.1.2 +* 3.1.2 (unreleased) - Increased maximum length for regex from 255 to 4095 (bug #981). @@ -27,7 +27,7 @@ - Various documentation improvements -* 3.1.1 +* 3.1.1 (2016-01-11) - Added a --no-revalued option @@ -57,7 +57,7 @@ - Add continuous integration (https://travis-ci.org/ledger/ledger) -* 3.1 +* 3.1 (2014-10-05) - Changed the definition of cost basis to preserve the original cost basis when a gain or loss is made (if you bought 1 AAA for $10 and then sold -- cgit v1.2.3 From 40f0bbfbb75296cc42e9fd44ac168048469176f2 Mon Sep 17 00:00:00 2001 From: Martin Michlmayr Date: Sat, 12 Jan 2019 18:37:44 -0200 Subject: Update NEWS for 3.1.2 --- doc/NEWS | 51 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/doc/NEWS b/doc/NEWS index 39404e95..c22461ab 100644 --- a/doc/NEWS +++ b/doc/NEWS @@ -2,28 +2,61 @@ * 3.1.2 (unreleased) -- Increased maximum length for regex from 255 to 4095 (bug #981). +- Increase maximum length for regex from 255 to 4095 (bug #981) - Initialize periods from from/since clause rather than earliest - transaction date (bug #1159). + transaction date (bug #1159) -- Check balance assertions against the amount after the posting (bug #1147). +- Check balance assertions against the amount after the posting (bug #1147) -- Allow balance assertions with multiple posts to same account (bug #1187). +- Allow balance assertions with multiple posts to same account (bug #1187) -- Fixed period duration of "every X days" and similar statements (bug #370). +- Fix period duration of "every X days" and similar statements (bug #370) -- Option --force-color does not require --color anymore (bug #1109). +- Make option --force-color not require --color anymore (bug #1109) -- Added quoted_rfc4180 to allow CVS output with RFC 4180 compliant quoting. +- Add quoted_rfc4180 to allow CVS output with RFC 4180 compliant quoting. + +- Add support for --prepend-format in accounts command + +- Fix handling of edge cases in trim function (bug #520) + +- Fix auto xact posts not getting applied to account total during + journal parse (bug #552) + +- Transfer null_post flags to generated postings + +- Fix segfault when using --market with --group-by + +- Use amount_width variable for budget report + +- Keep pending items in budgets until the last day they apply + +- Fixed bug where .total used in value expressions breaks totals + +- Make automated transactions work with assertions (bug #1127) + +- Improve parsing of date tokens (bug #1626) + +- Don't attempt to invert a value if it's already zero (bug #1703) + +- Do not parse user-specified init-file twice - Python: Removed double quotes from Unicode values. +- Python: Ensure that parse errors produce useful RuntimeErrors + +- Python: Expose journal expand_aliases + +- Python: Expose journal_t::register_account + +- Improve bash completion + - Emacs Lisp files have been moved to https://github.com/ledger/ledger-mode -- Fixed build under MSYS (32-bit). +- Fix build under MSYS (32-bit). -- Fixed build under Cygwin. +- Fix build under Cygwin. - Various documentation improvements -- cgit v1.2.3 From 15e5a6835005f0af7e26e19492764106ec3777a6 Mon Sep 17 00:00:00 2001 From: Tim Landscheidt Date: Sat, 12 Jan 2019 21:43:12 +0000 Subject: Remove unnecessary include for sys/stat.h --- src/system.hh.in | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/system.hh.in b/src/system.hh.in index 38ac1e63..e36d0aa3 100644 --- a/src/system.hh.in +++ b/src/system.hh.in @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -143,7 +143,6 @@ typedef std::ostream::pos_type ostream_pos_type; #undef isspace(c) #endif -#include #if defined(_WIN32) || defined(__CYGWIN__) #include #else -- cgit v1.2.3 From dac56df1f4f9bcdce87d4b095310af49e74d9395 Mon Sep 17 00:00:00 2001 From: Tim Landscheidt Date: Sun, 13 Jan 2019 16:59:06 +0000 Subject: Remove unnecessary include for unicodeobject.h --- src/system.hh.in | 1 - 1 file changed, 1 deletion(-) diff --git a/src/system.hh.in b/src/system.hh.in index 38ac1e63..3d5451cb 100644 --- a/src/system.hh.in +++ b/src/system.hh.in @@ -247,7 +247,6 @@ typedef std::ostream::pos_type ostream_pos_type; #include #include -#include #include #include -- cgit v1.2.3 From 34eab875c541c6c2f40a8b3db712b2e7ef33b4cd Mon Sep 17 00:00:00 2001 From: Tim Landscheidt Date: Sun, 13 Jan 2019 17:48:58 +0000 Subject: Remove unused function py_dump_relaxed() The only user of py_dump_relaxed() was removed in commit 0bbb4f2f0cbaa6ffb5c7a2c018a3819cca0b2405. --- src/py_value.cc | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/py_value.cc b/src/py_value.cc index f4f63946..486228c0 100644 --- a/src/py_value.cc +++ b/src/py_value.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -88,12 +88,6 @@ namespace { return buf.str(); } - string py_dump_relaxed(const value_t& value) { - std::ostringstream buf; - value.dump(buf, true); - return buf.str(); - } - void py_set_string(value_t& value, const string& str) { return value.set_string(str); } -- cgit v1.2.3 From 2ef1fbd738577a2ea1eb332d4a777ec659366b7d Mon Sep 17 00:00:00 2001 From: Tim Landscheidt Date: Mon, 14 Jan 2019 18:34:20 +0000 Subject: Fix warning about uninitialized variable prepend_width --- src/output.cc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/output.cc b/src/output.cc index c2fa83ac..09d3ad9e 100644 --- a/src/output.cc +++ b/src/output.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -284,8 +284,9 @@ void report_accounts::flush() std::ostream& out(report.output_stream); format_t prepend_format; std::size_t prepend_width; + bool do_prepend_format; - if (report.HANDLED(prepend_format_)) { + if ((do_prepend_format = report.HANDLED(prepend_format_))) { prepend_format.parse_format(report.HANDLER(prepend_format_).str()); prepend_width = report.HANDLED(prepend_width_) ? lexical_cast(report.HANDLER(prepend_width_).str()) @@ -293,7 +294,7 @@ void report_accounts::flush() } foreach (accounts_pair& entry, accounts) { - if (prepend_format) { + if (do_prepend_format) { bind_scope_t bound_scope(report, *entry.first); out.width(static_cast(prepend_width)); out << prepend_format(bound_scope); -- cgit v1.2.3 From f455d65992cc7189a2a6f60386901af9ef6eec68 Mon Sep 17 00:00:00 2001 From: Tim Landscheidt Date: Mon, 14 Jan 2019 20:22:25 +0000 Subject: Prefer system utf8cpp if available The current logic always uses the bundled utf8cpp. This is contrary to the stated intent of commit 1d7dd3e082be8a046f21d4a2d51026ac3c1f7c14 if UTFCPP_PATH is not set explicitly. --- cmake/FindUtfcpp.cmake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmake/FindUtfcpp.cmake b/cmake/FindUtfcpp.cmake index 185a8d88..93828ef4 100644 --- a/cmake/FindUtfcpp.cmake +++ b/cmake/FindUtfcpp.cmake @@ -10,7 +10,8 @@ set(UTFCPP_FOUND FALSE) find_path(UTFCPP_INCLUDE_DIR NAMES utf8.h - HINTS "${UTFCPP_PATH}" "${PROJECT_SOURCE_DIR}/lib/utfcpp/v2_0/source" + HINTS "${UTFCPP_PATH}" + PATHS "${PROJECT_SOURCE_DIR}/lib/utfcpp/v2_0/source" ) if (UTFCPP_INCLUDE_DIR) -- cgit v1.2.3 From 5682f377aed5b0db6b6c4a44b1d8868103b7e9f7 Mon Sep 17 00:00:00 2001 From: Martin Michlmayr Date: Tue, 15 Jan 2019 20:55:53 -0300 Subject: Fix parsing issue involving effective dates Cory Duplantis reported that "A specially crafted journal file can cause [an] integer underflow resulting in code execution". Cory provided this test case: Expenses:Food:Groceries $ 37.50 ; ] [=2004/01/01] Note the ] that comes before [ after the ;. This issue was reported and described in great detail by Cory Duplantis of Cisco Talos. This issue is known as TALOS-2017-0303 and has been assigned CVE-2017-2807. Cory's description can be found at https://www.talosintelligence.com/vulnerability_reports/TALOS-2017-0303 Fixes #1722 --- doc/NEWS | 3 +++ src/item.cc | 2 +- test/regress/1722.test | 12 ++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 test/regress/1722.test diff --git a/doc/NEWS b/doc/NEWS index c22461ab..80617b08 100644 --- a/doc/NEWS +++ b/doc/NEWS @@ -42,6 +42,9 @@ - Do not parse user-specified init-file twice +- Fix parsing issue of effective dates (bug #1722, TALOS-2017-0303, + CVE-2017-2807) + - Python: Removed double quotes from Unicode values. - Python: Ensure that parse errors produce useful RuntimeErrors diff --git a/src/item.cc b/src/item.cc index bd025c52..7132103e 100644 --- a/src/item.cc +++ b/src/item.cc @@ -152,7 +152,7 @@ void item_t::parse_tags(const char * p, if (const char * b = std::strchr(p, '[')) { if (*(b + 1) != '\0' && (std::isdigit(*(b + 1)) || *(b + 1) == '=')) { - if (const char * e = std::strchr(p, ']')) { + if (const char * e = std::strchr(b, ']')) { char buf[256]; std::strncpy(buf, b + 1, static_cast(e - b - 1)); buf[e - b - 1] = '\0'; diff --git a/test/regress/1722.test b/test/regress/1722.test new file mode 100644 index 00000000..432a19b2 --- /dev/null +++ b/test/regress/1722.test @@ -0,0 +1,12 @@ + +2003/12/20 Organic Co-op + Expenses:Food:Groceries $ 37.50 ; ] [=2004/01/01] + Assets:Cash $-37.50 + +test bal + $ -37.50 Assets:Cash + $ 37.50 Expenses:Food:Groceries +-------------------- + 0 +end test + -- cgit v1.2.3 From 347029a803475c21bf9fa508f27b0ed7233ff312 Mon Sep 17 00:00:00 2001 From: Tim Landscheidt Date: Wed, 16 Jan 2019 14:37:13 +0000 Subject: Compile strptime.cc only on Windows --- src/CMakeLists.txt | 10 ++++++++-- src/strptime.cc | 7 ++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 71d9478a..9cd54dbe 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -52,7 +52,6 @@ set(LEDGER_SOURCES times.cc error.cc utils.cc - strptime.cc wcwidth.cc) if (HAVE_BOOST_PYTHON) @@ -134,9 +133,16 @@ set(LEDGER_INCLUDES value.h views.h xact.h - strptime.h ${PROJECT_BINARY_DIR}/system.hh) +# Windows provides no strptime(), so supply our own. +if (WIN32 OR CYGWIN) + list(APPEND LEDGER_INCLUDES + strptime.h) + list(APPEND LEDGER_SOURCES + strptime.cc) +endif() + if (CMAKE_BUILD_TYPE STREQUAL "Debug") if (CMAKE_CXX_COMPILER MATCHES "clang\\+\\+") add_definitions( diff --git a/src/strptime.cc b/src/strptime.cc index 069b9267..ac6885a6 100644 --- a/src/strptime.cc +++ b/src/strptime.cc @@ -12,7 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if defined(_WIN32) || defined(__CYGWIN__) +#if !(defined(_WIN32) || defined(__CYGWIN__)) +#error This file should only be compiled on Windows. +#endif + // Implement strptime under windows #include "strptime.h" @@ -200,5 +203,3 @@ static char* _strptime(const char *s, const char *format, struct tm *tm) { char* strptime(const char *buf, const char *fmt, struct tm *tm) { return _strptime(buf, fmt, tm); } - -#endif // _WIN32 -- cgit v1.2.3 From d803e6f4825b83f1e8c193457e44e7afdb43e72f Mon Sep 17 00:00:00 2001 From: Pascal Fleury Date: Thu, 17 Jan 2019 01:13:12 +0100 Subject: Make acprep work with Python3. --- acprep | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acprep b/acprep index bcbf1308..72275c2e 100755 --- a/acprep +++ b/acprep @@ -510,7 +510,7 @@ class PrepareBuild(CommandLineApp): self.log.info("Installing Ledger's build dependencies ...") - system = self.get_stdout('uname', '-s') + system = self.get_stdout('uname', '-s').decode('utf8') if system == 'Darwin': if exists('/opt/local/bin/port'): -- cgit v1.2.3 From d1a22677c94f604f47fa637adda850e1df745f96 Mon Sep 17 00:00:00 2001 From: Pascal Fleury Date: Sun, 13 Jan 2019 23:03:50 +0100 Subject: update deps. --- acprep | 81 ++++++++++++++++++++++++++++++++++-------------------------------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/acprep b/acprep index 72275c2e..a6f34cad 100755 --- a/acprep +++ b/acprep @@ -62,17 +62,7 @@ class BoostInfo(object): if system == 'centos': return [ 'boost-devel' ] - elif system == 'ubuntu-xenial': - return [ 'libboost-dev', - 'libboost-date-time-dev', - 'libboost-filesystem-dev', - 'libboost-iostreams-dev', - 'libboost-python-dev', - 'libboost-regex-dev', - 'libboost-system-dev', - 'libboost-test-dev' ] - - elif system == 'ubuntu-trusty': + elif system == 'ubuntu-bionic' or system == 'ubuntu-xenial' or system == 'ubuntu-trusty': return [ 'libboost-dev', 'libboost-date-time-dev', 'libboost-filesystem-dev', @@ -548,14 +538,17 @@ class PrepareBuild(CommandLineApp): if exists('/etc/issue'): issue = open('/etc/issue') if issue.readline().startswith('Ubuntu'): - release = open('/etc/lsb-release') - info = release.read() - release.close() - if re.search('trusty', info): - self.log.info('Looks like you are using APT on Ubuntu Trusty') - packages = [ - 'sudo', 'apt-get', 'install', - 'build-essential', + info = dict([line.strip().split('=', 1) + for line in open('/etc/lsb-release')]) + release = info['DISTRIB_CODENAME'] + self.log.info('Looks like you are using APT on Ubuntu ' + release) + packages = [ + 'sudo', 'apt-get', 'install', + 'build-essential', + ] + + if release == 'bionic': + packages.extend([ 'doxygen', 'cmake', 'ninja-build', @@ -570,12 +563,9 @@ class PrepareBuild(CommandLineApp): 'lcov', 'libutfcpp-dev', 'sloccount' - ] + BoostInfo().dependencies('ubuntu-trusty') - elif re.search('xenial', info): - self.log.info('Looks like you are using APT on Ubuntu Xenial') - packages = [ - 'sudo', 'apt-get', 'install', - 'build-essential', + ]) + elif release == 'trusty': + packages.extend([ 'doxygen', 'cmake', 'ninja-build', @@ -590,13 +580,27 @@ class PrepareBuild(CommandLineApp): 'lcov', 'libutfcpp-dev', 'sloccount' - ] + BoostInfo().dependencies('ubuntu-xenial') - elif re.search('saucy', info): - self.log.info('Looks like you are using APT on Ubuntu Saucy') - packages = [ - 'sudo', 'apt-get', 'install', - 'build-essential', - 'libtool', + ]) + elif release == 'xenial': + packages.extend([ + 'doxygen', + 'cmake', + 'ninja-build', + 'zlib1g-dev', + 'libbz2-dev', + 'python-dev', + 'libgmp3-dev', + 'libmpfr-dev', + 'gettext', + 'libedit-dev', + 'texinfo', + 'lcov', + 'libutfcpp-dev', + 'sloccount' + ]) + elif release == 'saucy': + packages.extend([ + 'doxygen', 'cmake', 'ninja-build', 'zlib1g-dev', @@ -609,12 +613,9 @@ class PrepareBuild(CommandLineApp): 'texinfo', 'lcov', 'sloccount' - ] + BoostInfo().dependencies('ubuntu-saucy') - elif re.search('precise', info): - self.log.info('Looks like you are using APT on Ubuntu Precise') - packages = [ - 'sudo', 'apt-get', 'install', - 'build-essential', + ]) + elif release == 'precise': + packages.extend([ 'libtool', 'cmake', 'zlib1g-dev', @@ -628,11 +629,13 @@ class PrepareBuild(CommandLineApp): 'lcov', 'libutfcpp-dev', 'sloccount' - ] + BoostInfo().dependencies('ubuntu-precise') + ]) else: self.log.info('I do not recognize your version of Ubuntu!') packages = None if packages: + packages.extend( + BoostInfo().dependencies('ubuntu-' + release)) self.log.info('Executing: ' + ' '.join(packages)) self.execute(*packages) -- cgit v1.2.3 From 60f40baa2e508c3960e3899752c6dc0d6cdc0d55 Mon Sep 17 00:00:00 2001 From: Pascal Fleury Date: Thu, 17 Jan 2019 01:16:19 +0100 Subject: Update README info. --- README.md | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/README.md b/README.md index 0ddec293..1b0c3ba9 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ run: ### Ubuntu If you're going to build on Ubuntu, `sudo apt-get install ...` the -following packages (current as of Ubuntu 14.04): +following packages (current as of Ubuntu 18.04): $ sudo apt-get install build-essential cmake doxygen \ libboost-system-dev libboost-dev python-dev gettext git \ @@ -122,14 +122,6 @@ following packages (current as of Ubuntu 14.04): libboost-iostreams-dev libboost-python-dev libboost-regex-dev \ libboost-test-dev libedit-dev libgmp3-dev libmpfr-dev texinfo -Or, for Ubuntu 12.04: - - $ sudo apt-get install build-essential cmake zlib1g-dev libbz2-dev \ - python-dev gettext libgmp3-dev libmpfr-dev libboost-dev \ - libboost-regex-dev libboost-date-time-dev \ - libboost-filesystem-dev libboost-python-dev texinfo lcov \ - sloccount libboost-iostreams-dev libboost-test-dev - ### Debian Debian 9 (stretch), Debian 10 (buster), Debian testing and Debian unstable -- cgit v1.2.3 From b19b1115e6b60338d025c660c8a1f05c1ef1e644 Mon Sep 17 00:00:00 2001 From: Tommi Komulainen Date: Thu, 17 Jan 2019 18:41:50 +0100 Subject: Fix some boost format strings Fixes: Error: boost::bad_format_string: format-string is ill-formed --- src/draft.cc | 2 +- src/xact.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/draft.cc b/src/draft.cc index 424b7a9a..12d7faf7 100644 --- a/src/draft.cc +++ b/src/draft.cc @@ -68,7 +68,7 @@ void draft_t::xact_template_t::dump(std::ostream& out) const } else { foreach (const post_template_t& post, posts) { out << std::endl - << _f("[Posting \"%1\"]") % (post.from ? _("from") : _("to")) + << _f("[Posting \"%1%\"]") % (post.from ? _("from") : _("to")) << std::endl; if (post.account_mask) diff --git a/src/xact.h b/src/xact.h index b0a4417e..26e263fc 100644 --- a/src/xact.h +++ b/src/xact.h @@ -115,7 +115,7 @@ public: virtual string description() { if (pos) { std::ostringstream buf; - buf << _f("transaction at line %1") << pos->beg_line; + buf << _f("transaction at line %1%") % pos->beg_line; return buf.str(); } else { return string(_("generated transaction")); @@ -177,7 +177,7 @@ public: virtual string description() { if (pos) { std::ostringstream buf; - buf << _f("automated transaction at line %1") << pos->beg_line; + buf << _f("automated transaction at line %1%") % pos->beg_line; return buf.str(); } else { return string(_("generated automated transaction")); @@ -220,7 +220,7 @@ class period_xact_t : public xact_base_t virtual string description() { if (pos) { std::ostringstream buf; - buf << _f("periodic transaction at line %1") << pos->beg_line; + buf << _f("periodic transaction at line %1%") % pos->beg_line; return buf.str(); } else { return string(_("generated periodic transaction")); -- cgit v1.2.3 From 0fa7a49e7abe529b9ff037e97c0e392a86d9356d Mon Sep 17 00:00:00 2001 From: Tim Landscheidt Date: Thu, 17 Jan 2019 22:48:08 +0000 Subject: Remove unused development code related to USE_BOOST_FACETS The code can be accessed by Git history and reused in a branch if necessary. --- src/system.hh.in | 1 - src/times.cc | 83 ++------------------------------------------------------ 2 files changed, 2 insertions(+), 82 deletions(-) diff --git a/src/system.hh.in b/src/system.hh.in index c093e334..1f4a3958 100644 --- a/src/system.hh.in +++ b/src/system.hh.in @@ -88,7 +88,6 @@ #include #include #include -#include #include #include #include diff --git a/src/times.cc b/src/times.cc index eda71ae7..db0d74ff 100644 --- a/src/times.cc +++ b/src/times.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -43,23 +43,11 @@ optional epoch; date_time::weekdays start_of_week = gregorian::Sunday; -//#define USE_BOOST_FACETS 1 -#if defined(USE_BOOST_FACETS) -#error "Boost facets are not quite working yet" -#endif - namespace { template class temporal_io_t : public noncopyable { - string fmt_str; -#if defined(USE_BOOST_FACETS) - std::istringstream input_stream; - std::ostringstream output_stream; - InputFacetType * input_facet; - OutputFacetType * output_facet; - std::string temp_string; -#endif // USE_BOOST_FACETS + string fmt_str; public: date_traits_t traits; @@ -71,15 +59,6 @@ namespace { icontains(fmt_str, "%m") || icontains(fmt_str, "%b"), icontains(fmt_str, "%d")), input(_input) { -#if defined(USE_BOOST_FACETS) - if (input) { - input_facet = new InputFacetType(fmt_str); - input_stream.imbue(std::locale(std::locale::classic(), input_facet)); - } else { - output_facet = new OutputFacetType(fmt_str); - output_stream.imbue(std::locale(std::locale::classic(), output_facet)); - } -#endif // USE_BOOST_FACETS } void set_format(const char * fmt) { @@ -88,29 +67,15 @@ namespace { icontains(fmt_str, "%m") || icontains(fmt_str, "%b"), icontains(fmt_str, "%d")); -#if defined(USE_BOOST_FACETS) - if (input) - input_facet->format(fmt_str); - else - output_facet->format(fmt_str); -#endif // USE_BOOST_FACETS } T parse(const char *) {} std::string format(const T& when) { -#if defined(USE_BOOST_FACETS) - output_stream.str(temp_string); - output_stream.seekp(std::ios_base::beg); - output_stream.clear(); - output_stream << when; - return output_stream.str(); -#else // USE_BOOST_FACETS std::tm data(to_tm(when)); char buf[128]; std::strftime(buf, 127, fmt_str.c_str(), &data); return buf; -#endif // USE_BOOST_FACETS } }; @@ -119,34 +84,12 @@ namespace { posix_time::time_facet> ::parse(const char * str) { -#if defined(USE_BOOST_FACETS) - input_stream.seekg(std::ios_base::beg); - input_stream.clear(); - input_stream.str(str); - - datetime_t when; - input_stream >> when; -#if DEBUG_ON - if (when.is_not_a_date_time()) - DEBUG("times.parse", "Failed to parse date/time '" << str - << "' using pattern '" << fmt_str << "'"); -#endif - - if (! when.is_not_a_date_time() && - input_stream.good() && ! input_stream.eof() && - input_stream.peek() != EOF) { - DEBUG("times.parse", "This string has leftovers: '" << str << "'"); - return datetime_t(); - } - return when; -#else // USE_BOOST_FACETS std::tm data; std::memset(&data, 0, sizeof(std::tm)); if (strptime(str, fmt_str.c_str(), &data)) return posix_time::ptime_from_tm(data); else return datetime_t(); -#endif // USE_BOOST_FACETS } template <> @@ -154,27 +97,6 @@ namespace { gregorian::date_facet> ::parse(const char * str) { -#if defined(USE_BOOST_FACETS) - input_stream.seekg(std::ios_base::beg); - input_stream.clear(); - input_stream.str(str); - - date_t when; - input_stream >> when; -#if DEBUG_ON - if (when.is_not_a_date()) - DEBUG("times.parse", "Failed to parse date '" << str - << "' using pattern '" << fmt_str << "'"); -#endif - - if (! when.is_not_a_date() && - input_stream.good() && ! input_stream.eof() && - input_stream.peek() != EOF) { - DEBUG("times.parse", "This string has leftovers: '" << str << "'"); - return date_t(); - } - return when; -#else // USE_BOOST_FACETS std::tm data; std::memset(&data, 0, sizeof(std::tm)); data.tm_year = CURRENT_DATE().year() - 1900; @@ -183,7 +105,6 @@ namespace { return gregorian::date_from_tm(data); else return date_t(); -#endif // USE_BOOST_FACETS } typedef temporal_io_t Date: Thu, 17 Jan 2019 23:35:58 +0000 Subject: Drop conditionals for Boost earlier than 1.49 Ledger requires Boost 1.49 or later and enforces this in CMakeLists.txt. This means BOOST_VERSION will always be 104900 or higher. Also, since Boost 1.46, BOOST_FILESYSTEM_VERSION is 3. --- src/context.h | 6 +----- src/main.cc | 3 --- src/pyinterp.cc | 16 +--------------- src/stream.cc | 6 +----- src/textual.cc | 23 +---------------------- src/value.cc | 6 +----- src/value.h | 10 +--------- 7 files changed, 6 insertions(+), 64 deletions(-) diff --git a/src/context.h b/src/context.h index 0af59930..d69df73f 100644 --- a/src/context.h +++ b/src/context.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -113,11 +113,7 @@ inline parse_context_t open_for_reading(const path& pathname, const path& cwd) { path filename = resolve_path(pathname); -#if BOOST_VERSION >= 104600 && BOOST_FILESYSTEM_VERSION >= 3 filename = filesystem::absolute(filename, cwd); -#else - filename = filesystem::complete(filename, cwd); -#endif if (! exists(filename) || is_directory(filename)) throw_(std::runtime_error, _f("Cannot read journal file %1%") % filename); diff --git a/src/main.cc b/src/main.cc index 4348e8a6..218579cf 100644 --- a/src/main.cc +++ b/src/main.cc @@ -73,9 +73,6 @@ int main(int argc, char * argv[], char * envp[]) // Initialize global Boost/C++ environment std::ios::sync_with_stdio(false); -#if BOOST_VERSION < 104600 - filesystem::path::default_name_check(filesystem::portable_posix_name); -#endif std::signal(SIGINT, sigint_handler); #if !defined(_WIN32) && !defined(__CYGWIN__) diff --git a/src/pyinterp.cc b/src/pyinterp.cc index 19430bef..fad0b559 100644 --- a/src/pyinterp.cc +++ b/src/pyinterp.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -222,27 +222,13 @@ object python_interpreter_t::import_option(const string& str) python::list paths(sys_dict["path"]); if (contains(str, ".py")) { -#if BOOST_VERSION >= 103700 path& cwd(parsing_context.get_current().current_directory); -#if BOOST_VERSION >= 104600 && BOOST_FILESYSTEM_VERSION >= 3 path parent(filesystem::absolute(file, cwd).parent_path()); -#else - path parent(filesystem::complete(file, cwd).parent_path()); -#endif DEBUG("python.interp", "Adding " << parent << " to PYTHONPATH"); paths.insert(0, parent.string()); sys_dict["path"] = paths; -#if BOOST_VERSION >= 104600 name = file.stem().string(); -#else - name = file.stem(); -#endif -#else // BOOST_VERSION >= 103700 - paths.insert(0, file.branch_path().string()); - sys_dict["path"] = paths; - name = file.leaf(); -#endif // BOOST_VERSION >= 103700 } try { diff --git a/src/stream.cc b/src/stream.cc index c4bbb42e..91a190d8 100644 --- a/src/stream.cc +++ b/src/stream.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -89,11 +89,7 @@ namespace { else { // parent close(pfd[0]); typedef iostreams::stream fdstream; -#if BOOST_VERSION >= 104400 *os = new fdstream(pfd[1], iostreams::never_close_handle); -#else // BOOST_VERSION >= 104400 - *os = new fdstream(pfd[1]); -#endif // BOOST_VERSION >= 104400 } return pfd[1]; #else diff --git a/src/textual.cc b/src/textual.cc index 3416073b..33744ec3 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -743,17 +743,8 @@ void instance_t::include_directive(char * line) DEBUG("textual.include", "resolved path: " << filename.string()); mask_t glob; -#if BOOST_VERSION >= 103700 path parent_path = filename.parent_path(); -#if BOOST_VERSION >= 104600 glob.assign_glob('^' + filename.filename().string() + '$'); -#else - glob.assign_glob('^' + filename.filename() + '$'); -#endif -#else // BOOST_VERSION >= 103700 - path parent_path = filename.branch_path(); - glob.assign_glob('^' + filename.leaf() + '$'); -#endif // BOOST_VERSION >= 103700 bool files_found = false; if (exists(parent_path)) { @@ -761,21 +752,9 @@ void instance_t::include_directive(char * line) for (filesystem::directory_iterator iter(parent_path); iter != end; ++iter) { -#if BOOST_VERSION <= 103500 - if (is_regular(*iter)) -#else if (is_regular_file(*iter)) -#endif { -#if BOOST_VERSION >= 103700 -#if BOOST_VERSION >= 104600 string base = (*iter).path().filename().string(); -#else - string base = (*iter).filename(); -#endif -#else // BOOST_VERSION >= 103700 - string base = (*iter).leaf(); -#endif // BOOST_VERSION >= 103700 if (glob.match(base)) { journal_t * journal = context.journal; account_t * master = top_account(); diff --git a/src/value.cc b/src/value.cc index 1841505d..5ce30530 100644 --- a/src/value.cc +++ b/src/value.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -129,11 +129,7 @@ value_t::operator bool() const void value_t::set_type(type_t new_type) { if (new_type == VOID) { -#if BOOST_VERSION >= 103700 storage.reset(); -#else - storage = intrusive_ptr(); -#endif } else { if (! storage || storage->refc > 1) storage = new storage_t; diff --git a/src/value.h b/src/value.h index 35581bfb..e44f9354 100644 --- a/src/value.h +++ b/src/value.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -878,22 +878,14 @@ public: VERIFY(! is_null()); if (! is_sequence()) { -#if BOOST_VERSION >= 103700 storage.reset(); -#else - storage = intrusive_ptr(); -#endif } else { as_sequence_lval().pop_back(); const sequence_t& seq(as_sequence()); std::size_t new_size = seq.size(); if (new_size == 0) { -#if BOOST_VERSION >= 103700 storage.reset(); -#else - storage = intrusive_ptr(); -#endif } else if (new_size == 1) { *this = seq.front(); -- cgit v1.2.3 From 53f4035a2f4a6f33b447ce3658c4dc062058c422 Mon Sep 17 00:00:00 2001 From: Tim Landscheidt Date: Sat, 19 Jan 2019 15:04:40 +0000 Subject: Use standard GCC in Travis CI Commit 4c4367fe6b7f184605c900735fc5b646f45311c1 added some logic to compile Ledger with GCC 4.8 as Travis CI's Ubuntu Precise environments only offered 4.6 at that time. Since then, the default image has changed to Ubuntu Trusty which provides GCC 4.8. --- .travis.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index d10d4466..59ec77b2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,3 @@ -# Since the Travis CI environment http://docs.travis-ci.com/user/ci-environment/ -# provides GNU GCC 4.6, which does not support -std=c++11 GNU GCC 4.8 is installed - # NOTE: Please validate this file after editing it using # Travis WebLint https://lint.travis-ci.org/ # or travis-lint https://github.com/travis-ci/travis-lint @@ -64,11 +61,8 @@ addons: branch_pattern: coverity apt: sources: - - ubuntu-toolchain-r-test #- boost-latest packages: - - gcc-4.8 - - g++-4.8 - libgmp-dev - libmpfr-dev - libedit-dev @@ -86,7 +80,6 @@ before_install: # - if [ "${TRAVIS_BRANCH}" = "master" ]; then export BOOST_VERSION="${BOOST_VERSION_MIN}"; else export BOOST_VERSION="${BOOST_VERSION_MAX}"; fi - export BOOST_VERSION="${BOOST_VERSION_MAX}" - if [ -n "${BOOST_VERSION}" ]; then export BOOST_ROOT="${TRAVIS_BUILD_DIR}/../boost-trunk"; export CMAKE_MODULE_PATH="${BOOST_ROOT}"; fi - - if [ "${CXX}" = "g++" ]; then export CXX="$(which g++-4.8)"; export CC="$(which gcc-4.8)"; fi - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then export DYLD_LIBRARY_PATH="${BOOST_ROOT}/lib"; fi # c++ is a symlink to clang++, but the compiler behaves differently when invoked as c++ - if [ "${TRAVIS_OS_NAME}" = "osx" -a "${CXX}" = "clang++" ]; then export CXX="$(which c++)"; export CC="$(which cc)"; fi -- cgit v1.2.3 From 219869caf7e6bb2b70aef3e0e9a4c52c9fd96539 Mon Sep 17 00:00:00 2001 From: Tim Landscheidt Date: Sun, 20 Jan 2019 03:18:15 +0000 Subject: Remove broken and disabled Travis CI configurations The Travis CI configurations for macOS, Clang on Linux and CheckTexinfo.py and CheckManpage.py are broken and disabled or ignored. They appear to be non-trivial to fix, so the current stub is probably more distracting than helpful while also making changes to the working Linux configuration more difficult. --- .travis.yml | 37 ------------------------------------- tools/travis-before_install.sh | 4 ---- tools/travis-install.sh | 10 ---------- 3 files changed, 51 deletions(-) diff --git a/.travis.yml b/.travis.yml index 59ec77b2..5cac92d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,10 +5,8 @@ language: cpp compiler: - gcc - - clang os: - linux - - osx sudo: false cache: apt: true @@ -22,35 +20,9 @@ env: - BOOST_VERSION_MAX="1.61.0" # List of required boost libraries to build - BOOST_LIBS="date_time,filesystem,iostreams,python,regex,system,test" - # List of required Homebrew formulae to install - - BREWS="gmp,mpfr" # Encrypted COVERITY_SCAN_TOKEN - secure: "mYNxD1B8WNSvUeKzInehZ7syi2g1jH2ymeSQxoeKKD2duq3pvNWPdZdc4o9MlWQcAqcz58rhFZRIpuEWCnP0LbbJaG+MyuemMn9uAmg9Y4gFpMsBPHuTdf8pO3rDex+tkrr9puEJFgL+QV/TehxO6NDDpx7UdYvJb+4aZD/auYI=" -matrix: - exclude: - - os: linux - compiler: clang - # Compiling ledger on Linux with clang - # either crashes clang or results in a ledger binary that crashes with SIGSEGV. - - os: osx - compiler: gcc - # On macOS building ledger with GNU GCC 4.8 fails due to - # undefined symbols, maybe because boost was not being built with g++-4.8. - # Undefined symbols for architecture x86_64: - # "boost::re_detail::perl_matcher >, boost::regex_traits > >::construct_init(boost::basic_regex > > const&, boost::regex_constants::_match_flags)", referenced from: - # boost::re_detail::perl_matcher >, boost::regex_traits > >::perl_matcher(char const*, char const*, boost::match_results > >&, boost::basic_regex > > const&, boost::regex_constants::_match_flags, char const*) in main.cc.o - # boost::re_detail::perl_matcher >, boost::regex_traits > >::perl_matcher(char const*, char const*, boost::match_results > >&, boost::basic_regex > > const&, boost::regex_constants::_match_flags, char const*) in global.cc.o - # "boost::re_detail::perl_matcher >, boost::regex_traits > >::find()", referenced from: - # bool boost::regex_search > >(char const*, char const*, boost::basic_regex > > const&, boost::regex_constants::_match_flags) in main.cc.o - # bool boost::regex_search > >(char const*, char const*, boost::basic_regex > > const&, boost::regex_constants::_match_flags) in global.cc.o - - os: osx - compiler: clang - # On macOS building ledger with clang fails due to - # dyld: Library not loaded: libboost_python.dylib - # Referenced from: /Users/travis/build/ledger/ledger/ledger - # Reason: image not found - addons: coverity_scan: project: @@ -80,9 +52,6 @@ before_install: # - if [ "${TRAVIS_BRANCH}" = "master" ]; then export BOOST_VERSION="${BOOST_VERSION_MIN}"; else export BOOST_VERSION="${BOOST_VERSION_MAX}"; fi - export BOOST_VERSION="${BOOST_VERSION_MAX}" - if [ -n "${BOOST_VERSION}" ]; then export BOOST_ROOT="${TRAVIS_BUILD_DIR}/../boost-trunk"; export CMAKE_MODULE_PATH="${BOOST_ROOT}"; fi - - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then export DYLD_LIBRARY_PATH="${BOOST_ROOT}/lib"; fi - # c++ is a symlink to clang++, but the compiler behaves differently when invoked as c++ - - if [ "${TRAVIS_OS_NAME}" = "osx" -a "${CXX}" = "clang++" ]; then export CXX="$(which c++)"; export CC="$(which cc)"; fi - tools/travis-before_install.sh install: @@ -96,12 +65,6 @@ script: - ctest --output-on-failure - PYTHONPATH=. python python/demo.py -after_script: - # These scripts are run for informational purposes and - # should be reintegrated into CTest once they reliably verify the documentation. - - python test/CheckTexinfo.py -l ledger -s . - - python test/CheckManpage.py -l ledger -s . - notifications: email: on_success: change diff --git a/tools/travis-before_install.sh b/tools/travis-before_install.sh index fe010945..365fcab1 100755 --- a/tools/travis-before_install.sh +++ b/tools/travis-before_install.sh @@ -4,10 +4,6 @@ set -e set -o pipefail -if [ "${TRAVIS_OS_NAME}" = "osx" ]; then - brew update -fi - if [ -n "${BOOST_VERSION}" ]; then mkdir -p $BOOST_ROOT wget --no-verbose --output-document=- \ diff --git a/tools/travis-install.sh b/tools/travis-install.sh index 4e8bdc48..995f18df 100755 --- a/tools/travis-install.sh +++ b/tools/travis-install.sh @@ -4,16 +4,6 @@ set -e set -o pipefail -if [ "${TRAVIS_OS_NAME}" = "osx" ]; then - for formula in $(echo "${BREWS//,/ }"); do - echo "Checking ${formula} formula" - brew outdated "${formula}" \ - || (brew unlink "${formula}" - brew install "${formula}" - ) - done -fi - if [ -d "${BOOST_ROOT}" ]; then (cd "${BOOST_ROOT}" ./bootstrap.sh --with-libraries="${BOOST_LIBS}" -- cgit v1.2.3 From 0645976caecc581923d87ca3cc8658a9f7c35f52 Mon Sep 17 00:00:00 2001 From: Tim Landscheidt Date: Sun, 20 Jan 2019 03:46:57 +0000 Subject: Use build matrix to specify Boost versions for Travis CI In Travis CI, versions of libraries, etc. to build against are typically specified in a build matrix. In addition, currently there is no way to build against the distribution-provided Boost version. This change uses a build matrix for BOOST_VERSION and allows that variable to be empty for building against the distribution-provided Boost version. --- .travis.yml | 50 ++++++++++++++++++++++++------------------ tools/travis-before_install.sh | 12 ---------- tools/travis-install.sh | 12 ---------- 3 files changed, 29 insertions(+), 45 deletions(-) delete mode 100755 tools/travis-before_install.sh delete mode 100755 tools/travis-install.sh diff --git a/.travis.yml b/.travis.yml index 5cac92d2..ae2ff727 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,15 +13,14 @@ cache: env: global: - # Boost version to use: - # _MIN is used when building the master branch - # _MAX is used when building any other branch - - BOOST_VERSION_MIN="1.49.0" - - BOOST_VERSION_MAX="1.61.0" # List of required boost libraries to build - BOOST_LIBS="date_time,filesystem,iostreams,python,regex,system,test" # Encrypted COVERITY_SCAN_TOKEN - secure: "mYNxD1B8WNSvUeKzInehZ7syi2g1jH2ymeSQxoeKKD2duq3pvNWPdZdc4o9MlWQcAqcz58rhFZRIpuEWCnP0LbbJaG+MyuemMn9uAmg9Y4gFpMsBPHuTdf8pO3rDex+tkrr9puEJFgL+QV/TehxO6NDDpx7UdYvJb+4aZD/auYI=" + matrix: + # Boost version to build against; an empty string means the + # distribution's default. + - BOOST_VERSION="1.61.0" addons: coverity_scan: @@ -32,30 +31,39 @@ addons: build_command: "make" branch_pattern: coverity apt: - sources: - #- boost-latest packages: - libgmp-dev - libmpfr-dev - libedit-dev - #- libboost1.55-dev - #- libboost-test1.55-dev - #- libboost-regex1.55-dev - #- libboost-python1.55-dev - #- libboost-system1.55-dev - #- libboost-date-time1.55-dev - #- libboost-iostreams1.55-dev - #- libboost-filesystem1.55-dev - #- libboost-serialization1.55-dev + - libboost-dev + - libboost-test-dev + - libboost-regex-dev + - libboost-python-dev + - libboost-system-dev + - libboost-date-time-dev + - libboost-iostreams-dev + - libboost-filesystem-dev + - libboost-serialization-dev before_install: - # - if [ "${TRAVIS_BRANCH}" = "master" ]; then export BOOST_VERSION="${BOOST_VERSION_MIN}"; else export BOOST_VERSION="${BOOST_VERSION_MAX}"; fi - - export BOOST_VERSION="${BOOST_VERSION_MAX}" - - if [ -n "${BOOST_VERSION}" ]; then export BOOST_ROOT="${TRAVIS_BUILD_DIR}/../boost-trunk"; export CMAKE_MODULE_PATH="${BOOST_ROOT}"; fi - - tools/travis-before_install.sh + - | + if [ -n "${BOOST_VERSION}" ]; then + BOOST_SOURCE="$(mktemp -d)" + BOOST_SOURCE_URL="https://sourceforge.net/projects/boost/files/boost/${BOOST_VERSION}/boost_${BOOST_VERSION//./_}.tar.bz2/download" + curl -Ls "$BOOST_SOURCE_URL" | + tar jx -C "${BOOST_SOURCE}" --strip-components 1 + fi install: - - tools/travis-install.sh + - | + if [ -n "${BOOST_VERSION}" ]; then + export BOOST_ROOT="${HOME}/boost" + pushd "${BOOST_SOURCE}" + ./bootstrap.sh --with-libraries="${BOOST_LIBS}" + ./b2 threading=multi -d0 --prefix="${BOOST_ROOT}" install + popd + rm -Rf "${BOOST_SOURCE}" + fi before_script: - cmake . -DUSE_PYTHON=ON -DBUILD_DEBUG=ON diff --git a/tools/travis-before_install.sh b/tools/travis-before_install.sh deleted file mode 100755 index 365fcab1..00000000 --- a/tools/travis-before_install.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -#set -x -set -e -set -o pipefail - -if [ -n "${BOOST_VERSION}" ]; then - mkdir -p $BOOST_ROOT - wget --no-verbose --output-document=- \ - http://sourceforge.net/projects/boost/files/boost/${BOOST_VERSION}/boost_${BOOST_VERSION//./_}.tar.bz2/download \ - | tar jxf - --strip-components=1 -C "${BOOST_ROOT}" -fi diff --git a/tools/travis-install.sh b/tools/travis-install.sh deleted file mode 100755 index 995f18df..00000000 --- a/tools/travis-install.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -#set -x -set -e -set -o pipefail - -if [ -d "${BOOST_ROOT}" ]; then - (cd "${BOOST_ROOT}" - ./bootstrap.sh --with-libraries="${BOOST_LIBS}" - ./b2 threading=multi --prefix="${BOOST_ROOT}" -d0 install - ) -fi -- cgit v1.2.3 From 4bddaff78ec1ee99521eb8774cafe3a1e7c6d36c Mon Sep 17 00:00:00 2001 From: Tim Landscheidt Date: Tue, 22 Jan 2019 14:41:41 +0000 Subject: Move garbage-input.dat to test case using it --- test/garbage-input.dat | 793 ----------------------------------------- test/regress/25A099C9.dat | 793 +++++++++++++++++++++++++++++++++++++++++ test/regress/25A099C9.test | 60 ++-- tools/update_copyright_year.sh | 4 +- 4 files changed, 825 insertions(+), 825 deletions(-) delete mode 100644 test/garbage-input.dat create mode 100644 test/regress/25A099C9.dat diff --git a/test/garbage-input.dat b/test/garbage-input.dat deleted file mode 100644 index 1b7d2101..00000000 --- a/test/garbage-input.dat +++ /dev/null @@ -1,793 +0,0 @@ -/* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * - Neither the name of New Artisans LLC nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * @defgroup math Mathematical objects - */ - -/** - * @file amount.h - * @author John Wiegley - * - * @ingroup math - * - * @brief Basic type for handling commoditized math: amount_t - * - * An amount is the most basic numerical type in Ledger, and relies on - * commodity.h to represent commoditized amounts, which allows Ledger to - * handle mathematical expressions involving disparate commodities. - * - * Amounts can be of virtually infinite size and precision. When - * division or multiplication is performed, the precision is - * automatically expanded to include as many extra digits as necessary - * to avoid losing information. - */ -#ifndef _AMOUNT_H -#define _AMOUNT_H - -#include "utils.h" -#include "times.h" -#include "flags.h" - -namespace ledger { - -class commodity_t; -struct annotation_t; -struct keep_details_t; - -DECLARE_EXCEPTION(amount_error, std::runtime_error); - -enum parse_flags_enum_t { - PARSE_DEFAULT = 0x00, - PARSE_PARTIAL = 0x01, - PARSE_SINGLE = 0x02, - PARSE_NO_MIGRATE = 0x04, - PARSE_NO_REDUCE = 0x08, - PARSE_NO_ASSIGN = 0x10, - PARSE_NO_ANNOT = 0x20, - PARSE_OP_CONTEXT = 0x40, - PARSE_SOFT_FAIL = 0x80 -}; - -typedef basic_flags_t parse_flags_t; - -/** - * @brief Encapsulate infinite-precision commoditized amounts - * - * Used to represent commoditized infinite-precision numbers, and - * uncommoditized, plain numbers. In the commoditized case, commodities - * keep track of how they are used, and are always displayed back to the - * user after the same fashion. For uncommoditized numbers, no display - * truncation is ever done. In both cases, internal precision is always - * kept to an excessive degree. - */ -class amount_t - : public ordered_field_operators > > > -{ -public: - /** Ready the amount subsystem for use. - @note Normally called by session_t::initialize(). */ - static void initialize(); - /** Shutdown the amount subsystem and free all resources. - @note Normally called by session_t::shutdown(). */ - static void shutdown(); - - static bool is_initialized; - - /** The amount's decimal precision. */ - typedef uint_least16_t precision_t; - - /** Number of places of precision by which values are extended to - avoid losing precision during division and multiplication. */ - static const std::size_t extend_by_digits = 6U; - - /** If amounts should be streamed using to_fullstring() rather than - to_string(), so that complete precision is always displayed no matter - what the precision of an individual commodity may be. */ - static bool stream_fullstrings; - -protected: - void _copy(const amount_t& amt); - void _dup(); - void _clear(); - void _release(); - - struct bigint_t; - - bigint_t * quantity; - commodity_t * commodity_; - -public: - /** @name Constructors - @{ */ - - /** Creates a value for which is_null() is true, and which has no - value or commodity. If used in a value expression it evaluates to - zero, and its commodity equals \c commodity_t::null_commodity. */ - amount_t() : quantity(NULL), commodity_(NULL) { - TRACE_CTOR(amount_t, ""); - } - - /** Convert a double to an amount. As much precision as possible is - decoded from the binary floating point number. */ - amount_t(const double val); - - /** Convert an unsigned long to an amount. It's precision is zero. */ - amount_t(const unsigned long val); - - /** Convert a long to an amount. It's precision is zero, and the sign - is preserved. */ - amount_t(const long val); - - /** Parse a string as an (optionally commoditized) amount. If no - commodity is present, the resulting commodity is \c - commodity_t::null_commodity. The number may be of infinite - precision. */ - explicit amount_t(const string& val) : quantity(NULL) { - parse(val); - TRACE_CTOR(amount_t, "const string&"); - } - /** Parse a pointer to a C string as an (optionally commoditized) - amount. If no commodity is present, the resulting commodity is \c - commodity_t::null_commodity. The number may be of infinite - precision. */ - explicit amount_t(const char * val) : quantity(NULL) { - assert(val); - parse(val); - TRACE_CTOR(amount_t, "const char *"); - } - - /*@}*/ - - /** Create an amount whose display precision is never truncated, even - if the amount uses a commodity (which normally causes "round on - streaming" to occur). This function is mostly used by debugging - code and unit tests. This is the proper way to specify \c - $100.005, where display of the extra digit precision is required. - If a regular constructor were used, the amount would stream as \c - $100.01, even though its internal value equals \c $100.005. */ - static amount_t exact(const string& value); - - /** Release the reference count held for the underlying \c - amount_t::bigint_t object. */ - ~amount_t() { - TRACE_DTOR(amount_t); - if (quantity) - _release(); - } - - /** @name Assignment and copy - @{*/ - - /** Copy an amount object. Copies are very efficient, using a - copy-on-write model. Until the copy is changed, it refers to the - same memory used by the original via reference counting. The \c - amount_t::bigint_t class in amount.cc maintains the reference. */ - amount_t(const amount_t& amt) : quantity(NULL) { - if (amt.quantity) - _copy(amt); - else - commodity_ = NULL; - TRACE_CTOR(amount_t, "copy"); - } - /** Copy an amount object, applying the given commodity annotation - details afterward. This is equivalent to doing a normal copy - (@see amount_t(const amount_t&)) and then calling - amount_t::annotate(). */ - amount_t(const amount_t& amt, const annotation_t& details) : quantity(NULL) { - assert(amt.quantity); - _copy(amt); - annotate(details); - TRACE_CTOR(amount_t, "const amount_t&, const annotation_t&"); - } - /** Assign an amount object. This is like copying if the amount was - null beforehand, otherwise the previous value's reference is must - be freed. */ - amount_t& operator=(const amount_t& amt); - - amount_t& operator=(const double val) { - return *this = amount_t(val); - } - amount_t& operator=(const unsigned long val) { - return *this = amount_t(val); - } - amount_t& operator=(const long val) { - return *this = amount_t(val); - } - - /* Assign a string to an amount. This causes the contents of the - string to be parsed, look for a commoditized or uncommoditized - amount specifier. */ - amount_t& operator=(const string& str) { - return *this = amount_t(str); - } - amount_t& operator=(const char * str) { - assert(str); - return *this = amount_t(str); - } - - /*@}*/ - - /** @name Comparison - @{ */ - - /** Compare two amounts, returning a number less than zero if \p amt - is greater, exactly zero if they are equal, and greater than zero - if \p amt is less. This method is used to implement all of the - other comparison methods.*/ - int compare(const amount_t& amt) const; - - /** Test two amounts for equality. First the commodity pointers are - quickly tested, then the multi-precision values themselves must be - compared. */ - bool operator==(const amount_t& amt) const; - - template - bool operator==(const T& val) const { - return compare(val) == 0; - } - template - bool operator<(const T& amt) const { - return compare(amt) < 0; - } - template - bool operator>(const T& amt) const { - return compare(amt) > 0; - } - - /*@}*/ - - /** @name Binary arithmetic - */ - /*@{*/ - - amount_t& operator+=(const amount_t& amt); - amount_t& operator-=(const amount_t& amt); - amount_t& operator*=(const amount_t& amt) { - return multiply(amt); - } - amount_t& multiply(const amount_t& amt, bool ignore_commodity = false); - - /** Divide two amounts while extending the precision to preserve the - accuracy of the result. For example, if \c 10 is divided by \c 3, - the result ends up having a precision of \link - amount_t::extend_by_digits \endlink place to avoid losing internal - resolution. */ - amount_t& operator/=(const amount_t& amt); - - /*@}*/ - - /** @name Unary arithmetic - @{ */ - - /** Return an amount's internal precision. To find the precision it - should be displayed at -- assuming it was not created using - amount_t::exact() -- use the following expression instead: - @code - amount.commodity().precision() - @endcode */ - precision_t precision() const; - bool keep_precision() const; - void set_keep_precision(const bool keep = true) const; - precision_t display_precision() const; - - /** Returns the negated value of an amount. - @see operator-() - */ - amount_t negated() const { - amount_t temp(*this); - temp.in_place_negate(); - return temp; - } - void in_place_negate(); - - amount_t operator-() const { - return negated(); - } - - /** Returns the absolute value of an amount. Equivalent to: - @code - (x < * 0) ? - x : x - @endcode - */ - amount_t abs() const { - if (sign() < 0) - return negated(); - return *this; - } - - amount_t inverted() const { - amount_t temp(*this); - temp.in_place_invert(); - return temp; - } - void in_place_invert(); - - /** Yields an amount whose display precision when output is truncated - to the display precision of its commodity. This is normally the - default state of an amount, but if one has become unrounded, this - sets the "keep precision" state back to false. - @see set_keep_precision */ - amount_t rounded() const { - amount_t temp(*this); - temp.in_place_round(); - return temp; - } - void in_place_round(); - - /** Yields an amount which has lost all of its extra precision, beyond what - the display precision of the commodity would have printed. */ - amount_t truncated() const { - amount_t temp(*this); - temp.in_place_truncate(); - return temp; - } - 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. */ - amount_t floored() const { - amount_t temp(*this); - temp.in_place_floor(); - return temp; - } - void in_place_floor(); - - /** Yields an amount which has lost all of its extra precision, beyond what - the display precision of the commodity would have printed. */ - amount_t ceilinged() const { - amount_t temp(*this); - temp.in_place_ceiling(); - return temp; - } - void in_place_ceiling(); - - /** Yields an amount whose display precision is never truncated, even - though its commodity normally displays only rounded values. */ - amount_t unrounded() const { - amount_t temp(*this); - temp.in_place_unround(); - return temp; - } - void in_place_unround(); - - /** reduces a value to its most basic commodity form, for amounts that - utilize "scaling commodities". For example, an amount of \c 1h - after reduction will be \c 3600s. - */ - amount_t reduced() const { - amount_t temp(*this); - temp.in_place_reduce(); - return temp; - } - void in_place_reduce(); - - /** unreduce(), if used with a "scaling commodity", yields the most - compact form greater than one. That is, \c 3599s will unreduce to - \c 59.98m, while \c 3601 unreduces to \c 1h. - */ - amount_t unreduced() const { - amount_t temp(*this); - temp.in_place_unreduce(); - return temp; - } - void in_place_unreduce(); - - /** Returns the historical value for an amount -- the default moment - returns the most recently known price -- based on the price history - for the given commodity (or determined automatically, if none is - provided). For example, if the amount were 10 AAPL, and - on Apr 10, 2000 each share of \c AAPL was worth \c $10, then - calling value() for that moment in time would yield the amount \c - $100.00. - */ - optional - value(const datetime_t& moment = datetime_t(), - const commodity_t * in_terms_of = NULL) const; - - optional price() const; - - /*@}*/ - - /** @name Truth tests - */ - /*@{*/ - - /** Truth tests. An amount may be truth test in several ways: - - sign() returns an integer less than, greater than, or equal to - zero depending on whether the amount is negative, zero, or - greater than zero. Note that this function tests the actual - value of the amount -- using its internal precision -- and not - the display value. To test its display value, use: - `round().sign()'. - - is_nonzero(), or operator bool, returns true if an amount's - display value is not zero. - - is_zero() returns true if an amount's display value is zero. - Thus, $0.0001 is considered zero if the current display precision - for dollars is two decimal places. - - is_realzero() returns true if an amount's actual value is zero. - Thus, $0.0001 is never considered realzero. - - is_null() returns true if an amount has no value and no - commodity. This only occurs if an uninitialized amount has never - been assigned a value. - */ - int sign() const; - - operator bool() const { - return is_nonzero(); - } - bool is_nonzero() const { - return ! is_zero(); - } - - bool is_zero() const; - bool is_realzero() const { - return sign() == 0; - } - - bool is_null() const { - if (! quantity) { - assert(! commodity_); - return true; - } - return false; - } - - /*@}*/ - - /** @name Conversion - */ - /*@{*/ - - /** Conversion methods. An amount may be converted to the same types - it can be constructed from -- with the exception of unsigned - long. Implicit conversions are not allowed in C++ (though they - are in Python), rather the following conversion methods must be - called explicitly: - - to_double([bool]) returns an amount as a double. If the optional - boolean argument is true (the default), an exception is thrown if - the conversion would lose information. - - to_long([bool]) returns an amount as a long integer. If the - optional boolean argument is true (the default), an exception is - thrown if the conversion would lose information. - - fits_in_long() returns true if to_long() would not lose - precision. - - to_string() returns an amount'ss "display value" as a string -- - after rounding the value according to the commodity's default - precision. It is equivalent to: `round().to_fullstring()'. - - to_fullstring() returns an amount's "internal value" as a string, - without any rounding. - - quantity_string() returns an amount's "display value", but - without any commodity. Note that this is different from - `number().to_string()', because in that case the commodity has - been stripped and the full, internal precision of the amount - would be displayed. - */ - double to_double() const; - long to_long() const; - bool fits_in_long() const; - - operator string() const { - return to_string(); - } - string to_string() const; - string to_fullstring() const; - string quantity_string() const; - - /*@}*/ - - /** @name Commodity methods - */ - /*@{*/ - - /** The following methods relate to an - amount's commodity: - - commodity() returns an amount's commodity. If the amount has no - commodity, the value returned is the `null_commodity'. - - has_commodity() returns true if the amount has a commodity. - - set_commodity(commodity_t) sets an amount's commodity to the - given value. Note that this merely sets the current amount to - that commodity, it does not "observe" the amount for possible - changes in the maximum display precision of the commodity, the - way that `parse' does. - - clear_commodity() sets an amount's commodity to null, such that - has_commodity() afterwards returns false. - - number() returns a commodity-less version of an amount. This is - useful for accessing just the numeric portion of an amount. - */ - commodity_t * commodity_ptr() const; - commodity_t& commodity() const { - return *commodity_ptr(); - } - - bool has_commodity() const; - void set_commodity(commodity_t& comm) { - if (! quantity) - *this = 0L; - commodity_ = &comm; - } - amount_t with_commodity(const commodity_t& comm) const { - if (commodity_ == &comm) { - return *this; - } else { - amount_t tmp(*this); - tmp.set_commodity(const_cast(comm)); - return tmp; - } - } - void clear_commodity() { - commodity_ = NULL; - } - - amount_t number() const { - if (! has_commodity()) - return *this; - - amount_t temp(*this); - temp.clear_commodity(); - return temp; - } - - /*@}*/ - - /** @name Commodity annotations - */ - /*@{*/ - - /** An amount's commodity may be annotated with special details, such as the - price it was purchased for, when it was acquired, or an arbitrary note, - identifying perhaps the lot number of an item. - - annotate_commodity(amount_t price, [datetime_t date, string tag]) - sets the annotations for the current amount's commodity. Only - the price argument is required, although it can be passed as - `none' if no price is desired. - - commodity_annotated() returns true if an amount's commodity has - any annotation details associated with it. - - annotation_details() returns all of the details of an annotated - commodity's annotations. The structure returns will evaluate as - boolean false if there are no details. - - strip_annotations() returns an amount whose commodity's annotations have - been stripped. - */ - void annotate(const annotation_t& details); - bool has_annotation() const; - - annotation_t& annotation(); - const annotation_t& annotation() const { - return const_cast(*this).annotation(); - } - - /** If the lot price is considered whenever working with commoditized - values. - - Let's say a user adds two values of the following form: - @code - 10 AAPL + 10 AAPL {$20} - @endcode - - This expression adds ten shares of Apple stock with another ten - shares that were purchased for \c $20 a share. If \c keep_price - is false, the result of this expression is an amount equal to - 20 AAPL. If \c keep_price is \c true the expression - yields an exception for adding amounts with different commodities. - In that case, a \link balance_t \endlink object must be used to - store the combined sum. */ - amount_t strip_annotations(const keep_details_t& what_to_keep) const; - - /*@}*/ - - /** @name Parsing - */ - /*@{*/ - - /** The `flags' argument of both parsing may be one or more of the - following: - - PARSE_NO_MIGRATE means to not pay attention to the way an - amount is used. Ordinarily, if an amount were $100.001, for - example, it would cause the default display precision for $ to be - "widened" to three decimal places. If PARSE_NO_MIGRATE is - used, the commodity's default display precision is not changed. - - PARSE_NO_REDUCE means not to call in_place_reduce() on the - resulting amount after it is parsed. - - These parsing methods observe the amounts they parse (unless - PARSE_NO_MIGRATE is true), and set the display details of - the corresponding commodity accordingly. This way, amounts do - not require commodities to be pre-defined in any way, but merely - displays them back to the user in the same fashion as it saw them - used. - - There is also a static convenience method called - `parse_conversion' which can be used to define a relationship - between scaling commodity values. For example, Ledger uses it to - define the relationships among various time values: - - @code - amount_t::parse_conversion("1.0m", "60s"); // a minute is 60 seconds - amount_t::parse_conversion("1.0h", "60m"); // an hour is 60 minutes - @endcode - - The method parse() is used to parse an amount from an input stream - or a string. A global operator>>() is also defined which simply - calls parse on the input stream. The parse() method has two forms: - - parse(istream, flags_t) parses an amount from the given input - stream. - - parse(string, flags_t) parses an amount from the given string. - - parse(string, flags_t) also parses an amount from a string. - */ - bool parse(std::istream& in, - const parse_flags_t& flags = PARSE_DEFAULT); - bool parse(const string& str, - const parse_flags_t& flags = PARSE_DEFAULT) { - std::istringstream stream(str); - bool result = parse(stream, flags); - return result; - } - - static void parse_conversion(const string& larger_str, - const string& smaller_str); - - /*@}*/ - - /** @name Printing - */ - /*@{*/ - - /** An amount may be output to a stream using the `print' method. There is - also a global operator<< defined which simply calls print for an amount - on the given stream. There is one form of the print method, which takes - one required argument and two arguments with default values: - - print(ostream, bool omit_commodity = false, bool full_precision = false) - prints an amounts to the given output stream, using its commodity's - default display characteristics. If `omit_commodity' is true, the - commodity will not be displayed, only the amount (although the - commodity's display precision is still used). If `full_precision' is - true, the full internal precision of the amount is displayed, regardless - of its commodity's display precision. - */ -#define AMOUNT_PRINT_NO_FLAGS 0x00 -#define AMOUNT_PRINT_RIGHT_JUSTIFY 0x01 -#define AMOUNT_PRINT_COLORIZE 0x02 -#define AMOUNT_PRINT_NO_COMPUTED_ANNOTATIONS 0x04 -#define AMOUNT_PRINT_ELIDE_COMMODITY_QUOTES 0x08 - - void print(std::ostream& out, - const uint_least8_t flags = AMOUNT_PRINT_NO_FLAGS) const; - - /*@}*/ - - /** @name Debugging - */ - /*@{*/ - - /** There are two methods defined to help with debugging: - - dump(ostream) dumps an amount to an output stream. There is - little different from print(), it simply surrounds the display - value with a marker, for example "AMOUNT($1.00)". This code is - used by other dumping code elsewhere in Ledger. - - valid() returns true if an amount is valid. This ensures that if - an amount has a commodity, it has a valid value pointer, for - example, even if that pointer simply points to a zero value. - */ - void dump(std::ostream& out) const { - out << "AMOUNT("; - print(out); - out << ")"; - } - - bool valid() const; - -#if HAVE_BOOST_SERIALIZATION -private: - /** Serialization. */ - - friend class boost::serialization::access; - - template - void serialize(Archive& ar, const unsigned int /* version */); -#endif // HAVE_BOOST_SERIALIZATION - - /*@}*/ -}; - -inline amount_t amount_t::exact(const string& value) { - amount_t temp; - temp.parse(value, PARSE_NO_MIGRATE); - return temp; -} - -inline string amount_t::to_string() const { - std::ostringstream bufstream; - print(bufstream); - return bufstream.str(); -} - -inline string amount_t::to_fullstring() const { - std::ostringstream bufstream; - unrounded().print(bufstream); - return bufstream.str(); -} - -inline string amount_t::quantity_string() const { - std::ostringstream bufstream; - number().print(bufstream); - return bufstream.str(); -} - -inline std::ostream& operator<<(std::ostream& out, const amount_t& amt) { - if (amount_t::stream_fullstrings) - amt.unrounded().print(out); - else - amt.print(out); - return out; -} -inline std::istream& operator>>(std::istream& in, amount_t& amt) { - amt.parse(in); - return in; -} - -void put_amount(property_tree::ptree& pt, const amount_t& amt, - bool wrap = true, bool commodity_details = false); - -} // namespace ledger - -#endif // _AMOUNT_H diff --git a/test/regress/25A099C9.dat b/test/regress/25A099C9.dat new file mode 100644 index 00000000..1b7d2101 --- /dev/null +++ b/test/regress/25A099C9.dat @@ -0,0 +1,793 @@ +/* + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @defgroup math Mathematical objects + */ + +/** + * @file amount.h + * @author John Wiegley + * + * @ingroup math + * + * @brief Basic type for handling commoditized math: amount_t + * + * An amount is the most basic numerical type in Ledger, and relies on + * commodity.h to represent commoditized amounts, which allows Ledger to + * handle mathematical expressions involving disparate commodities. + * + * Amounts can be of virtually infinite size and precision. When + * division or multiplication is performed, the precision is + * automatically expanded to include as many extra digits as necessary + * to avoid losing information. + */ +#ifndef _AMOUNT_H +#define _AMOUNT_H + +#include "utils.h" +#include "times.h" +#include "flags.h" + +namespace ledger { + +class commodity_t; +struct annotation_t; +struct keep_details_t; + +DECLARE_EXCEPTION(amount_error, std::runtime_error); + +enum parse_flags_enum_t { + PARSE_DEFAULT = 0x00, + PARSE_PARTIAL = 0x01, + PARSE_SINGLE = 0x02, + PARSE_NO_MIGRATE = 0x04, + PARSE_NO_REDUCE = 0x08, + PARSE_NO_ASSIGN = 0x10, + PARSE_NO_ANNOT = 0x20, + PARSE_OP_CONTEXT = 0x40, + PARSE_SOFT_FAIL = 0x80 +}; + +typedef basic_flags_t parse_flags_t; + +/** + * @brief Encapsulate infinite-precision commoditized amounts + * + * Used to represent commoditized infinite-precision numbers, and + * uncommoditized, plain numbers. In the commoditized case, commodities + * keep track of how they are used, and are always displayed back to the + * user after the same fashion. For uncommoditized numbers, no display + * truncation is ever done. In both cases, internal precision is always + * kept to an excessive degree. + */ +class amount_t + : public ordered_field_operators > > > +{ +public: + /** Ready the amount subsystem for use. + @note Normally called by session_t::initialize(). */ + static void initialize(); + /** Shutdown the amount subsystem and free all resources. + @note Normally called by session_t::shutdown(). */ + static void shutdown(); + + static bool is_initialized; + + /** The amount's decimal precision. */ + typedef uint_least16_t precision_t; + + /** Number of places of precision by which values are extended to + avoid losing precision during division and multiplication. */ + static const std::size_t extend_by_digits = 6U; + + /** If amounts should be streamed using to_fullstring() rather than + to_string(), so that complete precision is always displayed no matter + what the precision of an individual commodity may be. */ + static bool stream_fullstrings; + +protected: + void _copy(const amount_t& amt); + void _dup(); + void _clear(); + void _release(); + + struct bigint_t; + + bigint_t * quantity; + commodity_t * commodity_; + +public: + /** @name Constructors + @{ */ + + /** Creates a value for which is_null() is true, and which has no + value or commodity. If used in a value expression it evaluates to + zero, and its commodity equals \c commodity_t::null_commodity. */ + amount_t() : quantity(NULL), commodity_(NULL) { + TRACE_CTOR(amount_t, ""); + } + + /** Convert a double to an amount. As much precision as possible is + decoded from the binary floating point number. */ + amount_t(const double val); + + /** Convert an unsigned long to an amount. It's precision is zero. */ + amount_t(const unsigned long val); + + /** Convert a long to an amount. It's precision is zero, and the sign + is preserved. */ + amount_t(const long val); + + /** Parse a string as an (optionally commoditized) amount. If no + commodity is present, the resulting commodity is \c + commodity_t::null_commodity. The number may be of infinite + precision. */ + explicit amount_t(const string& val) : quantity(NULL) { + parse(val); + TRACE_CTOR(amount_t, "const string&"); + } + /** Parse a pointer to a C string as an (optionally commoditized) + amount. If no commodity is present, the resulting commodity is \c + commodity_t::null_commodity. The number may be of infinite + precision. */ + explicit amount_t(const char * val) : quantity(NULL) { + assert(val); + parse(val); + TRACE_CTOR(amount_t, "const char *"); + } + + /*@}*/ + + /** Create an amount whose display precision is never truncated, even + if the amount uses a commodity (which normally causes "round on + streaming" to occur). This function is mostly used by debugging + code and unit tests. This is the proper way to specify \c + $100.005, where display of the extra digit precision is required. + If a regular constructor were used, the amount would stream as \c + $100.01, even though its internal value equals \c $100.005. */ + static amount_t exact(const string& value); + + /** Release the reference count held for the underlying \c + amount_t::bigint_t object. */ + ~amount_t() { + TRACE_DTOR(amount_t); + if (quantity) + _release(); + } + + /** @name Assignment and copy + @{*/ + + /** Copy an amount object. Copies are very efficient, using a + copy-on-write model. Until the copy is changed, it refers to the + same memory used by the original via reference counting. The \c + amount_t::bigint_t class in amount.cc maintains the reference. */ + amount_t(const amount_t& amt) : quantity(NULL) { + if (amt.quantity) + _copy(amt); + else + commodity_ = NULL; + TRACE_CTOR(amount_t, "copy"); + } + /** Copy an amount object, applying the given commodity annotation + details afterward. This is equivalent to doing a normal copy + (@see amount_t(const amount_t&)) and then calling + amount_t::annotate(). */ + amount_t(const amount_t& amt, const annotation_t& details) : quantity(NULL) { + assert(amt.quantity); + _copy(amt); + annotate(details); + TRACE_CTOR(amount_t, "const amount_t&, const annotation_t&"); + } + /** Assign an amount object. This is like copying if the amount was + null beforehand, otherwise the previous value's reference is must + be freed. */ + amount_t& operator=(const amount_t& amt); + + amount_t& operator=(const double val) { + return *this = amount_t(val); + } + amount_t& operator=(const unsigned long val) { + return *this = amount_t(val); + } + amount_t& operator=(const long val) { + return *this = amount_t(val); + } + + /* Assign a string to an amount. This causes the contents of the + string to be parsed, look for a commoditized or uncommoditized + amount specifier. */ + amount_t& operator=(const string& str) { + return *this = amount_t(str); + } + amount_t& operator=(const char * str) { + assert(str); + return *this = amount_t(str); + } + + /*@}*/ + + /** @name Comparison + @{ */ + + /** Compare two amounts, returning a number less than zero if \p amt + is greater, exactly zero if they are equal, and greater than zero + if \p amt is less. This method is used to implement all of the + other comparison methods.*/ + int compare(const amount_t& amt) const; + + /** Test two amounts for equality. First the commodity pointers are + quickly tested, then the multi-precision values themselves must be + compared. */ + bool operator==(const amount_t& amt) const; + + template + bool operator==(const T& val) const { + return compare(val) == 0; + } + template + bool operator<(const T& amt) const { + return compare(amt) < 0; + } + template + bool operator>(const T& amt) const { + return compare(amt) > 0; + } + + /*@}*/ + + /** @name Binary arithmetic + */ + /*@{*/ + + amount_t& operator+=(const amount_t& amt); + amount_t& operator-=(const amount_t& amt); + amount_t& operator*=(const amount_t& amt) { + return multiply(amt); + } + amount_t& multiply(const amount_t& amt, bool ignore_commodity = false); + + /** Divide two amounts while extending the precision to preserve the + accuracy of the result. For example, if \c 10 is divided by \c 3, + the result ends up having a precision of \link + amount_t::extend_by_digits \endlink place to avoid losing internal + resolution. */ + amount_t& operator/=(const amount_t& amt); + + /*@}*/ + + /** @name Unary arithmetic + @{ */ + + /** Return an amount's internal precision. To find the precision it + should be displayed at -- assuming it was not created using + amount_t::exact() -- use the following expression instead: + @code + amount.commodity().precision() + @endcode */ + precision_t precision() const; + bool keep_precision() const; + void set_keep_precision(const bool keep = true) const; + precision_t display_precision() const; + + /** Returns the negated value of an amount. + @see operator-() + */ + amount_t negated() const { + amount_t temp(*this); + temp.in_place_negate(); + return temp; + } + void in_place_negate(); + + amount_t operator-() const { + return negated(); + } + + /** Returns the absolute value of an amount. Equivalent to: + @code + (x < * 0) ? - x : x + @endcode + */ + amount_t abs() const { + if (sign() < 0) + return negated(); + return *this; + } + + amount_t inverted() const { + amount_t temp(*this); + temp.in_place_invert(); + return temp; + } + void in_place_invert(); + + /** Yields an amount whose display precision when output is truncated + to the display precision of its commodity. This is normally the + default state of an amount, but if one has become unrounded, this + sets the "keep precision" state back to false. + @see set_keep_precision */ + amount_t rounded() const { + amount_t temp(*this); + temp.in_place_round(); + return temp; + } + void in_place_round(); + + /** Yields an amount which has lost all of its extra precision, beyond what + the display precision of the commodity would have printed. */ + amount_t truncated() const { + amount_t temp(*this); + temp.in_place_truncate(); + return temp; + } + 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. */ + amount_t floored() const { + amount_t temp(*this); + temp.in_place_floor(); + return temp; + } + void in_place_floor(); + + /** Yields an amount which has lost all of its extra precision, beyond what + the display precision of the commodity would have printed. */ + amount_t ceilinged() const { + amount_t temp(*this); + temp.in_place_ceiling(); + return temp; + } + void in_place_ceiling(); + + /** Yields an amount whose display precision is never truncated, even + though its commodity normally displays only rounded values. */ + amount_t unrounded() const { + amount_t temp(*this); + temp.in_place_unround(); + return temp; + } + void in_place_unround(); + + /** reduces a value to its most basic commodity form, for amounts that + utilize "scaling commodities". For example, an amount of \c 1h + after reduction will be \c 3600s. + */ + amount_t reduced() const { + amount_t temp(*this); + temp.in_place_reduce(); + return temp; + } + void in_place_reduce(); + + /** unreduce(), if used with a "scaling commodity", yields the most + compact form greater than one. That is, \c 3599s will unreduce to + \c 59.98m, while \c 3601 unreduces to \c 1h. + */ + amount_t unreduced() const { + amount_t temp(*this); + temp.in_place_unreduce(); + return temp; + } + void in_place_unreduce(); + + /** Returns the historical value for an amount -- the default moment + returns the most recently known price -- based on the price history + for the given commodity (or determined automatically, if none is + provided). For example, if the amount were 10 AAPL, and + on Apr 10, 2000 each share of \c AAPL was worth \c $10, then + calling value() for that moment in time would yield the amount \c + $100.00. + */ + optional + value(const datetime_t& moment = datetime_t(), + const commodity_t * in_terms_of = NULL) const; + + optional price() const; + + /*@}*/ + + /** @name Truth tests + */ + /*@{*/ + + /** Truth tests. An amount may be truth test in several ways: + + sign() returns an integer less than, greater than, or equal to + zero depending on whether the amount is negative, zero, or + greater than zero. Note that this function tests the actual + value of the amount -- using its internal precision -- and not + the display value. To test its display value, use: + `round().sign()'. + + is_nonzero(), or operator bool, returns true if an amount's + display value is not zero. + + is_zero() returns true if an amount's display value is zero. + Thus, $0.0001 is considered zero if the current display precision + for dollars is two decimal places. + + is_realzero() returns true if an amount's actual value is zero. + Thus, $0.0001 is never considered realzero. + + is_null() returns true if an amount has no value and no + commodity. This only occurs if an uninitialized amount has never + been assigned a value. + */ + int sign() const; + + operator bool() const { + return is_nonzero(); + } + bool is_nonzero() const { + return ! is_zero(); + } + + bool is_zero() const; + bool is_realzero() const { + return sign() == 0; + } + + bool is_null() const { + if (! quantity) { + assert(! commodity_); + return true; + } + return false; + } + + /*@}*/ + + /** @name Conversion + */ + /*@{*/ + + /** Conversion methods. An amount may be converted to the same types + it can be constructed from -- with the exception of unsigned + long. Implicit conversions are not allowed in C++ (though they + are in Python), rather the following conversion methods must be + called explicitly: + + to_double([bool]) returns an amount as a double. If the optional + boolean argument is true (the default), an exception is thrown if + the conversion would lose information. + + to_long([bool]) returns an amount as a long integer. If the + optional boolean argument is true (the default), an exception is + thrown if the conversion would lose information. + + fits_in_long() returns true if to_long() would not lose + precision. + + to_string() returns an amount'ss "display value" as a string -- + after rounding the value according to the commodity's default + precision. It is equivalent to: `round().to_fullstring()'. + + to_fullstring() returns an amount's "internal value" as a string, + without any rounding. + + quantity_string() returns an amount's "display value", but + without any commodity. Note that this is different from + `number().to_string()', because in that case the commodity has + been stripped and the full, internal precision of the amount + would be displayed. + */ + double to_double() const; + long to_long() const; + bool fits_in_long() const; + + operator string() const { + return to_string(); + } + string to_string() const; + string to_fullstring() const; + string quantity_string() const; + + /*@}*/ + + /** @name Commodity methods + */ + /*@{*/ + + /** The following methods relate to an + amount's commodity: + + commodity() returns an amount's commodity. If the amount has no + commodity, the value returned is the `null_commodity'. + + has_commodity() returns true if the amount has a commodity. + + set_commodity(commodity_t) sets an amount's commodity to the + given value. Note that this merely sets the current amount to + that commodity, it does not "observe" the amount for possible + changes in the maximum display precision of the commodity, the + way that `parse' does. + + clear_commodity() sets an amount's commodity to null, such that + has_commodity() afterwards returns false. + + number() returns a commodity-less version of an amount. This is + useful for accessing just the numeric portion of an amount. + */ + commodity_t * commodity_ptr() const; + commodity_t& commodity() const { + return *commodity_ptr(); + } + + bool has_commodity() const; + void set_commodity(commodity_t& comm) { + if (! quantity) + *this = 0L; + commodity_ = &comm; + } + amount_t with_commodity(const commodity_t& comm) const { + if (commodity_ == &comm) { + return *this; + } else { + amount_t tmp(*this); + tmp.set_commodity(const_cast(comm)); + return tmp; + } + } + void clear_commodity() { + commodity_ = NULL; + } + + amount_t number() const { + if (! has_commodity()) + return *this; + + amount_t temp(*this); + temp.clear_commodity(); + return temp; + } + + /*@}*/ + + /** @name Commodity annotations + */ + /*@{*/ + + /** An amount's commodity may be annotated with special details, such as the + price it was purchased for, when it was acquired, or an arbitrary note, + identifying perhaps the lot number of an item. + + annotate_commodity(amount_t price, [datetime_t date, string tag]) + sets the annotations for the current amount's commodity. Only + the price argument is required, although it can be passed as + `none' if no price is desired. + + commodity_annotated() returns true if an amount's commodity has + any annotation details associated with it. + + annotation_details() returns all of the details of an annotated + commodity's annotations. The structure returns will evaluate as + boolean false if there are no details. + + strip_annotations() returns an amount whose commodity's annotations have + been stripped. + */ + void annotate(const annotation_t& details); + bool has_annotation() const; + + annotation_t& annotation(); + const annotation_t& annotation() const { + return const_cast(*this).annotation(); + } + + /** If the lot price is considered whenever working with commoditized + values. + + Let's say a user adds two values of the following form: + @code + 10 AAPL + 10 AAPL {$20} + @endcode + + This expression adds ten shares of Apple stock with another ten + shares that were purchased for \c $20 a share. If \c keep_price + is false, the result of this expression is an amount equal to + 20 AAPL. If \c keep_price is \c true the expression + yields an exception for adding amounts with different commodities. + In that case, a \link balance_t \endlink object must be used to + store the combined sum. */ + amount_t strip_annotations(const keep_details_t& what_to_keep) const; + + /*@}*/ + + /** @name Parsing + */ + /*@{*/ + + /** The `flags' argument of both parsing may be one or more of the + following: + + PARSE_NO_MIGRATE means to not pay attention to the way an + amount is used. Ordinarily, if an amount were $100.001, for + example, it would cause the default display precision for $ to be + "widened" to three decimal places. If PARSE_NO_MIGRATE is + used, the commodity's default display precision is not changed. + + PARSE_NO_REDUCE means not to call in_place_reduce() on the + resulting amount after it is parsed. + + These parsing methods observe the amounts they parse (unless + PARSE_NO_MIGRATE is true), and set the display details of + the corresponding commodity accordingly. This way, amounts do + not require commodities to be pre-defined in any way, but merely + displays them back to the user in the same fashion as it saw them + used. + + There is also a static convenience method called + `parse_conversion' which can be used to define a relationship + between scaling commodity values. For example, Ledger uses it to + define the relationships among various time values: + + @code + amount_t::parse_conversion("1.0m", "60s"); // a minute is 60 seconds + amount_t::parse_conversion("1.0h", "60m"); // an hour is 60 minutes + @endcode + + The method parse() is used to parse an amount from an input stream + or a string. A global operator>>() is also defined which simply + calls parse on the input stream. The parse() method has two forms: + + parse(istream, flags_t) parses an amount from the given input + stream. + + parse(string, flags_t) parses an amount from the given string. + + parse(string, flags_t) also parses an amount from a string. + */ + bool parse(std::istream& in, + const parse_flags_t& flags = PARSE_DEFAULT); + bool parse(const string& str, + const parse_flags_t& flags = PARSE_DEFAULT) { + std::istringstream stream(str); + bool result = parse(stream, flags); + return result; + } + + static void parse_conversion(const string& larger_str, + const string& smaller_str); + + /*@}*/ + + /** @name Printing + */ + /*@{*/ + + /** An amount may be output to a stream using the `print' method. There is + also a global operator<< defined which simply calls print for an amount + on the given stream. There is one form of the print method, which takes + one required argument and two arguments with default values: + + print(ostream, bool omit_commodity = false, bool full_precision = false) + prints an amounts to the given output stream, using its commodity's + default display characteristics. If `omit_commodity' is true, the + commodity will not be displayed, only the amount (although the + commodity's display precision is still used). If `full_precision' is + true, the full internal precision of the amount is displayed, regardless + of its commodity's display precision. + */ +#define AMOUNT_PRINT_NO_FLAGS 0x00 +#define AMOUNT_PRINT_RIGHT_JUSTIFY 0x01 +#define AMOUNT_PRINT_COLORIZE 0x02 +#define AMOUNT_PRINT_NO_COMPUTED_ANNOTATIONS 0x04 +#define AMOUNT_PRINT_ELIDE_COMMODITY_QUOTES 0x08 + + void print(std::ostream& out, + const uint_least8_t flags = AMOUNT_PRINT_NO_FLAGS) const; + + /*@}*/ + + /** @name Debugging + */ + /*@{*/ + + /** There are two methods defined to help with debugging: + + dump(ostream) dumps an amount to an output stream. There is + little different from print(), it simply surrounds the display + value with a marker, for example "AMOUNT($1.00)". This code is + used by other dumping code elsewhere in Ledger. + + valid() returns true if an amount is valid. This ensures that if + an amount has a commodity, it has a valid value pointer, for + example, even if that pointer simply points to a zero value. + */ + void dump(std::ostream& out) const { + out << "AMOUNT("; + print(out); + out << ")"; + } + + bool valid() const; + +#if HAVE_BOOST_SERIALIZATION +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template + void serialize(Archive& ar, const unsigned int /* version */); +#endif // HAVE_BOOST_SERIALIZATION + + /*@}*/ +}; + +inline amount_t amount_t::exact(const string& value) { + amount_t temp; + temp.parse(value, PARSE_NO_MIGRATE); + return temp; +} + +inline string amount_t::to_string() const { + std::ostringstream bufstream; + print(bufstream); + return bufstream.str(); +} + +inline string amount_t::to_fullstring() const { + std::ostringstream bufstream; + unrounded().print(bufstream); + return bufstream.str(); +} + +inline string amount_t::quantity_string() const { + std::ostringstream bufstream; + number().print(bufstream); + return bufstream.str(); +} + +inline std::ostream& operator<<(std::ostream& out, const amount_t& amt) { + if (amount_t::stream_fullstrings) + amt.unrounded().print(out); + else + amt.print(out); + return out; +} +inline std::istream& operator>>(std::istream& in, amount_t& amt) { + amt.parse(in); + return in; +} + +void put_amount(property_tree::ptree& pt, const amount_t& amt, + bool wrap = true, bool commodity_details = false); + +} // namespace ledger + +#endif // _AMOUNT_H diff --git a/test/regress/25A099C9.test b/test/regress/25A099C9.test index cfc0eabd..36776520 100644 --- a/test/regress/25A099C9.test +++ b/test/regress/25A099C9.test @@ -1,61 +1,61 @@ -test -f test/garbage-input.dat reg -> 29 +test -f test/regress/25A099C9.dat reg -> 29 __ERROR__ -While parsing file "$sourcepath/test/garbage-input.dat", line 1: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 1: Error: Directive '/*' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 32: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 32: Error: Directive '/**' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 36: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 36: Error: Directive '/**' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 66: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 66: Error: No quantity specified for amount -While parsing file "$sourcepath/test/garbage-input.dat", line 69: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 69: Error: Unexpected whitespace at beginning of line -While parsing file "$sourcepath/test/garbage-input.dat", line 78: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 78: Error: Directive '};' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 82: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 82: Error: Directive '/**' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 93: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 93: Error: Unexpected whitespace at beginning of line -While parsing file "$sourcepath/test/garbage-input.dat", line 97: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 97: Error: Directive '{' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 98: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 98: Error: Directive 'public:' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 120: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 120: Error: Directive 'protected:' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 131: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 131: Error: Directive 'public:' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 711: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 711: Error: Unexpected whitespace at beginning of line -While parsing file "$sourcepath/test/garbage-input.dat", line 740: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 740: Error: Directive 'private:' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 749: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 749: Error: Unexpected whitespace at beginning of line -While parsing file "$sourcepath/test/garbage-input.dat", line 750: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 750: Error: Directive '};' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 752: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 752: Error: Invalid date/time: line amount_t amoun -While parsing file "$sourcepath/test/garbage-input.dat", line 756: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 756: Error: Directive '}' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 758: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 758: Error: Invalid date/time: line string amount_ -While parsing file "$sourcepath/test/garbage-input.dat", line 762: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 762: Error: Directive '}' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 764: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 764: Error: Invalid date/time: line string amount_ -While parsing file "$sourcepath/test/garbage-input.dat", line 768: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 768: Error: Directive '}' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 770: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 770: Error: Invalid date/time: line string amount_ -While parsing file "$sourcepath/test/garbage-input.dat", line 774: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 774: Error: Directive '}' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 776: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 776: Error: Invalid date/time: line std::ostream& -While parsing file "$sourcepath/test/garbage-input.dat", line 782: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 782: Error: Directive '}' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 783: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 783: Error: Invalid date/time: line std::istream& -While parsing file "$sourcepath/test/garbage-input.dat", line 786: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 786: Error: Directive '}' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 789: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 789: Error: Unexpected whitespace at beginning of line end test diff --git a/tools/update_copyright_year.sh b/tools/update_copyright_year.sh index db9541d4..ab08a947 100755 --- a/tools/update_copyright_year.sh +++ b/tools/update_copyright_year.sh @@ -5,7 +5,7 @@ # This script will replace the last year of Copyright statements with the first # argument of this script (defaulting to the current year). -# Copyright (c) 2016 Alexis Hildebrandt +# Copyright (c) 2016, 2019 Alexis Hildebrandt # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -31,6 +31,6 @@ YEAR=${1:-$(date +%Y)} GREP=${2:-egrep} ${GREP} -Rl 'Copyright.*Wiegley' $(git ls-files | cut -d / -f1 | uniq) \ - | ${GREP} -v "(test/garbage-input.dat|$(basename $0))" \ + | ${GREP} -v "(test/regress/25A099C9.dat|$(basename $0))" \ | xargs sed -i '' -e "s/\(Copyright.*\)-20[0-9]\{2\}/\1-${YEAR}/" -- cgit v1.2.3 From 52439c2d762f34f4823f0c1178131dc8ad8b03c4 Mon Sep 17 00:00:00 2001 From: Tim Landscheidt Date: Tue, 22 Jan 2019 15:23:44 +0000 Subject: Do not set dependencies for target check The set_target_properties() commands themselves do not cause the tests to run if the target check is made, and as the target check executes ctest, all tests will be run anyway. --- test/CMakeLists.txt | 4 ---- test/unit/CMakeLists.txt | 3 --- 2 files changed, 7 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d0b62f6e..41eecb36 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -27,8 +27,6 @@ macro(add_ledger_harness_tests _class) ${TestFile} ${TEST_PYTHON_FLAGS}) set_tests_properties(${_class}Test_${TestFile_Name} PROPERTIES ENVIRONMENT "TZ=${Ledger_TEST_TIMEZONE}") - set_target_properties(check - PROPERTIES DEPENDS ${_class}Test_${TestFile_Name}) endif() endforeach() endif() @@ -48,8 +46,6 @@ if (PYTHONINTERP_FOUND) --ledger $ --file ${TestFile}) set_tests_properties(${_class}Test_${TestFile_Name} PROPERTIES ENVIRONMENT "TZ=${Ledger_TEST_TIMEZONE}") - set_target_properties(check - PROPERTIES DEPENDS ${_class}Test_${TestFile_Name}) endforeach() # CheckManpage and CheckTexinfo are disabled, since they do not work diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 9cacb4e7..3611b00a 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -20,7 +20,4 @@ if (BUILD_LIBRARY) target_link_libraries(MathTests ${PYTHON_LIBRARIES}) endif() add_ledger_test(MathTests) - - set_target_properties(check PROPERTIES DEPENDS LedgerUtilTests) - set_target_properties(check PROPERTIES DEPENDS LedgerMathTests) endif() -- cgit v1.2.3 From f3bad93db256db07b6cb831d4d24f47543f57e4a Mon Sep 17 00:00:00 2001 From: Michael Budde Date: Tue, 22 Jan 2019 19:22:03 +0100 Subject: Ignore null deferred postings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All-null transactions (i.e. a transaction where all postings have a null amount) are discarded during parsing and the `xact` object is free'd. But if the transaction contains a deferred posting this results in a use-after-free vulnerability because a reference to the deferred posting is stored in the account object which is later read when deferred postings are applied after parsing is finished. Ignore null deferred postings to prevent this – they should not have any effect any way. Thanks to Cory Duplantis for reporting this issue and providing an initial analysis. Ref TALOS-2017-0304, CVE-2017-2808 Fixes #1723 --- doc/NEWS | 3 +++ src/xact.cc | 8 +++++--- test/regress/1723.test | 5 +++++ 3 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 test/regress/1723.test diff --git a/doc/NEWS b/doc/NEWS index 80617b08..5152ad7c 100644 --- a/doc/NEWS +++ b/doc/NEWS @@ -45,6 +45,9 @@ - Fix parsing issue of effective dates (bug #1722, TALOS-2017-0303, CVE-2017-2807) +- Fix use-after-free issue with deferred postings (bug #1723, TALOS-2017-0304, + CVE-2017-2808) + - Python: Removed double quotes from Unicode values. - Python: Ensure that parse errors produce useful RuntimeErrors diff --git a/src/xact.cc b/src/xact.cc index 5df9ebc5..10a7106a 100644 --- a/src/xact.cc +++ b/src/xact.cc @@ -395,10 +395,12 @@ bool xact_base_t::finalize() some_null = true; } - if (post->has_flags(POST_DEFERRED)) - post->account->add_deferred_post(id(), post); - else + if (post->has_flags(POST_DEFERRED)) { + if (!post->amount.is_null()) + post->account->add_deferred_post(id(), post); + } else { post->account->add_post(post); + } post->xdata().add_flags(POST_EXT_VISITED); post->account->xdata().add_flags(ACCOUNT_EXT_VISITED); diff --git a/test/regress/1723.test b/test/regress/1723.test new file mode 100644 index 00000000..62a50386 --- /dev/null +++ b/test/regress/1723.test @@ -0,0 +1,5 @@ +2017/3/17 deferred posting + + +test reg +end test -- cgit v1.2.3 From 3d860ffc5b9be8887abd12c6d3ebc34fc5dcac05 Mon Sep 17 00:00:00 2001 From: Gonzalo Rizzo Date: Fri, 25 Jan 2019 09:54:16 -0300 Subject: Quick nitpick styling change/enhancement --- acprep | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/acprep b/acprep index a6f34cad..c768bc52 100755 --- a/acprep +++ b/acprep @@ -53,16 +53,16 @@ def which(program): class BoostInfo(object): def dependencies(self, system): - if system == 'darwin-homebrew': + if system in ['darwin-homebrew']: return [ 'boost' ] - if system == 'darwin-macports': + if system in ['darwin-macports']: return [ 'boost-jam', 'boost', '+python27+universal' ] - if system == 'centos': + if system in ['centos']: return [ 'boost-devel' ] - elif system == 'ubuntu-bionic' or system == 'ubuntu-xenial' or system == 'ubuntu-trusty': + elif system in ['ubuntu-bionic', 'ubuntu-xenial', 'ubuntu-trusty']: return [ 'libboost-dev', 'libboost-date-time-dev', 'libboost-filesystem-dev', @@ -72,7 +72,7 @@ class BoostInfo(object): 'libboost-system-dev', 'libboost-test-dev' ] - elif system == 'ubuntu-saucy' or system == 'ubuntu-precise': + elif system in [ 'ubuntu-saucy', 'ubuntu-precise']: return [ 'autopoint', 'libboost-dev', 'libboost-test-dev', @@ -82,7 +82,7 @@ class BoostInfo(object): 'libboost-iostreams-dev', 'libboost-python-dev' ] - elif system == 'ubuntu-lucid': + elif system in ['ubuntu-lucid']: return [ 'bjam', 'autopoint', 'libboost-dev', 'libboost-regex-dev', -- cgit v1.2.3 From d967c3485d09687586527ff206cc2401f099a3d4 Mon Sep 17 00:00:00 2001 From: Tim Landscheidt Date: Fri, 25 Jan 2019 12:04:08 -0300 Subject: Add tzdata to build dependencies for Ubuntu The test suite uses the symbolic time zone name "America/Chicago". To resolve that, the tzdata package needs to be installed. This fixes #1739. --- INSTALL.md | 2 +- README.md | 2 +- acprep | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index a366dbfd..1d5bedba 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -57,7 +57,7 @@ A: You're probably missing some dependency libraries. If you tried libboost-date-time-dev libboost-filesystem-dev \ libboost-graph-dev libboost-iostreams-dev \ libboost-python-dev libboost-regex-dev libboost-test-dev \ - doxygen libedit-dev libmpc-dev + doxygen libedit-dev libmpc-dev tzdata ---------------------------------------------------------------------- diff --git a/README.md b/README.md index 1b0c3ba9..de083f7e 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ following packages (current as of Ubuntu 18.04): libboost-system-dev libboost-dev python-dev gettext git \ libboost-date-time-dev libboost-filesystem-dev \ libboost-iostreams-dev libboost-python-dev libboost-regex-dev \ - libboost-test-dev libedit-dev libgmp3-dev libmpfr-dev texinfo + libboost-test-dev libedit-dev libgmp3-dev libmpfr-dev texinfo tzdata ### Debian diff --git a/acprep b/acprep index c768bc52..c5b4c203 100755 --- a/acprep +++ b/acprep @@ -62,7 +62,8 @@ class BoostInfo(object): if system in ['centos']: return [ 'boost-devel' ] - elif system in ['ubuntu-bionic', 'ubuntu-xenial', 'ubuntu-trusty']: + elif system in ['ubuntu-bionic', 'ubuntu-xenial', + 'ubuntu-trusty', 'ubuntu-cosmic']: return [ 'libboost-dev', 'libboost-date-time-dev', 'libboost-filesystem-dev', @@ -70,7 +71,8 @@ class BoostInfo(object): 'libboost-python-dev', 'libboost-regex-dev', 'libboost-system-dev', - 'libboost-test-dev' ] + 'libboost-test-dev', + 'tzdata' ] elif system in [ 'ubuntu-saucy', 'ubuntu-precise']: return [ 'autopoint', -- cgit v1.2.3 From ccd330746a77cf335b2816a3048a8e25e61f8ed4 Mon Sep 17 00:00:00 2001 From: Tim Landscheidt Date: Tue, 15 Jan 2019 14:00:00 +0000 Subject: Remove workaround for isspace() on FreeBSD 4 and earlier FreeBSD 4 was declared end-of-life in 2006 (https://lists.freebsd.org/pipermail/freebsd-security/2006-October/004111.html). Currently, only FreeBSD 11 and 12 are supported (https://www.freebsd.org/security/security.html#sup). --- src/system.hh.in | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/system.hh.in b/src/system.hh.in index 1f4a3958..799c7102 100644 --- a/src/system.hh.in +++ b/src/system.hh.in @@ -135,11 +135,6 @@ typedef std::ostream::pos_type ostream_pos_type; #include #include -#if defined __FreeBSD__ && __FreeBSD__ <= 4 -// FreeBSD has a broken isspace macro, so don't use it -#undef isspace(c) -#endif - #if defined(_WIN32) || defined(__CYGWIN__) #include #else -- cgit v1.2.3 From 0e5f0792ff7cfcc2012a53fa5372f4d6d04b5948 Mon Sep 17 00:00:00 2001 From: Tim Landscheidt Date: Tue, 15 Jan 2019 14:21:19 +0000 Subject: Drop support for gcc 2 and earlier --- src/annotate.cc | 4 ++-- src/commodity.cc | 4 ++-- src/context.h | 28 ++++++++++++++-------------- src/error.cc | 10 +++++----- src/error.h | 10 +++++----- src/expr.cc | 14 +++++++------- src/format.cc | 4 ++-- src/item.h | 14 +++++++------- src/op.cc | 4 ++-- src/op.h | 22 +++++++++++----------- src/quotes.cc | 7 +------ src/system.hh.in | 27 --------------------------- src/textual.cc | 10 +++++----- src/token.cc | 4 ++-- 14 files changed, 65 insertions(+), 97 deletions(-) diff --git a/src/annotate.cc b/src/annotate.cc index ab81d412..c5ccdf07 100644 --- a/src/annotate.cc +++ b/src/annotate.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -81,7 +81,7 @@ bool annotation_t::operator<(const annotation_t& rhs) const void annotation_t::parse(std::istream& in) { do { - istream_pos_type pos = in.tellg(); + std::istream::pos_type pos = in.tellg(); if (static_cast(pos) < 0) return; diff --git a/src/commodity.cc b/src/commodity.cc index a8520ca1..d6d5ca98 100644 --- a/src/commodity.cc +++ b/src/commodity.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -294,7 +294,7 @@ bool commodity_t::symbol_needs_quotes(const string& symbol) void commodity_t::parse_symbol(std::istream& in, string& symbol) { - istream_pos_type pos = in.tellg(); + std::istream::pos_type pos = in.tellg(); char buf[256]; char c = peek_next_nonws(in); diff --git a/src/context.h b/src/context.h index 0af59930..aba3a60e 100644 --- a/src/context.h +++ b/src/context.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -58,19 +58,19 @@ public: shared_ptr stream; - path pathname; - path current_directory; - journal_t * journal; - account_t * master; - scope_t * scope; - char linebuf[MAX_LINE + 1]; - istream_pos_type line_beg_pos; - istream_pos_type curr_pos; - std::size_t linenum; - std::size_t errors; - std::size_t count; - std::size_t sequence; - std::string last; + path pathname; + path current_directory; + journal_t * journal; + account_t * master; + scope_t * scope; + char linebuf[MAX_LINE + 1]; + std::istream::pos_type line_beg_pos; + std::istream::pos_type curr_pos; + std::size_t linenum; + std::size_t errors; + std::size_t count; + std::size_t sequence; + std::string last; explicit parse_context_t(const path& cwd) : current_directory(cwd), master(NULL), scope(NULL), diff --git a/src/error.cc b/src/error.cc index 7e49645b..1ab92840 100644 --- a/src/error.cc +++ b/src/error.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -78,10 +78,10 @@ string line_context(const string& line, return buf.str(); } -string source_context(const path& file, - const istream_pos_type pos, - const istream_pos_type end_pos, - const string& prefix) +string source_context(const path& file, + const std::istream::pos_type pos, + const std::istream::pos_type end_pos, + const string& prefix) { const std::streamoff len = end_pos - pos; if (! len || file.empty()) diff --git a/src/error.h b/src/error.h index bc9953cd..3bdcde98 100644 --- a/src/error.h +++ b/src/error.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -81,10 +81,10 @@ string line_context(const string& line, const string::size_type pos = 0, const string::size_type end_pos = 0); -string source_context(const path& file, - const istream_pos_type pos, - const istream_pos_type end_pos, - const string& prefix = ""); +string source_context(const path& file, + const std::istream::pos_type pos, + const std::istream::pos_type end_pos, + const string& prefix = ""); #define DECLARE_EXCEPTION(name, kind) \ class name : public kind { \ diff --git a/src/expr.cc b/src/expr.cc index 85818e4b..c8945d3d 100644 --- a/src/expr.cc +++ b/src/expr.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -94,9 +94,9 @@ void expr_t::parse(std::istream& in, const parse_flags_t& flags, const optional& original_string) { parser_t parser; - istream_pos_type start_pos = in.tellg(); + std::istream::pos_type start_pos = in.tellg(); ptr = parser.parse(in, flags, original_string); - istream_pos_type end_pos = in.tellg(); + std::istream::pos_type end_pos = in.tellg(); if (original_string) { set_text(*original_string); @@ -290,10 +290,10 @@ value_t source_command(call_scope_t& args) in = &std::cin; } - symbol_scope_t file_locals(args); - std::size_t linenum = 0; - char buf[4096]; - istream_pos_type pos; + symbol_scope_t file_locals(args); + std::size_t linenum = 0; + char buf[4096]; + std::istream::pos_type pos; while (in->good() && ! in->eof()) { pos = in->tellg(); diff --git a/src/format.cc b/src/format.cc index bb578141..5b9baa21 100644 --- a/src/format.cc +++ b/src/format.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -103,7 +103,7 @@ namespace { p += std::strlen(p); } else { assert(str.good()); - istream_pos_type pos = str.tellg(); + std::istream::pos_type pos = str.tellg(); expr.set_text(string(p, p + long(pos))); p += long(pos) - 1; diff --git a/src/item.h b/src/item.h index a28bf59e..51539eff 100644 --- a/src/item.h +++ b/src/item.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -48,12 +48,12 @@ namespace ledger { struct position_t { - path pathname; - istream_pos_type beg_pos; - std::size_t beg_line; - istream_pos_type end_pos; - std::size_t end_line; - std::size_t sequence; + path pathname; + std::istream::pos_type beg_pos; + std::size_t beg_line; + std::istream::pos_type end_pos; + std::size_t end_line; + std::size_t sequence; position_t() : beg_pos(0), beg_line(0), end_pos(0), end_line(0), sequence(0) { diff --git a/src/op.cc b/src/op.cc index 00b84459..5801b1dd 100644 --- a/src/op.cc +++ b/src/op.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -963,7 +963,7 @@ void expr_t::op_t::dump(std::ostream& out, const int depth) const string op_context(const expr_t::ptr_op_t op, const expr_t::ptr_op_t locus) { - ostream_pos_type start_pos, end_pos; + std::ostream::pos_type start_pos, end_pos; expr_t::op_t::context_t context(op, locus, &start_pos, &end_pos); std::ostringstream buf; buf << " "; diff --git a/src/op.h b/src/op.h index bbbb5d81..aab362c7 100644 --- a/src/op.h +++ b/src/op.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -285,19 +285,19 @@ public: struct context_t { - ptr_op_t expr_op; - ptr_op_t op_to_find; - ostream_pos_type * start_pos; - ostream_pos_type * end_pos; - bool relaxed; + ptr_op_t expr_op; + ptr_op_t op_to_find; + std::ostream::pos_type * start_pos; + std::ostream::pos_type * end_pos; + bool relaxed; context_t() : start_pos(NULL), end_pos(NULL), relaxed(false) {} - context_t(const ptr_op_t& _expr_op, - const ptr_op_t& _op_to_find, - ostream_pos_type * const _start_pos = NULL, - ostream_pos_type * const _end_pos = NULL, - const bool _relaxed = true) + context_t(const ptr_op_t& _expr_op, + const ptr_op_t& _op_to_find, + std::ostream::pos_type * const _start_pos = NULL, + std::ostream::pos_type * const _end_pos = NULL, + const bool _relaxed = true) : expr_op(_expr_op), op_to_find(_op_to_find), start_pos(_start_pos), end_pos(_end_pos), relaxed(_relaxed) {} diff --git a/src/quotes.cc b/src/quotes.cc index 50791560..8ae8ca90 100644 --- a/src/quotes.cc +++ b/src/quotes.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -79,13 +79,8 @@ commodity_quote_from_script(commodity_t& commodity, if (optional > point = commodity_pool_t::current_pool->parse_price_directive(buf)) { if (commodity_pool_t::current_pool->price_db) { -#if defined(__GNUG__) && __GNUG__ < 3 - ofstream database(*commodity_pool_t::current_pool->price_db, - ios::out | ios::app); -#else ofstream database(*commodity_pool_t::current_pool->price_db, std::ios_base::out | std::ios_base::app); -#endif database << "P " << format_datetime(point->second.when, FMT_WRITTEN) << " " << commodity.symbol() diff --git a/src/system.hh.in b/src/system.hh.in index 799c7102..97651e8b 100644 --- a/src/system.hh.in +++ b/src/system.hh.in @@ -81,10 +81,6 @@ /* System includes */ /*------------------------------------------------------------------------*/ -#if defined(__GNUG__) && __GNUG__ < 3 -#define _XOPEN_SOURCE -#endif - #include #include #include @@ -104,29 +100,6 @@ #include #include -#if defined(__GNUG__) && __GNUG__ < 3 - -namespace std { - inline ostream & right (ostream & i) { - i.setf(i.right, i.adjustfield); - return i; - } - inline ostream & left (ostream & i) { - i.setf(i.left, i.adjustfield); - return i; - } -} - -typedef std::streamoff istream_pos_type; -typedef std::streamoff ostream_pos_type; - -#else // ! (defined(__GNUG__) && __GNUG__ < 3) - -typedef std::istream::pos_type istream_pos_type; -typedef std::ostream::pos_type ostream_pos_type; - -#endif - #include #include #include diff --git a/src/textual.cc b/src/textual.cc index 3416073b..1e494969 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -557,7 +557,7 @@ void instance_t::option_directive(char * line) void instance_t::automated_xact_directive(char * line) { - istream_pos_type pos = context.line_beg_pos; + std::istream::pos_type pos = context.line_beg_pos; bool reveal_context = true; @@ -653,7 +653,7 @@ void instance_t::automated_xact_directive(char * line) void instance_t::period_xact_directive(char * line) { - istream_pos_type pos = context.line_beg_pos; + std::istream::pos_type pos = context.line_beg_pos; bool reveal_context = true; @@ -916,8 +916,8 @@ void instance_t::end_apply_directive(char * kind) void instance_t::account_directive(char * line) { - istream_pos_type beg_pos = context.line_beg_pos; - std::size_t beg_linenum = context.linenum; + std::istream::pos_type beg_pos = context.line_beg_pos; + std::size_t beg_linenum = context.linenum; char * p = skip_ws(line); account_t * account = diff --git a/src/token.cc b/src/token.cc index 1ec052ed..76bf5106 100644 --- a/src/token.cc +++ b/src/token.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -380,7 +380,7 @@ void expr_t::token_t::next(std::istream& in, const parse_flags_t& pflags) break; default: { - istream_pos_type pos = in.tellg(); + std::istream::pos_type pos = in.tellg(); // First, check to see if it's a reserved word, such as: and or not int result = parse_reserved_word(in); -- cgit v1.2.3 From 6828b8e89bdd9328c81e81e98088143786559976 Mon Sep 17 00:00:00 2001 From: Christoph Dittmann Date: Sun, 17 Jun 2018 11:23:31 +0100 Subject: Expose post_t::given_cost over python This fixes #1655 by making the post_t::given_cost variable accessible over python. This allows access to the given cost of a posting. For example, here it will be "-2 EUR": A -2 XXX {1 EUR} [2018-01-01] @@ 2 EUR If a per-unit cost is given, the given_cost variable will still contain the cost of the posting. For example, here it will be "-4 EUR": B -2 XXX {1 EUR} [2018-01-01] @ 2 EUR --- src/py_post.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/py_post.cc b/src/py_post.cc index 6195570b..525b47c2 100644 --- a/src/py_post.cc +++ b/src/py_post.cc @@ -153,6 +153,11 @@ void export_post() return_value_policy()), make_setter(&post_t::cost, return_value_policy())) + .add_property("given_cost", + make_getter(&post_t::given_cost, + return_value_policy()), + make_setter(&post_t::given_cost, + return_value_policy())) .add_property("assigned_amount", make_getter(&post_t::assigned_amount, return_value_policy()), -- cgit v1.2.3 From 7c0ae5b02571e21f97d45f5d091cb78af9885713 Mon Sep 17 00:00:00 2001 From: Michael Budde Date: Sat, 26 Jan 2019 09:30:35 +0100 Subject: Fix possible stack overflow in date parsing routine It is possible to create a stack overflow by giving a date that is longer than the buffer that is used during date parsing because the length of the input string is not checked. The `VERIFY` macro is only enabled when debug-mode is enabled and the `--verify-memory` argument is used. Prevent the issue by always checking the input string length and discarding dates that does not fit in the buffer as invalid. This issue has been assigned CVE-2017-12482. Fixes #1224 --- doc/NEWS | 3 +++ src/times.cc | 4 +++- test/regress/1224.test | 9 +++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 test/regress/1224.test diff --git a/doc/NEWS b/doc/NEWS index 5152ad7c..a7c9bbb5 100644 --- a/doc/NEWS +++ b/doc/NEWS @@ -48,6 +48,9 @@ - Fix use-after-free issue with deferred postings (bug #1723, TALOS-2017-0304, CVE-2017-2808) +- Fix possible stack overflow in date parsing routine (bug #1224, + CVE-2017-12482) + - Python: Removed double quotes from Unicode values. - Python: Ensure that parse errors produce useful RuntimeErrors diff --git a/src/times.cc b/src/times.cc index db0d74ff..74773755 100644 --- a/src/times.cc +++ b/src/times.cc @@ -127,7 +127,9 @@ namespace { date_t parse_date_mask_routine(const char * date_str, date_io_t& io, date_traits_t * traits = NULL) { - VERIFY(std::strlen(date_str) < 127); + if (std::strlen(date_str) > 127) { + throw_(date_error, _f("Invalid date: %1%") % date_str); + } char buf[128]; std::strcpy(buf, date_str); diff --git a/test/regress/1224.test b/test/regress/1224.test new file mode 100644 index 00000000..ecf87228 --- /dev/null +++ b/test/regress/1224.test @@ -0,0 +1,9 @@ +2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + +test reg -> 1 +__ERROR__ +While parsing file "$FILE", line 1: +While parsing transaction: +> 2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +Error: Invalid date: 2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +end test -- cgit v1.2.3 From c5343f18744d0f6fddcc590f9a54c23674d8c489 Mon Sep 17 00:00:00 2001 From: Martin Michlmayr Date: Sat, 26 Jan 2019 13:02:25 -0300 Subject: Fix possible stack overflow in option parsing routine It is possible to create a stack overflow by giving an option that is longer than the buffer that is used during option parsing because the length of the input string is not checked. Prevent the issue by always checking the input string length and discarding options that does not fit in the buffer as invalid. This issue has been assigned CVE-2017-12481. Thanks to Gwan Yeong Kim for reporting this issue. Fixes #1222 --- doc/NEWS | 3 +++ src/option.cc | 5 +++++ test/regress/1222.test | 7 +++++++ 3 files changed, 15 insertions(+) create mode 100644 test/regress/1222.test diff --git a/doc/NEWS b/doc/NEWS index a7c9bbb5..39fce3d6 100644 --- a/doc/NEWS +++ b/doc/NEWS @@ -48,6 +48,9 @@ - Fix use-after-free issue with deferred postings (bug #1723, TALOS-2017-0304, CVE-2017-2808) +- Fix possible stack overflow in option parsing routine (bug #1222, + CVE-2017-12481) + - Fix possible stack overflow in date parsing routine (bug #1224, CVE-2017-12482) diff --git a/src/option.cc b/src/option.cc index ab6c37e0..81f9af5b 100644 --- a/src/option.cc +++ b/src/option.cc @@ -42,6 +42,11 @@ namespace { { char buf[128]; char * p = buf; + + if (name.length() > 127) { + throw_(option_error, _f("Illegal option --%1%") % name); + } + foreach (char ch, name) { if (ch == '-') *p++ = '_'; diff --git a/test/regress/1222.test b/test/regress/1222.test new file mode 100644 index 00000000..535a0e32 --- /dev/null +++ b/test/regress/1222.test @@ -0,0 +1,7 @@ +--fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo + +test reg -> 1 +__ERROR__ +While parsing file "$FILE", line 1: +Error: Illegal option --fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo +end test -- cgit v1.2.3 From 33cbed3211ccab0f18e1ad7af3fea6af697df2e0 Mon Sep 17 00:00:00 2001 From: Jonas Meurer Date: Sat, 26 Jan 2019 13:41:21 -0300 Subject: Add short option -f (for --file) to man page --- doc/ledger.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ledger.1 b/doc/ledger.1 index fc4dd3c0..a76750ba 100644 --- a/doc/ledger.1 +++ b/doc/ledger.1 @@ -581,7 +581,7 @@ Direct to require pre-declarations for entities (such as accounts, commodities and tags) rather than taking entities from cleared transactions as defined. -.It Fl \-file Ar FILE +.It Fl \-file Ar FILE Pq Fl f Read journal data from .Ar FILE . .It Fl \-first Ar INT -- cgit v1.2.3 From 1116472dce6523ce18a7a9ea9376aac8bed9f864 Mon Sep 17 00:00:00 2001 From: Tim Landscheidt Date: Sat, 26 Jan 2019 17:40:21 +0000 Subject: Use CMAKE_CXX_COMPILER_ID for conditions based on compiler CMAKE_CXX_COMPILER is the path to the compiler binary and does not need to follow a specific pattern. For example, on Linux with GCC and without an explicit "-DCMAKE_CXX_COMPILER:PATH=" option, CMAKE_CXX_COMPILER is "/usr/bin/c++" which does not match "g++". CMAKE_CXX_COMPILER_ID however will always reliably be "Clang" or "GNU". --- CMakeLists.txt | 3 +-- src/CMakeLists.txt | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c85a5157..c2c27097 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -296,8 +296,7 @@ configure_file( ${PROJECT_SOURCE_DIR}/src/system.hh.in ${PROJECT_BINARY_DIR}/system.hh) -if((CMAKE_CXX_COMPILER MATCHES "clang") OR - (CMAKE_CXX_COMPILER MATCHES "clang\\+\\+")) +if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") set(CMAKE_INCLUDE_SYSTEM_FLAG_CXX "-isystem ") endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9cd54dbe..06acecf2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -144,7 +144,7 @@ if (WIN32 OR CYGWIN) endif() if (CMAKE_BUILD_TYPE STREQUAL "Debug") - if (CMAKE_CXX_COMPILER MATCHES "clang\\+\\+") + if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") add_definitions( # -Weverything # -Wno-disabled-macro-expansion @@ -199,7 +199,7 @@ if (CMAKE_BUILD_TYPE STREQUAL "Debug") DEPENDS ${_header_filename}) endmacro(ADD_PCH_RULE _header_filename _src_list _other_srcs) - elseif (CMAKE_CXX_COMPILER MATCHES "g\\+\\+") + elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(GXX_WARNING_FLAGS -pedantic -Wall -- cgit v1.2.3 From 96c88663ce3b74fe04d483ad453f1dff504123ff Mon Sep 17 00:00:00 2001 From: Michael Budde Date: Mon, 28 Jan 2019 18:43:08 +0100 Subject: Fix use-after-free when destroying filter chain When using the `--gain` option the `temporaries_t` in `changed_value_posts` filter stores a reference to the `` temp account created in `display_filter_posts`. When destroying the filter chain `display_filter_posts` is destroyed before `changed_value_posts` and this can result in a use-after-free in `temporaries_t::clear()` when `temps` in `changed_value_posts` is cleared during destruction if there are any temp posts referencing the `` account. Fix the issue by clearing the `temporaries_t` in `changed_value_posts` before destroying the rest of the filter chain (which includes `display_filter_posts`). Fixes #541 --- doc/NEWS | 2 ++ src/filters.h | 1 + 2 files changed, 3 insertions(+) diff --git a/doc/NEWS b/doc/NEWS index 39fce3d6..cb7a761c 100644 --- a/doc/NEWS +++ b/doc/NEWS @@ -54,6 +54,8 @@ - Fix possible stack overflow in date parsing routine (bug #1224, CVE-2017-12482) +- Fix use-after-free when using --gain (bug #541) + - Python: Removed double quotes from Unicode values. - Python: Ensure that parse errors produce useful RuntimeErrors diff --git a/src/filters.h b/src/filters.h index c1dc2e04..cf053a24 100644 --- a/src/filters.h +++ b/src/filters.h @@ -604,6 +604,7 @@ public: virtual ~changed_value_posts() { TRACE_DTOR(changed_value_posts); + temps.clear(); handler.reset(); } -- cgit v1.2.3 From b554d1296888f567c3a661cbd6de2a57457528d1 Mon Sep 17 00:00:00 2001 From: Tim Landscheidt Date: Sun, 27 Jan 2019 14:09:31 +0000 Subject: Add Travis CI setup for macOS and homebrew-installed Boost On macOS, CMake detects the Boost.Python component installed by homebrew only when named "python27". Thus this change not only adds a Travis CI setup for macOS, but also a CMake option to switch the component name between "python" and "python27". In addition, precompiling system.hh does not work with the current setup for Clang, so another CMake option to disable it is added. The currently used commands to compile specific versions of Boost do not produce a result that works out of the box on macOS. It should be possible just to mimic homebrew's formula for boost-python (https://github.com/Homebrew/homebrew-core/blob/master/Formula/boost-python.rb), but for the moment on macOS this change tests only against Boost installed by homebrew. --- .travis.yml | 25 +++++++++++++++++++++++-- CMakeLists.txt | 8 +++++++- src/CMakeLists.txt | 4 +++- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index ae2ff727..825ad6d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ compiler: - gcc os: - linux + - osx sudo: false cache: apt: true @@ -20,8 +21,18 @@ env: matrix: # Boost version to build against; an empty string means the # distribution's default. + - BOOST_VERSION="" - BOOST_VERSION="1.61.0" +# The configuration for macOS only works with Boost installed by +# homebrew, so exclude the other combinations. +matrix: + exclude: + - os: linux + env: BOOST_VERSION="" + - os: osx + env: BOOST_VERSION="1.61.0" + addons: coverity_scan: project: @@ -44,6 +55,12 @@ addons: - libboost-iostreams-dev - libboost-filesystem-dev - libboost-serialization-dev + homebrew: + packages: + - boost + - boost-python + - gmp + - mpfr before_install: - | @@ -66,8 +83,12 @@ install: fi before_script: - - cmake . -DUSE_PYTHON=ON -DBUILD_DEBUG=ON - - make + # On macOS, CMake finds the Boost.Python installed by homebrew only + # with the component name "python27". Also, precompiling system.hh + # does not work. + - if [ "$TRAVIS_OS_NAME" = osx -a -z "$BOOST_VERSION" ]; then EXTRA_CMAKE_ARGS="-DPRECOMPILE_SYSTEM_HH=OFF -DUSE_PYTHON27_COMPONENT=ON"; fi + - cmake . -DUSE_PYTHON=ON -DBUILD_DEBUG=ON $EXTRA_CMAKE_ARGS + - make VERBOSE=1 script: - ctest --output-on-failure diff --git a/CMakeLists.txt b/CMakeLists.txt index c2c27097..2116701e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,10 +27,12 @@ endif() ######################################################################## option(USE_PYTHON "Build support for the Python scripting bridge" OFF) +OPTION(USE_PYTHON27_COMPONENT "Use python27 as name of Boost.Python component" OFF) option(USE_DOXYGEN "Build reference documentation using Doxygen" OFF) option(DISABLE_ASSERTS "Build without any internal consistency checks" OFF) option(BUILD_DEBUG "Build support for runtime debugging" OFF) +option(PRECOMPILE_SYSTEM_HH "Precompile system.hh" ON) option(BUILD_LIBRARY "Build and install Ledger as a library" ON) option(BUILD_DOCS "Build and install documentation" OFF) @@ -67,7 +69,11 @@ if (USE_PYTHON) find_package(PythonLibs) if (PYTHONLIBS_FOUND) - set(BOOST_PYTHON python) + if(USE_PYTHON27_COMPONENT) + set(BOOST_PYTHON "python27") + else() + set(BOOST_PYTHON "python") + endif() set(HAVE_BOOST_PYTHON 1) include_directories(SYSTEM ${PYTHON_INCLUDE_DIRS}) else() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 06acecf2..9b39ea94 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -267,7 +267,9 @@ else() endmacro(ADD_PCH_RULE _header_filename _src_list _other_srcs) endif() -add_pch_rule(${PROJECT_BINARY_DIR}/system.hh LEDGER_SOURCES LEDGER_CLI_SOURCES) +if(PRECOMPILE_SYSTEM_HH) + add_pch_rule(${PROJECT_BINARY_DIR}/system.hh LEDGER_SOURCES LEDGER_CLI_SOURCES) +endif() include(GNUInstallDirs) -- cgit v1.2.3 From 2c61d41deaab0343f29b424dbcd1a751928b3056 Mon Sep 17 00:00:00 2001 From: Martin Michlmayr Date: Wed, 30 Jan 2019 00:32:23 -0300 Subject: Update copyright statement for 2019 --- LICENSE.md | 2 +- doc/ledger3.texi | 2 +- src/global.h | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 93cc207c..36dc7cfd 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright (c) 2003-2018, John Wiegley. All rights reserved. +Copyright (c) 2003-2019, John Wiegley. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are diff --git a/doc/ledger3.texi b/doc/ledger3.texi index 9f9a1095..0ac62b29 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -111,7 +111,7 @@ @copying -Copyright @copyright{} 2003--2018, John Wiegley. All rights reserved. +Copyright @copyright{} 2003--2019, John Wiegley. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are diff --git a/src/global.h b/src/global.h index 8f8266ac..4eb02571 100644 --- a/src/global.h +++ b/src/global.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -129,7 +129,7 @@ public: out << '-' << Ledger_VERSION_DATE; out << _(", the command-line accounting tool"); out << - _("\n\nCopyright (c) 2003-2018, John Wiegley. All rights reserved.\n\n\ + _("\n\nCopyright (c) 2003-2019, John Wiegley. All rights reserved.\n\n\ This program is made available under the terms of the BSD Public License.\n\ See LICENSE file included with the distribution for details and disclaimer."); out << std::endl; -- cgit v1.2.3 From 557ab32d30bcca3622e06a6809a541a015e84aa3 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 30 Jan 2019 11:09:08 -0800 Subject: Expose a new utility function for balances: sorted_amounts --- src/balance.cc | 18 +++++++++--------- src/balance.h | 7 +++++++ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/balance.cc b/src/balance.cc index fa1bc20c..478ceb45 100644 --- a/src/balance.cc +++ b/src/balance.cc @@ -240,6 +240,14 @@ balance_t::strip_annotations(const keep_details_t& what_to_keep) const return temp; } +void balance_t::sorted_amounts(amounts_array& sorted) const +{ + foreach (const amounts_map::value_type& pair, amounts) + sorted.push_back(&pair.second); + std::stable_sort(sorted.begin(), sorted.end(), + commodity_t::compare_by_commodity()); +} + void balance_t::map_sorted_amounts(function fn) const { if (! amounts.empty()) { @@ -249,16 +257,8 @@ void balance_t::map_sorted_amounts(function fn) const fn(amount); } else { - typedef std::vector amounts_array; amounts_array sorted; - - foreach (const amounts_map::value_type& pair, amounts) - if (pair.second) - sorted.push_back(&pair.second); - - std::stable_sort(sorted.begin(), sorted.end(), - commodity_t::compare_by_commodity()); - + sorted_amounts(sorted); foreach (const amount_t * amount, sorted) fn(*amount); } diff --git a/src/balance.h b/src/balance.h index 8e773fc5..b9c9c2c8 100644 --- a/src/balance.h +++ b/src/balance.h @@ -81,6 +81,7 @@ class balance_t { public: typedef std::map amounts_map; + typedef std::vector amounts_array; amounts_map amounts; @@ -528,6 +529,12 @@ public: */ balance_t strip_annotations(const keep_details_t& what_to_keep) const; + /** + * Given a balance, insert a commodity-wise sort of the amounts into the + * given amounts_array. + */ + void sorted_amounts(amounts_array& sorted) const; + /** * Iteration primitives. `map_sorted_amounts' allows one to visit * each amount in balance in the proper order for displaying to the -- cgit v1.2.3 From 0080a8c2adfcc4fe26ff0f116f98e6c9fd814652 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 30 Jan 2019 11:46:54 -0800 Subject: Add back some whitespace for clarity --- src/balance.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/balance.cc b/src/balance.cc index 478ceb45..1ae9edb2 100644 --- a/src/balance.cc +++ b/src/balance.cc @@ -243,7 +243,7 @@ balance_t::strip_annotations(const keep_details_t& what_to_keep) const void balance_t::sorted_amounts(amounts_array& sorted) const { foreach (const amounts_map::value_type& pair, amounts) - sorted.push_back(&pair.second); + sorted.push_back(&pair.second); std::stable_sort(sorted.begin(), sorted.end(), commodity_t::compare_by_commodity()); } -- cgit v1.2.3 From 7a5871d3b53e66ee261bea4b41512e99269b18b1 Mon Sep 17 00:00:00 2001 From: Martin Michlmayr Date: Tue, 5 Feb 2019 08:18:54 -0500 Subject: Update NEWS for 3.1.2 --- doc/NEWS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/NEWS b/doc/NEWS index cb7a761c..a59a1b1a 100644 --- a/doc/NEWS +++ b/doc/NEWS @@ -1,6 +1,6 @@ Ledger NEWS -* 3.1.2 (unreleased) +* 3.1.2 (2019-02-05) - Increase maximum length for regex from 255 to 4095 (bug #981) @@ -32,7 +32,7 @@ - Keep pending items in budgets until the last day they apply -- Fixed bug where .total used in value expressions breaks totals +- Fix bug where .total used in value expressions breaks totals - Make automated transactions work with assertions (bug #1127) -- cgit v1.2.3