summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am10
-rwxr-xr-xacprep13
-rwxr-xr-xcontrib/getquote.pl5
-rw-r--r--lib/Makefile2
-rw-r--r--lisp/ledger.el50
-rw-r--r--src/account.cc19
-rw-r--r--src/account.h7
-rw-r--r--src/amount.cc50
-rw-r--r--src/amount.h2
-rw-r--r--src/annotate.cc205
-rw-r--r--src/annotate.h199
-rw-r--r--src/balance.cc17
-rw-r--r--src/balance.h9
-rw-r--r--src/commodity.cc470
-rw-r--r--src/commodity.h232
-rw-r--r--src/derive.cc144
-rw-r--r--src/filters.cc39
-rw-r--r--src/filters.h8
-rw-r--r--src/format.cc18
-rw-r--r--src/format.h6
-rw-r--r--src/global.cc164
-rw-r--r--src/global.h7
-rw-r--r--src/interactive.cc13
-rw-r--r--src/item.cc19
-rw-r--r--src/iterators.cc4
-rw-r--r--src/iterators.h2
-rw-r--r--src/ledger.h81
-rw-r--r--src/op.cc14
-rw-r--r--src/option.cc18
-rw-r--r--src/option.h49
-rw-r--r--src/output.cc9
-rw-r--r--src/parser.cc15
-rw-r--r--src/pool.cc361
-rw-r--r--src/pool.h141
-rw-r--r--src/post.cc8
-rw-r--r--src/post.h1
-rw-r--r--src/predicate.h1
-rw-r--r--src/py_amount.cc2
-rw-r--r--src/py_journal.cc11
-rw-r--r--src/py_value.cc1
-rw-r--r--src/pyledger.h36
-rw-r--r--src/quotes.cc99
-rw-r--r--src/quotes.h34
-rw-r--r--src/report.cc140
-rw-r--r--src/report.h434
-rw-r--r--src/session.cc38
-rw-r--r--src/session.h31
-rw-r--r--src/textual.cc74
-rw-r--r--src/times.cc8
-rw-r--r--src/token.cc5
-rw-r--r--src/token.h2
-rw-r--r--src/unistring.h13
-rw-r--r--src/value.cc76
-rw-r--r--src/value.h7
-rw-r--r--src/xact.cc17
-rw-r--r--src/xact.h8
-rw-r--r--test/baseline/feat-balance-assignments.test37
-rw-r--r--test/baseline/feat-fixated-prices.test (renamed from test/baseline/feature-fixated-prices.test)0
-rw-r--r--test/baseline/opt-anon.test4
-rw-r--r--test/baseline/opt-color.test13
-rw-r--r--test/baseline/opt-comm-as-account.test11
-rw-r--r--test/baseline/opt-comm-as-payee.test11
-rw-r--r--test/baseline/opt-input-date-format.test11
-rw-r--r--test/baseline/opt-limit.test212
-rw-r--r--test/baseline/opt-lot-tags.test12
-rw-r--r--test/baseline/opt-lots-actual.test27
-rw-r--r--test/baseline/opt-no-total.test10
-rw-r--r--test/baseline/opt-only.test236
-rw-r--r--test/baseline/opt-percent.test94
-rw-r--r--test/baseline/opt-period.test290
-rw-r--r--test/baseline/opt-plot-amount-format.test10
-rw-r--r--test/baseline/opt-plot-total-format.test10
-rw-r--r--test/baseline/opt-price.test47
-rw-r--r--test/baseline/opt-prices-format.test19
-rw-r--r--test/baseline/opt-pricesdb-format.test19
-rw-r--r--test/baseline/opt-print-format.test10
-rw-r--r--test/baseline/opt-quantity.test11
-rw-r--r--test/baseline/opt-real.test11
-rw-r--r--test/baseline/opt-register-format.test10
-rw-r--r--test/baseline/opt-related-all.test14
-rw-r--r--test/baseline/opt-related.test13
-rw-r--r--test/baseline/opt-seed.test432
-rw-r--r--test/baseline/opt-sort-all.test122
-rw-r--r--test/baseline/opt-sort-xacts.test176
-rw-r--r--test/baseline/opt-sort.test246
-rw-r--r--test/baseline/opt-subtotal.test93
-rw-r--r--test/baseline/opt-total-data.test10
-rw-r--r--test/baseline/opt-total.test10
-rw-r--r--test/baseline/opt-truncate.test144
-rw-r--r--test/baseline/opt-unround.test94
-rw-r--r--test/input/drewr.dat4
-rw-r--r--test/regress/04C5E1CA.test18
-rw-r--r--test/regress/5A03CFC3.test72
-rw-r--r--test/regress/5FBF2ED8.test20
-rw-r--r--test/regress/647D5DB9.test17
-rw-r--r--test/regress/6DAB9FE3.test10
-rw-r--r--test/regress/727B2DF8.test81
-rw-r--r--test/regress/793F6BF0.test52
-rw-r--r--test/regress/86D2BDC4.test12
-rw-r--r--test/regress/B68FFB0D.test15
-rw-r--r--test/regress/BBFA1759.test10
-rw-r--r--test/regress/C523E23F.test21
-rw-r--r--test/regress/D943AE0F.test13
-rw-r--r--test/regress/F559EC12.test35
-rw-r--r--test/unit/t_amount.cc40
105 files changed, 4950 insertions, 1387 deletions
diff --git a/Makefile.am b/Makefile.am
index cdc7d4b3..8ed18fbb 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -30,6 +30,9 @@ libledger_util_la_LDFLAGS = -release $(VERSION).0
libledger_math_la_SOURCES = \
src/value.cc \
src/balance.cc \
+ src/quotes.cc \
+ src/pool.cc \
+ src/annotate.cc \
src/commodity.cc \
src/amount.cc
@@ -65,7 +68,6 @@ libledger_data_la_CPPFLAGS = $(lib_cppflags)
libledger_data_la_LDFLAGS = -release $(VERSION).0
libledger_report_la_SOURCES = \
- src/quotes.cc \
src/stats.cc \
src/generate.cc \
src/derive.cc \
@@ -94,6 +96,9 @@ pkginclude_HEADERS = \
\
src/amount.h \
src/commodity.h \
+ src/annotate.h \
+ src/pool.h \
+ src/quotes.h \
src/balance.h \
src/value.h \
\
@@ -126,12 +131,9 @@ pkginclude_HEADERS = \
src/stats.h \
src/output.h \
src/emacs.h \
- src/quotes.h \
\
src/global.h \
- src/ledger.h \
\
- src/pyledger.h \
src/pyinterp.h \
\
lib/fdstream.h \
diff --git a/acprep b/acprep
index c1a0ddc7..97be7b61 100755
--- a/acprep
+++ b/acprep
@@ -584,7 +584,7 @@ class PrepareBuild(CommandLineApp):
environ, conf_args = self.configure_environment()
- boost = 'boost_1_38_0'
+ boost = 'boost_1_39_0'
tarball = boost + '.tar.bz2'
if not exists(boost):
@@ -604,7 +604,7 @@ class PrepareBuild(CommandLineApp):
fd.close()
digest = csum.hexdigest()
- if digest != '5eca2116d39d61382b8f8235915cb267':
+ if digest != 'a17281fd88c48e0d866e1a12deecbcc0':
self.log.error('Boost source tarball fails to match checksum')
sys.exit(1)
@@ -950,17 +950,17 @@ class PrepareBuild(CommandLineApp):
self.sys_include_dirs.insert(0, '/usr/local/stow/cppunit-debug/include')
self.sys_library_dirs.insert(0, '/usr/local/stow/cppunit-debug/lib')
- if exists('/usr/local/lib/libboost_regex-xgcc43-sd-1_38.a'):
+ if exists('/usr/local/lib/libboost_regex-xgcc43-sd-1_39.a'):
self.envvars['BOOST_HOME'] = '/usr/local'
- self.envvars['BOOST_SUFFIX'] = '-xgcc43-sd-1_38'
- self.envvars['BOOST_VERSION'] = '1_38'
+ self.envvars['BOOST_SUFFIX'] = '-xgcc43-sd-1_39'
+ self.envvars['BOOST_VERSION'] = '1_39'
self.log.debug('Setting BOOST_SUFFIX => %s' %
self.envvars['BOOST_SUFFIX'])
self.log.debug('Setting BOOST_VERSION => %s' %
self.envvars['BOOST_VERSION'])
- self.sys_include_dirs.append('/usr/local/include/boost-1_38')
+ self.sys_include_dirs.append('/usr/local/include/boost-1_39')
def setup_flavor_opt(self):
self.CXXFLAGS.append('-O3')
@@ -996,6 +996,7 @@ class PrepareBuild(CommandLineApp):
line = re.sub('^\t(\$\((LIBTOOL|CXX)\).*?\.(cc|cpp))$',
'\t@echo " " CXX \$@;\\1 > /dev/null', line)
line = re.sub('^\tmv -f', '\t@mv -f', line)
+ line = re.sub('^\t\$\(am__mv\)', '\t@$(am__mv)', line)
line = re.sub('^\t(\$\((.*?)LINK\).*)',
'\t@echo " " LD \$@;\\1 > /dev/null', line)
Makefile_new.write(line)
diff --git a/contrib/getquote.pl b/contrib/getquote.pl
index bed561d6..8e3bb678 100755
--- a/contrib/getquote.pl
+++ b/contrib/getquote.pl
@@ -1,8 +1,9 @@
-#!/usr/bin/perl
+#!/usr/bin/env perl
$timeout = 60;
use Finance::Quote;
+use POSIX qw(strftime localtime time);
$q = Finance::Quote->new;
$q->timeout($timeout);
@@ -10,6 +11,8 @@ $q->require_labels(qw/price/);
%quotes = $q->fetch("nasdaq", $ARGV[0]);
if ($quotes{$ARGV[0], "price"}) {
+ print strftime('%Y/%m/%d %H:%M:%S', localtime(time()));
+ print " ", $ARGV[0], " ";
print "\$", $quotes{$ARGV[0], "price"}, "\n";
} else {
exit 1;
diff --git a/lib/Makefile b/lib/Makefile
index 67b45485..38ff924d 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -8,7 +8,7 @@
STOW_ROOT = /usr/local/stow
BOOST_SOURCE = boost
-BOOST_VERSION = 1_38_0
+BOOST_VERSION = 1_39_0
# architecture=combined
boost-release:
diff --git a/lisp/ledger.el b/lisp/ledger.el
index a649250d..be730fd9 100644
--- a/lisp/ledger.el
+++ b/lisp/ledger.el
@@ -128,17 +128,17 @@ text that should replace the format specifier."
(defvar bold 'bold)
(defvar ledger-font-lock-keywords
- '(("^[0-9./=]+\\s-+\\(?:([^)]+)\\s-+\\)?\\([^*].+\\)" 1 bold)
- ("\s \\{5,\\}\\([$]-[0-9][0-9,.]*\\)" 1 font-lock-warning-face)
- ("\s \\{5,\\}\\(-?[0-9][0-9,.]*\\)" 1 font-lock-type-face)
- ("^\\s-+.+?\\( \\|\t\\|\n\\|\\s-+$\\)" . font-lock-keyword-face)
- ("^\s +\\((\\)\\([A-Za-z0-9]+:[A-Za-z0-9]+[A-Za-z0-9: ]*\\)\\()\\)"
- (1 font-lock-function-name-face)
- (2 font-lock-variable-name-face)
- (3 font-lock-function-name-face))
- ("^[0-9]+[/-][0-9]+.*\\([*]\\)" 1 bold)
- ("^\\([~=]\\)\\s " 1 font-lock-function-name-face))
- "Improved expressions to highlight in Ledger mode.")
+ '(("\\( \\| \\|^\\)\\(;.*\\)" 2 font-lock-comment-face)
+ ("^[0-9]+[-/.=][-/.=0-9]+\\s-+\\(([^)]+)\\s-+\\)?\\([^*].+?\\)\\(\\( ;\\| ;\\|$\\)\\)" 2 bold)
+ ;;("^[0-9]+[-/.=][-/.=0-9]+\\s-+\\(([^)]+)\\s-+\\)?\\([*].+?\\)\\(\\( ;\\| ;\\|$\\)\\)"
+ ;; 2 font-lock-type-face)
+ ("^\\s-+\\([*]\\s-*\\)?\\(\\([[(]\\)?[^*:
+ ]+?:[^]);
+ ]+?\\([])]\\)?\\)\\( \\| \\|$\\)"
+ 2 font-lock-keyword-face)
+ ("^\\([~=].+\\)" 1 font-lock-function-name-face)
+ ("^\\([A-Za-z]+ .+\\)" 1 font-lock-function-name-face))
+ "Expressions to highlight in Ledger mode.")
(defsubst ledger-current-year ()
(format-time-string "%Y"))
@@ -225,9 +225,7 @@ Return the difference in the format of a time value."
(mapcar 'eval args)))
(goto-char (point-min))
(if (looking-at "Error: ")
- (progn
- (message (buffer-string))
- (error))
+ (error (buffer-string))
(buffer-string)))
"\n"))))
@@ -415,8 +413,20 @@ dropped."
(defun ledger-toggle-current (&optional style)
(interactive)
- (if ledger-clear-whole-entries
- (ledger-toggle-current-entry style)
+ (if (or ledger-clear-whole-entries
+ (eq 'entry (ledger-thing-at-point)))
+ (progn
+ (save-excursion
+ (forward-line)
+ (goto-char (line-beginning-position))
+ (while (and (not (eolp))
+ (save-excursion
+ (not (eq 'entry (ledger-thing-at-point)))))
+ (if (looking-at "\\s-+[*!]")
+ (ledger-toggle-current-transaction nil))
+ (forward-line)
+ (goto-char (line-beginning-position))))
+ (ledger-toggle-current-entry style))
(ledger-toggle-current-transaction style)))
(defvar ledger-mode-abbrev-table)
@@ -483,7 +493,7 @@ dropped."
(account ledger-acct)
(inhibit-read-only t)
cleared)
- (when (equal (car where) "<stdin>")
+ (when (or (equal (car where) "<stdin>") (equal (car where) "/dev/stdin"))
(with-current-buffer ledger-buf
(goto-char (cdr where))
(setq cleared (ledger-toggle-current 'pending)))
@@ -522,7 +532,7 @@ dropped."
(defun ledger-reconcile-delete ()
(interactive)
(let ((where (get-text-property (point) 'where)))
- (when (equal (car where) "<stdin>")
+ (when (or (equal (car where) "<stdin>") (equal (car where) "/dev/stdin"))
(with-current-buffer ledger-buf
(goto-char (cdr where))
(ledger-delete-current-entry))
@@ -534,7 +544,7 @@ dropped."
(defun ledger-reconcile-visit ()
(interactive)
(let ((where (get-text-property (point) 'where)))
- (when (equal (car where) "<stdin>")
+ (when (or (equal (car where) "<stdin>") (equal (car where) "/dev/stdin"))
(switch-to-buffer-other-window ledger-buf)
(goto-char (cdr where)))))
@@ -557,7 +567,7 @@ dropped."
(let ((where (get-text-property (point) 'where))
(face (get-text-property (point) 'face)))
(if (and (eq face 'bold)
- (equal (car where) "<stdin>"))
+ (or (equal (car where) "<stdin>") (equal (car where) "/dev/stdin")))
(with-current-buffer ledger-buf
(goto-char (cdr where))
(ledger-toggle-current 'cleared))))
diff --git a/src/account.cc b/src/account.cc
index 711e815e..c2628134 100644
--- a/src/account.cc
+++ b/src/account.cc
@@ -140,7 +140,7 @@ string account_t::partial_name(bool flat) const
if (! flat) {
std::size_t count = acct->children_with_flags(ACCOUNT_EXT_TO_DISPLAY);
assert(count > 0);
- if (count > 1)
+ if (count > 1 || acct->has_flags(ACCOUNT_EXT_TO_DISPLAY))
break;
}
pname = acct->name + ":" + pname;
@@ -194,6 +194,10 @@ namespace {
return false;
}
+ value_t get_true(account_t&) {
+ return true;
+ }
+
value_t get_depth_spacer(account_t& account)
{
std::size_t depth = 0;
@@ -202,7 +206,7 @@ namespace {
acct = acct->parent) {
std::size_t count = acct->children_with_flags(ACCOUNT_EXT_TO_DISPLAY);
assert(count > 0);
- if (count > 1)
+ if (count > 1 || acct->has_flags(ACCOUNT_EXT_TO_DISPLAY))
depth++;
}
@@ -217,6 +221,10 @@ namespace {
value_t get_wrapper(call_scope_t& scope) {
return (*Func)(find_scope<account_t>(scope));
}
+
+ value_t get_parent(account_t& account) {
+ return value_t(static_cast<scope_t *>(account.parent));
+ }
}
expr_t::ptr_op_t account_t::lookup(const string& name)
@@ -243,9 +251,16 @@ expr_t::ptr_op_t account_t::lookup(const string& name)
return WRAP_FUNCTOR(get_wrapper<&get_depth_spacer>);
break;
+ case 'i':
+ if (name == "is_account")
+ return WRAP_FUNCTOR(get_wrapper<&get_true>);
+ break;
+
case 'p':
if (name == "partial_account")
return WRAP_FUNCTOR(get_partial_name);
+ else if (name == "parent")
+ return WRAP_FUNCTOR(get_wrapper<&get_parent>);
break;
case 's':
diff --git a/src/account.h b/src/account.h
index 2f3eec24..56c0c72b 100644
--- a/src/account.h
+++ b/src/account.h
@@ -74,7 +74,6 @@ class account_t : public scope_t
posts_deque posts;
bool known;
- mutable void * data;
mutable string _fullname;
account_t(account_t * _parent = NULL,
@@ -82,7 +81,7 @@ class account_t : public scope_t
const optional<string>& _note = none)
: scope_t(), parent(_parent), name(_name), note(_note),
depth(static_cast<unsigned short>(parent ? parent->depth + 1 : 0)),
- known(false), data(NULL) {
+ known(false) {
TRACE_CTOR(account_t, "account_t *, const string&, const string&");
}
account_t(const account_t& other)
@@ -92,10 +91,8 @@ class account_t : public scope_t
note(other.note),
depth(other.depth),
accounts(other.accounts),
- known(other.known),
- data(NULL) {
+ known(other.known) {
TRACE_CTOR(account_t, "copy");
- assert(other.data == NULL);
}
~account_t();
diff --git a/src/amount.cc b/src/amount.cc
index 7c9a6931..2434f110 100644
--- a/src/amount.cc
+++ b/src/amount.cc
@@ -33,6 +33,8 @@
#include "amount.h"
#include "commodity.h"
+#include "annotate.h"
+#include "pool.h"
namespace ledger {
@@ -343,8 +345,8 @@ amount_t& amount_t::operator*=(const amount_t& amt)
_dup();
mpq_mul(MP(quantity), MP(quantity), MP(amt.quantity));
- quantity->prec = static_cast<precision_t>(quantity->prec +
- amt.quantity->prec);
+ quantity->prec =
+ static_cast<precision_t>(quantity->prec + amt.quantity->prec);
if (! has_commodity())
commodity_ = amt.commodity_;
@@ -382,7 +384,7 @@ amount_t& amount_t::operator/=(const amount_t& amt)
mpq_div(MP(quantity), MP(quantity), MP(amt.quantity));
quantity->prec =
static_cast<precision_t>(quantity->prec + amt.quantity->prec +
- quantity->prec + extend_by_digits);
+ extend_by_digits);
if (! has_commodity())
commodity_ = amt.commodity_;
@@ -488,7 +490,10 @@ void amount_t::in_place_unround()
return;
_dup();
+
+ DEBUG("amount.unround", "Unrounding " << *this);
set_keep_precision(true);
+ DEBUG("amount.unround", "Unrounded = " << *this);
}
void amount_t::in_place_reduce()
@@ -551,17 +556,37 @@ amount_t::value(const bool primary_only,
annotation().has_flags(ANNOTATION_PRICE_FIXATED)) {
return (*annotation().price * number()).rounded();
}
- else if (optional<price_point_t> point =
- commodity().find_price(in_terms_of, moment)) {
- return (point->price * number()).rounded();
+ else {
+ optional<price_point_t> point =
+ commodity().find_price(in_terms_of, moment);
+
+ // Whether a price was found or not, check whether we should attempt
+ // to download a price from the Internet. This is done if (a) no
+ // price was found, or (b) the price is "stale" according to the
+ // setting of --price-exp.
+ point = commodity().check_for_updated_price(point, moment, in_terms_of);
+ if (point)
+ return (point->price * number()).rounded();
}
}
} else {
- throw_(amount_error, _("Cannot determine value of an uninitialized amount"));
+ throw_(amount_error,
+ _("Cannot determine value of an uninitialized amount"));
}
return none;
}
+amount_t amount_t::price() const
+{
+ if (is_annotated() && annotation().price) {
+ amount_t temp(*annotation().price);
+ temp *= *this;
+ DEBUG("amount.price", "Returning price of " << *this << " = " << temp);
+ return temp;
+ }
+ return *this;
+}
+
int amount_t::sign() const
{
@@ -740,7 +765,7 @@ void amount_t::annotate(const annotation_t& details)
if (! quantity)
throw_(amount_error, _("Cannot annotate the commodity of an uninitialized amount"));
else if (! has_commodity())
- throw_(amount_error, _("Cannot annotate an amount with no commodity"));
+ return; // ignore attempt to annotate a "bare commodity
if (commodity().annotated) {
this_ann = &as_annotated_commodity(commodity());
@@ -770,8 +795,9 @@ bool amount_t::is_annotated() const
throw_(amount_error,
_("Cannot determine if an uninitialized amount's commodity is annotated"));
- assert(! commodity().annotated || as_annotated_commodity(commodity()).details);
- return commodity().annotated;
+ assert(! has_commodity() || ! commodity().annotated ||
+ as_annotated_commodity(commodity()).details);
+ return has_commodity() && commodity().annotated;
}
annotation_t& amount_t::annotation()
@@ -1062,8 +1088,10 @@ void amount_t::print(std::ostream& _out) const
bool amount_t::valid() const
{
if (quantity) {
- if (! quantity->valid())
+ if (! quantity->valid()) {
+ DEBUG("ledger.validate", "amount_t: ! quantity->valid()");
return false;
+ }
if (quantity->ref == 0) {
DEBUG("ledger.validate", "amount_t: quantity->ref == 0");
diff --git a/src/amount.h b/src/amount.h
index 990b326f..b3c632af 100644
--- a/src/amount.h
+++ b/src/amount.h
@@ -384,6 +384,8 @@ public:
const optional<datetime_t>& moment = none,
const optional<commodity_t&>& in_terms_of = none) const;
+ amount_t price() const;
+
/*@}*/
/** @name Truth tests
diff --git a/src/annotate.cc b/src/annotate.cc
new file mode 100644
index 00000000..1ea39b5d
--- /dev/null
+++ b/src/annotate.cc
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2003-2009, 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.
+ */
+
+#include <system.hh>
+
+#include "amount.h"
+#include "commodity.h"
+#include "annotate.h"
+#include "pool.h"
+
+namespace ledger {
+
+void annotation_t::parse(std::istream& in)
+{
+ do {
+ istream_pos_type pos = in.tellg();
+
+ char buf[256];
+ char c = peek_next_nonws(in);
+ if (c == '{') {
+ if (price)
+ throw_(amount_error, _("Commodity specifies more than one price"));
+
+ in.get(c);
+ c = peek_next_nonws(in);
+ if (c == '=') {
+ in.get(c);
+ add_flags(ANNOTATION_PRICE_FIXATED);
+ }
+
+ READ_INTO(in, buf, 255, c, c != '}');
+ if (c == '}')
+ in.get(c);
+ else
+ throw_(amount_error, _("Commodity price lacks closing brace"));
+
+ amount_t temp;
+ temp.parse(buf, amount_t::PARSE_NO_MIGRATE);
+
+ DEBUG("commodity.annotations", "Parsed annotation price: " << temp);
+
+ // Since this price will maintain its own precision, make sure
+ // it is at least as large as the base commodity, since the user
+ // may have only specified {$1} or something similar.
+
+ if (temp.has_commodity() &&
+ temp.precision() > temp.commodity().precision())
+ temp = temp.rounded(); // no need to retain individual precision
+
+ price = temp;
+ }
+ else if (c == '[') {
+ if (date)
+ throw_(amount_error, _("Commodity specifies more than one date"));
+
+ in.get(c);
+ READ_INTO(in, buf, 255, c, c != ']');
+ if (c == ']')
+ in.get(c);
+ else
+ throw_(amount_error, _("Commodity date lacks closing bracket"));
+
+ date = parse_date(buf);
+ }
+ else if (c == '(') {
+ if (tag)
+ throw_(amount_error, _("Commodity specifies more than one tag"));
+
+ in.get(c);
+ READ_INTO(in, buf, 255, c, c != ')');
+ if (c == ')')
+ in.get(c);
+ else
+ throw_(amount_error, _("Commodity tag lacks closing parenthesis"));
+
+ tag = buf;
+ }
+ else {
+ in.clear();
+ in.seekg(pos, std::ios::beg);
+ break;
+ }
+ } while (true);
+
+#if defined(DEBUG_ON)
+ if (SHOW_DEBUG("amounts.commodities") && *this) {
+ DEBUG("amounts.commodities",
+ "Parsed commodity annotations: " << std::endl << *this);
+ }
+#endif
+}
+
+void annotation_t::print(std::ostream& out, bool keep_base) const
+{
+ if (price)
+ out << " {"
+ << (has_flags(ANNOTATION_PRICE_FIXATED) ? "=" : "")
+ << (keep_base ? *price : price->unreduced()).rounded()
+ << '}';
+
+ if (date)
+ out << " [" << format_date(*date, string("%Y/%m/%d")) << ']';
+
+ if (tag)
+ out << " (" << *tag << ')';
+}
+
+bool keep_details_t::keep_all(const commodity_t& comm) const
+{
+ return (! comm.annotated ||
+ (keep_price && keep_date && keep_tag && ! only_actuals));
+}
+
+bool keep_details_t::keep_any(const commodity_t& comm) const
+{
+ return comm.annotated && (keep_price || keep_date || keep_tag);
+}
+
+bool annotated_commodity_t::operator==(const commodity_t& comm) const
+{
+ // If the base commodities don't match, the game's up.
+ if (base != comm.base)
+ return false;
+
+ assert(annotated);
+ if (! comm.annotated)
+ return false;
+
+ if (details != as_annotated_commodity(comm).details)
+ return false;
+
+ return true;
+}
+
+commodity_t&
+annotated_commodity_t::strip_annotations(const keep_details_t& what_to_keep)
+{
+ DEBUG("commodity.annotated.strip",
+ "Reducing commodity " << *this << std::endl
+ << " keep price " << what_to_keep.keep_price << " "
+ << " keep date " << what_to_keep.keep_date << " "
+ << " keep tag " << what_to_keep.keep_tag);
+
+ commodity_t * new_comm;
+
+ bool keep_price = (what_to_keep.keep_price &&
+ (! what_to_keep.only_actuals ||
+ ! details.has_flags(ANNOTATION_PRICE_CALCULATED)));
+ bool keep_date = (what_to_keep.keep_date &&
+ (! what_to_keep.only_actuals ||
+ ! details.has_flags(ANNOTATION_DATE_CALCULATED)));
+ bool keep_tag = (what_to_keep.keep_tag &&
+ (! what_to_keep.only_actuals ||
+ ! details.has_flags(ANNOTATION_TAG_CALCULATED)));
+
+ if ((keep_price && details.price) ||
+ (keep_date && details.date) ||
+ (keep_tag && details.tag))
+ {
+ new_comm = parent().find_or_create
+ (referent(), annotation_t(keep_price ? details.price : none,
+ keep_date ? details.date : none,
+ keep_tag ? details.tag : none));
+ } else {
+ new_comm = parent().find_or_create(base_symbol());
+ }
+
+ assert(new_comm);
+ return *new_comm;
+}
+
+void annotated_commodity_t::write_annotations(std::ostream& out) const
+{
+ details.print(out, parent().keep_base);
+}
+
+} // namespace ledger
diff --git a/src/annotate.h b/src/annotate.h
new file mode 100644
index 00000000..d98f7ef6
--- /dev/null
+++ b/src/annotate.h
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2003-2009, 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.
+ */
+
+/**
+ * @addtogroup math
+ */
+
+/**
+ * @file annotate.h
+ * @author John Wiegley
+ *
+ * @ingroup math
+ *
+ * @brief Types for annotating commodities
+ *
+ * Long.
+ */
+#ifndef _ANNOTATE_H
+#define _ANNOTATE_H
+
+namespace ledger {
+
+/**
+ * @brief Brief
+ *
+ * Long.
+ */
+struct annotation_t : public supports_flags<>,
+ public equality_comparable<annotation_t>
+{
+#define ANNOTATION_PRICE_CALCULATED 0x01
+#define ANNOTATION_PRICE_FIXATED 0x02
+#define ANNOTATION_DATE_CALCULATED 0x04
+#define ANNOTATION_TAG_CALCULATED 0x08
+
+ optional<amount_t> price;
+ optional<date_t> date;
+ optional<string> tag;
+
+ explicit annotation_t(const optional<amount_t>& _price = none,
+ const optional<date_t>& _date = none,
+ const optional<string>& _tag = none)
+ : supports_flags<>(), price(_price), date(_date), tag(_tag) {
+ TRACE_CTOR(annotation_t, "const optional<amount_t>& + date_t + string");
+ }
+ annotation_t(const annotation_t& other)
+ : supports_flags<>(other.flags()),
+ price(other.price), date(other.date), tag(other.tag) {
+ TRACE_CTOR(annotation_t, "copy");
+ }
+ ~annotation_t() {
+ TRACE_DTOR(annotation_t);
+ }
+
+ operator bool() const {
+ return price || date || tag;
+ }
+
+ bool operator==(const annotation_t& rhs) const {
+ return (price == rhs.price &&
+ date == rhs.date &&
+ tag == rhs.tag);
+ }
+
+ void parse(std::istream& in);
+
+ void print(std::ostream& out, bool keep_base = false) const;
+
+ bool valid() const {
+ assert(*this);
+ return true;
+ }
+};
+
+struct keep_details_t
+{
+ bool keep_price;
+ bool keep_date;
+ bool keep_tag;
+ bool only_actuals;
+
+ explicit keep_details_t(bool _keep_price = false,
+ bool _keep_date = false,
+ bool _keep_tag = false,
+ bool _only_actuals = false)
+ : keep_price(_keep_price),
+ keep_date(_keep_date),
+ keep_tag(_keep_tag),
+ only_actuals(_only_actuals)
+ {
+ TRACE_CTOR(keep_details_t, "bool, bool, bool, bool");
+ }
+ keep_details_t(const keep_details_t& other)
+ : keep_price(other.keep_price), keep_date(other.keep_date),
+ keep_tag(other.keep_tag), only_actuals(other.only_actuals) {
+ TRACE_CTOR(keep_details_t, "copy");
+ }
+ ~keep_details_t() throw() {
+ TRACE_DTOR(keep_details_t);
+ }
+
+ bool keep_all() const {
+ return keep_price && keep_date && keep_tag && ! only_actuals;
+ }
+ bool keep_all(const commodity_t& comm) const;
+
+ bool keep_any() const {
+ return keep_price || keep_date || keep_tag;
+ }
+ bool keep_any(const commodity_t& comm) const;
+};
+
+inline std::ostream& operator<<(std::ostream& out,
+ const annotation_t& details) {
+ details.print(out);
+ return out;
+}
+
+/**
+ * @brief Brief
+ *
+ * Long.
+ */
+class annotated_commodity_t
+ : public commodity_t,
+ public equality_comparable<annotated_commodity_t,
+ equality_comparable2<annotated_commodity_t, commodity_t,
+ noncopyable> >
+{
+public:
+ commodity_t * ptr;
+ annotation_t details;
+
+ explicit annotated_commodity_t(commodity_t * _ptr,
+ const annotation_t& _details)
+ : commodity_t(_ptr->parent_, _ptr->base), ptr(_ptr), details(_details) {
+ TRACE_CTOR(annotated_commodity_t, "");
+ annotated = true;
+ }
+ virtual ~annotated_commodity_t() {
+ TRACE_DTOR(annotated_commodity_t);
+ }
+
+ virtual bool operator==(const commodity_t& comm) const;
+ virtual bool operator==(const annotated_commodity_t& comm) const {
+ return *this == static_cast<const commodity_t&>(comm);
+ }
+
+ virtual commodity_t& referent() {
+ return *ptr;
+ }
+ virtual const commodity_t& referent() const {
+ return *ptr;
+ }
+
+ virtual commodity_t& strip_annotations(const keep_details_t& what_to_keep);
+ virtual void write_annotations(std::ostream& out) const;
+};
+
+inline annotated_commodity_t&
+as_annotated_commodity(commodity_t& commodity) {
+ return downcast<annotated_commodity_t>(commodity);
+}
+inline const annotated_commodity_t&
+as_annotated_commodity(const commodity_t& commodity) {
+ return downcast<const annotated_commodity_t>(commodity);
+}
+
+} // namespace ledger
+
+#endif // _ANNOTATE_H
diff --git a/src/balance.cc b/src/balance.cc
index 864926cf..274f860a 100644
--- a/src/balance.cc
+++ b/src/balance.cc
@@ -33,6 +33,7 @@
#include "balance.h"
#include "commodity.h"
+#include "pool.h"
#include "unistring.h" // for justify()
namespace ledger {
@@ -202,6 +203,16 @@ balance_t::value(const bool primary_only,
return resolved ? temp : optional<balance_t>();
}
+balance_t balance_t::price() const
+{
+ balance_t temp;
+
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.price();
+
+ return temp;
+}
+
optional<amount_t>
balance_t::commodity_amount(const optional<const commodity_t&>& commodity) const
{
@@ -243,7 +254,8 @@ balance_t::strip_annotations(const keep_details_t& what_to_keep) const
void balance_t::print(std::ostream& out,
const int first_width,
const int latter_width,
- const bool right_justify) const
+ const bool right_justify,
+ const bool colorize) const
{
bool first = true;
int lwidth = latter_width;
@@ -272,7 +284,8 @@ void balance_t::print(std::ostream& out,
std::ostringstream buf;
buf << *amount;
- justify(out, buf.str(), width, right_justify);
+ justify(out, buf.str(), width, right_justify,
+ colorize && amount->sign() < 0);
}
if (first) {
diff --git a/src/balance.h b/src/balance.h
index 15657c5e..765c46dd 100644
--- a/src/balance.h
+++ b/src/balance.h
@@ -367,6 +367,8 @@ public:
const optional<datetime_t>& moment = none,
const optional<commodity_t&>& in_terms_of = none) const;
+ balance_t price() const;
+
/**
* Truth tests. An balance may be truth test in two ways:
*
@@ -483,7 +485,8 @@ public:
void print(std::ostream& out,
const int first_width = -1,
const int latter_width = -1,
- const bool right_justify = true) const;
+ const bool right_justify = true,
+ const bool colorize = true) const;
/**
* Debugging methods. There are two methods defined to help with
@@ -511,8 +514,10 @@ public:
bool valid() const {
foreach (const amounts_map::value_type& pair, amounts)
- if (! pair.second.valid())
+ if (! pair.second.valid()) {
+ DEBUG("ledger.validate", "balance_t: ! pair.second.valid()");
return false;
+ }
return true;
}
};
diff --git a/src/commodity.cc b/src/commodity.cc
index 67a86b87..37f0b573 100644
--- a/src/commodity.cc
+++ b/src/commodity.cc
@@ -33,6 +33,8 @@
#include "amount.h"
#include "commodity.h"
+#include "annotate.h"
+#include "pool.h"
namespace ledger {
@@ -95,8 +97,9 @@ void commodity_t::base_t::varied_history_t::
hist->add_price(source, date, price, reflexive);
}
-bool commodity_t::base_t::varied_history_t::remove_price(const datetime_t& date,
- commodity_t& comm)
+bool commodity_t::base_t::varied_history_t::
+ remove_price(const datetime_t& date,
+ commodity_t& comm)
{
DEBUG("commodity.prices.add", "varied_remove_price: " << date << ", " << comm);
@@ -107,8 +110,8 @@ bool commodity_t::base_t::varied_history_t::remove_price(const datetime_t& date
optional<price_point_t>
commodity_t::base_t::history_t::
- find_price(const optional<datetime_t>& moment,
- const optional<datetime_t>& oldest
+ find_price(const optional<datetime_t>& moment,
+ const optional<datetime_t>& oldest
#if defined(DEBUG_ON)
, const int indent
#endif
@@ -185,16 +188,6 @@ optional<price_point_t>
}
}
-#if 0
- if (! has_flags(COMMODITY_NOMARKET) && parent().get_quote) {
- if (optional<amount_t> quote = parent().get_quote
- (*this, age, moment,
- (hist && hist->prices.size() > 0 ?
- (*hist->prices.rbegin()).first : optional<datetime_t>())))
- return *quote;
- }
-#endif
-
if (! found) {
#if defined(DEBUG_ON)
DEBUG_INDENT("commodity.prices.find", indent);
@@ -351,6 +344,8 @@ optional<price_point_t>
DEBUG_INDENT("commodity.prices.find", indent);
DEBUG("commodity.prices.find",
" found price " << best.price << " from " << best.when);
+ DEBUG("commodity.download",
+ "found price " << best.price << " from " << best.when);
#endif
return best;
}
@@ -368,7 +363,8 @@ optional<commodity_t::base_t::history_t&>
#if 0
// jww (2008-09-20): Document which option switch to use here
throw_(commodity_error,
- _("Cannot determine price history: prices known for multiple commodities (use -x)"));
+ _("Cannot determine price history: "
+ "prices known for multiple commodities (use -x)"));
#endif
comm = (*histories.begin()).first;
} else {
@@ -382,78 +378,43 @@ optional<commodity_t::base_t::history_t&>
return none;
}
-void commodity_t::exchange(commodity_t& commodity,
- const amount_t& per_unit_cost,
- const datetime_t& moment)
-{
- DEBUG("commodity.prices.add", "exchanging commodity " << commodity
- << " at per unit cost " << per_unit_cost << " on " << moment);
-
- commodity_t& base_commodity
- (commodity.annotated ?
- as_annotated_commodity(commodity).referent() : commodity);
-
- base_commodity.add_price(moment, per_unit_cost);
-}
-
-commodity_t::cost_breakdown_t
-commodity_t::exchange(const amount_t& amount,
- const amount_t& cost,
- const bool is_per_unit,
- const optional<datetime_t>& moment,
- const optional<string>& tag)
+optional<price_point_t>
+commodity_t::check_for_updated_price(const optional<price_point_t>& point,
+ const optional<datetime_t>& moment,
+ const optional<commodity_t&>& in_terms_of)
{
- DEBUG("commodity.prices.add", "exchange: " << amount << " for " << cost);
- DEBUG("commodity.prices.add", "exchange: is-per-unit = " << is_per_unit);
-#if defined(DEBUG_ON)
- if (moment)
- DEBUG("commodity.prices.add", "exchange: moment = " << *moment);
- if (tag)
- DEBUG("commodity.prices.add", "exchange: tag = " << *tag);
-#endif
-
- commodity_t& commodity(amount.commodity());
-
- annotation_t * current_annotation = NULL;
- if (commodity.annotated)
- current_annotation = &as_annotated_commodity(commodity).details;
-
- amount_t per_unit_cost = (is_per_unit ? cost : cost / amount).abs();
-
- DEBUG("commodity.prices.add", "exchange: per-unit-cost = " << per_unit_cost);
-
- exchange(commodity, per_unit_cost, moment ? *moment : CURRENT_TIME());
-
- cost_breakdown_t breakdown;
- breakdown.final_cost = ! is_per_unit ? cost : cost * amount;
+ if (parent().get_quotes && ! has_flags(COMMODITY_NOMARKET)) {
+ bool exceeds_leeway = true;
- DEBUG("commodity.prices.add",
- "exchange: final-cost = " << breakdown.final_cost);
-
- if (current_annotation && current_annotation->price)
- breakdown.basis_cost
- = (*current_annotation->price * amount).unrounded();
- else
- breakdown.basis_cost = breakdown.final_cost;
-
- DEBUG("commodity.prices.add",
- "exchange: basis-cost = " << breakdown.basis_cost);
-
- annotation_t annotation(per_unit_cost, moment ?
- moment->date() : optional<date_t>(), tag);
-
- annotation.add_flags(ANNOTATION_PRICE_CALCULATED);
- if (moment)
- annotation.add_flags(ANNOTATION_DATE_CALCULATED);
- if (tag)
- annotation.add_flags(ANNOTATION_TAG_CALCULATED);
-
- breakdown.amount = amount_t(amount, annotation);
+ if (point) {
+ time_duration_t::sec_type seconds_diff;
+ if (moment) {
+ seconds_diff = (*moment - point->when).total_seconds();
+ DEBUG("commodity.download", "moment = " << *moment);
+ DEBUG("commodity.download", "slip.moment = " << seconds_diff);
+ } else {
+ seconds_diff = (CURRENT_TIME() - point->when).total_seconds();
+ DEBUG("commodity.download", "slip.now = " << seconds_diff);
+ }
- DEBUG("commodity.prices.add",
- "exchange: amount = " << breakdown.amount);
+ DEBUG("commodity.download", "leeway = " << parent().quote_leeway);
+ if (seconds_diff < parent().quote_leeway)
+ exceeds_leeway = false;
+ }
- return breakdown;
+ if (exceeds_leeway) {
+ DEBUG("commodity.download",
+ "attempting to download a more current quote...");
+ if (optional<price_point_t> quote =
+ parent().get_commodity_quote(*this, in_terms_of)) {
+ if (! in_terms_of ||
+ (quote->price.has_commodity() &&
+ quote->price.commodity() == *in_terms_of))
+ return quote;
+ }
+ }
+ }
+ return point;
}
commodity_t::operator bool() const
@@ -499,14 +460,14 @@ void commodity_t::parse_symbol(std::istream& in, string& symbol)
{
// Invalid commodity characters:
// SPACE, TAB, NEWLINE, RETURN
- // 0-9 . , ; - + * / ^ ? : & | ! = %
+ // 0-9 . , ; - + * / ^ ? : & | ! =
// < > { } [ ] ( ) @
static int invalid_chars[256] = {
/* 0 1 2 3 4 5 6 7 8 9 a b c d e f */
/* 00 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0,
/* 10 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- /* 20 */ 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1,
+ /* 20 */ 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1,
/* 30 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
/* 40 */ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* 50 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0,
@@ -635,159 +596,6 @@ bool commodity_t::valid() const
return true;
}
-void annotation_t::parse(std::istream& in)
-{
- do {
- istream_pos_type pos = in.tellg();
-
- char buf[256];
- char c = peek_next_nonws(in);
- if (c == '{') {
- if (price)
- throw_(amount_error, _("Commodity specifies more than one price"));
-
- in.get(c);
- c = peek_next_nonws(in);
- if (c == '=') {
- in.get(c);
- add_flags(ANNOTATION_PRICE_FIXATED);
- }
-
- READ_INTO(in, buf, 255, c, c != '}');
- if (c == '}')
- in.get(c);
- else
- throw_(amount_error, _("Commodity price lacks closing brace"));
-
- amount_t temp;
- temp.parse(buf, amount_t::PARSE_NO_MIGRATE);
-
- DEBUG("commodity.annotations", "Parsed annotation price: " << temp);
-
- // Since this price will maintain its own precision, make sure
- // it is at least as large as the base commodity, since the user
- // may have only specified {$1} or something similar.
-
- if (temp.has_commodity() &&
- temp.precision() > temp.commodity().precision())
- temp = temp.rounded(); // no need to retain individual precision
-
- price = temp;
- }
- else if (c == '[') {
- if (date)
- throw_(amount_error, _("Commodity specifies more than one date"));
-
- in.get(c);
- READ_INTO(in, buf, 255, c, c != ']');
- if (c == ']')
- in.get(c);
- else
- throw_(amount_error, _("Commodity date lacks closing bracket"));
-
- date = parse_date(buf);
- }
- else if (c == '(') {
- if (tag)
- throw_(amount_error, _("Commodity specifies more than one tag"));
-
- in.get(c);
- READ_INTO(in, buf, 255, c, c != ')');
- if (c == ')')
- in.get(c);
- else
- throw_(amount_error, _("Commodity tag lacks closing parenthesis"));
-
- tag = buf;
- }
- else {
- in.clear();
- in.seekg(pos, std::ios::beg);
- break;
- }
- } while (true);
-
-#if defined(DEBUG_ON)
- if (SHOW_DEBUG("amounts.commodities") && *this) {
- DEBUG("amounts.commodities",
- "Parsed commodity annotations: " << std::endl << *this);
- }
-#endif
-}
-
-bool annotated_commodity_t::operator==(const commodity_t& comm) const
-{
- // If the base commodities don't match, the game's up.
- if (base != comm.base)
- return false;
-
- assert(annotated);
- if (! comm.annotated)
- return false;
-
- if (details != as_annotated_commodity(comm).details)
- return false;
-
- return true;
-}
-
-commodity_t&
-annotated_commodity_t::strip_annotations(const keep_details_t& what_to_keep)
-{
- DEBUG("commodity.annotated.strip",
- "Reducing commodity " << *this << std::endl
- << " keep price " << what_to_keep.keep_price << " "
- << " keep date " << what_to_keep.keep_date << " "
- << " keep tag " << what_to_keep.keep_tag);
-
- commodity_t * new_comm;
-
- bool keep_price = (what_to_keep.keep_price &&
- (! what_to_keep.only_actuals ||
- ! details.has_flags(ANNOTATION_PRICE_CALCULATED)));
- bool keep_date = (what_to_keep.keep_date &&
- (! what_to_keep.only_actuals ||
- ! details.has_flags(ANNOTATION_DATE_CALCULATED)));
- bool keep_tag = (what_to_keep.keep_tag &&
- (! what_to_keep.only_actuals ||
- ! details.has_flags(ANNOTATION_TAG_CALCULATED)));
-
- if ((keep_price && details.price) ||
- (keep_date && details.date) ||
- (keep_tag && details.tag))
- {
- new_comm = parent().find_or_create
- (referent(), annotation_t(keep_price ? details.price : none,
- keep_date ? details.date : none,
- keep_tag ? details.tag : none));
- } else {
- new_comm = parent().find_or_create(base_symbol());
- }
-
- assert(new_comm);
- return *new_comm;
-}
-
-void annotated_commodity_t::write_annotations(std::ostream& out) const
-{
- details.print(out, parent().keep_base);
-}
-
-void annotation_t::print(std::ostream& out, bool keep_base) const
-{
- if (price)
- out << " {"
- << (has_flags(ANNOTATION_PRICE_FIXATED) ? "=" : "")
- << (keep_base ? *price : price->unreduced()).rounded()
- << '}';
-
- if (date)
- out << " [" << format_date(*date, string("%Y/%m/%d")) << ']';
-
- if (tag)
- out << " (" << *tag << ')';
-}
-
bool compare_amount_commodities::operator()(const amount_t * left,
const amount_t * right) const
{
@@ -855,190 +663,4 @@ bool compare_amount_commodities::operator()(const amount_t * left,
}
}
-commodity_pool_t::commodity_pool_t()
- : default_commodity(NULL), keep_base(false)
-{
- TRACE_CTOR(commodity_pool_t, "");
- null_commodity = create("");
- null_commodity->add_flags(COMMODITY_BUILTIN | COMMODITY_NOMARKET);
-}
-
-commodity_t * commodity_pool_t::create(const string& symbol)
-{
- shared_ptr<commodity_t::base_t>
- base_commodity(new commodity_t::base_t(symbol));
- std::auto_ptr<commodity_t> commodity(new commodity_t(this, base_commodity));
-
- DEBUG("amounts.commodities", "Creating base commodity " << symbol);
-
- // Create the "qualified symbol" version of this commodity's symbol
- if (commodity_t::symbol_needs_quotes(symbol)) {
- commodity->qualified_symbol = "\"";
- *commodity->qualified_symbol += symbol;
- *commodity->qualified_symbol += "\"";
- }
-
- DEBUG("amounts.commodities",
- "Creating commodity '" << commodity->symbol() << "'");
-
- std::pair<commodities_map::iterator, bool> result
- = commodities.insert(commodities_map::value_type(commodity->mapping_key(),
- commodity.get()));
- assert(result.second);
-
- return commodity.release();
-}
-
-commodity_t * commodity_pool_t::find_or_create(const string& symbol)
-{
- DEBUG("amounts.commodities", "Find-or-create commodity " << symbol);
-
- commodity_t * commodity = find(symbol);
- if (commodity)
- return commodity;
- return create(symbol);
-}
-
-commodity_t * commodity_pool_t::find(const string& symbol)
-{
- DEBUG("amounts.commodities", "Find commodity " << symbol);
-
- commodities_map::const_iterator i = commodities.find(symbol);
- if (i != commodities.end())
- return (*i).second;
- return NULL;
-}
-
-commodity_t *
-commodity_pool_t::create(const string& symbol, const annotation_t& details)
-{
- commodity_t * new_comm = create(symbol);
- if (! new_comm)
- return NULL;
-
- if (details)
- return find_or_create(*new_comm, details);
- else
- return new_comm;
-}
-
-namespace {
- string make_qualified_name(const commodity_t& comm,
- const annotation_t& details)
- {
- assert(details);
-
- if (details.price && details.price->sign() < 0)
- throw_(amount_error, _("A commodity's price may not be negative"));
-
- std::ostringstream name;
- comm.print(name);
- details.print(name, comm.parent().keep_base);
-
- DEBUG("amounts.commodities", "make_qualified_name for "
- << *comm.qualified_symbol << std::endl << details);
- DEBUG("amounts.commodities", "qualified_name is " << name.str());
-
- return name.str();
- }
-}
-
-commodity_t *
-commodity_pool_t::find(const string& symbol, const annotation_t& details)
-{
- commodity_t * comm = find(symbol);
- if (! comm)
- return NULL;
-
- if (details) {
- string name = make_qualified_name(*comm, details);
-
- if (commodity_t * ann_comm = find(name)) {
- assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details);
- return ann_comm;
- }
- return NULL;
- } else {
- return comm;
- }
-}
-
-commodity_t *
-commodity_pool_t::find_or_create(const string& symbol,
- const annotation_t& details)
-{
- commodity_t * comm = find(symbol);
- if (! comm)
- return NULL;
-
- if (details)
- return find_or_create(*comm, details);
- else
- return comm;
-}
-
-commodity_t *
-commodity_pool_t::create(commodity_t& comm,
- const annotation_t& details,
- const string& mapping_key)
-{
- assert(comm);
- assert(details);
- assert(! mapping_key.empty());
-
- std::auto_ptr<commodity_t> commodity
- (new annotated_commodity_t(&comm, details));
-
- commodity->qualified_symbol = comm.symbol();
- assert(! commodity->qualified_symbol->empty());
-
- DEBUG("amounts.commodities", "Creating annotated commodity "
- << "symbol " << commodity->symbol()
- << " key " << mapping_key << std::endl << details);
-
- // Add the fully annotated name to the map, so that this symbol may
- // quickly be found again.
- commodity->mapping_key_ = mapping_key;
-
- std::pair<commodities_map::iterator, bool> result
- = commodities.insert(commodities_map::value_type(mapping_key,
- commodity.get()));
- assert(result.second);
-
- return commodity.release();
-}
-
-commodity_t * commodity_pool_t::find_or_create(commodity_t& comm,
- const annotation_t& details)
-{
- assert(comm);
- assert(details);
-
- string name = make_qualified_name(comm, details);
- assert(! name.empty());
-
- if (commodity_t * ann_comm = find(name)) {
- assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details);
- return ann_comm;
- }
- return create(comm, details, name);
-}
-
-void commodity_pool_t::parse_commodity_price(char * optarg)
-{
- char * equals = std::strchr(optarg, '=');
- if (! equals)
- return;
-
- optarg = skip_ws(optarg);
- while (equals > optarg && std::isspace(*(equals - 1)))
- equals--;
-
- std::string symbol(optarg, 0, equals - optarg);
- amount_t price(equals + 1);
-
- if (commodity_t * commodity = find_or_create(symbol))
- commodity->add_price(CURRENT_TIME(), price);
-}
-
} // namespace ledger
diff --git a/src/commodity.h b/src/commodity.h
index 8121199a..5d73f4e8 100644
--- a/src/commodity.h
+++ b/src/commodity.h
@@ -86,7 +86,6 @@ public:
struct history_t
{
history_map prices;
- ptime last_lookup;
void add_price(commodity_t& source,
const datetime_t& date,
@@ -310,24 +309,10 @@ public:
return none;
}
- // Methods to exchange one commodity for another, while recording the
- // factored price.
-
- static void exchange(commodity_t& commodity,
- const amount_t& per_unit_cost,
- const datetime_t& moment);
-
- struct cost_breakdown_t {
- amount_t amount;
- amount_t final_cost;
- amount_t basis_cost;
- };
-
- static cost_breakdown_t exchange(const amount_t& amount,
- const amount_t& cost,
- const bool is_per_unit = false,
- const optional<datetime_t>& moment = none,
- const optional<string>& tag = none);
+ optional<price_point_t>
+ check_for_updated_price(const optional<price_point_t>& point,
+ const optional<datetime_t>& moment,
+ const optional<commodity_t&>& in_terms_of);
// Methods related to parsing, reading, writing, etc., the commodity
// itself.
@@ -357,219 +342,10 @@ inline std::ostream& operator<<(std::ostream& out, const commodity_t& comm) {
*
* Long.
*/
-struct annotation_t : public supports_flags<>,
- public equality_comparable<annotation_t>
-{
-#define ANNOTATION_PRICE_CALCULATED 0x01
-#define ANNOTATION_PRICE_FIXATED 0x02
-#define ANNOTATION_DATE_CALCULATED 0x04
-#define ANNOTATION_TAG_CALCULATED 0x08
-
- optional<amount_t> price;
- optional<date_t> date;
- optional<string> tag;
-
- explicit annotation_t(const optional<amount_t>& _price = none,
- const optional<date_t>& _date = none,
- const optional<string>& _tag = none)
- : supports_flags<>(), price(_price), date(_date), tag(_tag) {
- TRACE_CTOR(annotation_t, "const optional<amount_t>& + date_t + string");
- }
- annotation_t(const annotation_t& other)
- : supports_flags<>(other.flags()),
- price(other.price), date(other.date), tag(other.tag) {
- TRACE_CTOR(annotation_t, "copy");
- }
- ~annotation_t() {
- TRACE_DTOR(annotation_t);
- }
-
- operator bool() const {
- return price || date || tag;
- }
-
- bool operator==(const annotation_t& rhs) const {
- return (price == rhs.price &&
- date == rhs.date &&
- tag == rhs.tag);
- }
-
- void parse(std::istream& in);
-
- void print(std::ostream& out, bool keep_base = false) const;
-
- bool valid() const {
- assert(*this);
- return true;
- }
-};
-
-struct keep_details_t
-{
- bool keep_price;
- bool keep_date;
- bool keep_tag;
- bool only_actuals;
-
- explicit keep_details_t(bool _keep_price = false,
- bool _keep_date = false,
- bool _keep_tag = false,
- bool _only_actuals = false)
- : keep_price(_keep_price),
- keep_date(_keep_date),
- keep_tag(_keep_tag),
- only_actuals(_only_actuals)
- {
- TRACE_CTOR(keep_details_t, "bool, bool, bool, bool");
- }
- keep_details_t(const keep_details_t& other)
- : keep_price(other.keep_price), keep_date(other.keep_date),
- keep_tag(other.keep_tag), only_actuals(other.only_actuals) {
- TRACE_CTOR(keep_details_t, "copy");
- }
- ~keep_details_t() throw() {
- TRACE_DTOR(keep_details_t);
- }
-
- bool keep_all() const {
- return keep_price && keep_date && keep_tag && ! only_actuals;
- }
- bool keep_all(const commodity_t& comm) const {
- return (! comm.annotated ||
- (keep_price && keep_date && keep_tag && ! only_actuals));
- }
-
- bool keep_any() const {
- return keep_price || keep_date || keep_tag;
- }
- bool keep_any(const commodity_t& comm) const {
- return comm.annotated && (keep_price || keep_date || keep_tag);
- }
-};
-
-inline std::ostream& operator<<(std::ostream& out,
- const annotation_t& details) {
- details.print(out);
- return out;
-}
-
-/**
- * @brief Brief
- *
- * Long.
- */
-class annotated_commodity_t
- : public commodity_t,
- public equality_comparable<annotated_commodity_t,
- equality_comparable2<annotated_commodity_t, commodity_t,
- noncopyable> >
-{
-public:
- commodity_t * ptr;
- annotation_t details;
-
- explicit annotated_commodity_t(commodity_t * _ptr,
- const annotation_t& _details)
- : commodity_t(_ptr->parent_, _ptr->base), ptr(_ptr), details(_details) {
- TRACE_CTOR(annotated_commodity_t, "");
- annotated = true;
- }
- virtual ~annotated_commodity_t() {
- TRACE_DTOR(annotated_commodity_t);
- }
-
- virtual bool operator==(const commodity_t& comm) const;
- virtual bool operator==(const annotated_commodity_t& comm) const {
- return *this == static_cast<const commodity_t&>(comm);
- }
-
- virtual commodity_t& referent() {
- return *ptr;
- }
- virtual const commodity_t& referent() const {
- return *ptr;
- }
-
- virtual commodity_t& strip_annotations(const keep_details_t& what_to_keep);
- virtual void write_annotations(std::ostream& out) const;
-};
-
-inline annotated_commodity_t&
-as_annotated_commodity(commodity_t& commodity) {
- return downcast<annotated_commodity_t>(commodity);
-}
-inline const annotated_commodity_t&
-as_annotated_commodity(const commodity_t& commodity) {
- return downcast<const annotated_commodity_t>(commodity);
-}
-
-
-/**
- * @brief Brief
- *
- * Long.
- */
struct compare_amount_commodities {
bool operator()(const amount_t * left, const amount_t * right) const;
};
-/**
- * @brief Brief
- *
- * Long.
- */
-class commodity_pool_t : public noncopyable
-{
- /**
- * The commodities collection in commodity_pool_t maintains pointers to all
- * the commodities which have ever been created by the user, whether
- * explicitly by calling the create methods of commodity_pool_t, or
- * implicitly by parsing a commoditized amount.
- */
- typedef std::map<string, commodity_t *> commodities_map;
-
-public:
- commodities_map commodities;
-
- commodity_t * null_commodity;
- commodity_t * default_commodity;
-
- bool keep_base;
-
-public:
- boost::function<optional<amount_t>
- (commodity_t& commodity,
- const optional<datetime_t>& date,
- const optional<datetime_t>& moment,
- const optional<datetime_t>& last)> get_quote;
-
- explicit commodity_pool_t();
-
- ~commodity_pool_t() {
- TRACE_DTOR(commodity_pool_t);
- foreach (commodities_map::value_type pair, commodities)
- checked_delete(pair.second);
- }
-
- commodity_t * create(const string& symbol);
- commodity_t * find(const string& name);
- commodity_t * find_or_create(const string& symbol);
-
- commodity_t * create(const string& symbol, const annotation_t& details);
- commodity_t * find(const string& symbol, const annotation_t& details);
- commodity_t * find_or_create(const string& symbol,
- const annotation_t& details);
-
- commodity_t * create(commodity_t& comm,
- const annotation_t& details,
- const string& mapping_key);
-
- commodity_t * find_or_create(commodity_t& comm,
- const annotation_t& details);
-
- void parse_commodity_price(char * optarg);
-};
-
} // namespace ledger
#endif // _COMMODITY_H
diff --git a/src/derive.cc b/src/derive.cc
index 98c50fe1..2fe5754f 100644
--- a/src/derive.cc
+++ b/src/derive.cc
@@ -98,7 +98,7 @@ namespace {
foreach (const post_template_t& post, posts) {
straccstream accum;
out << std::endl
- << ACCUM(accum << _("[Posting \"%1\"]_")
+ << ACCUM(accum << _("[Posting \"%1\"]")
<< (post.from ? _("from") : _("to")))
<< std::endl;
@@ -240,28 +240,30 @@ namespace {
report_t& report)
{
if (tmpl.payee_mask.empty())
- throw std::runtime_error(_("'xact' command requires at least a payee"));
-
- xact_t * matching = NULL;
- journal_t& journal(*report.session.journal.get());
+ throw std::runtime_error(_("xact' command requires at least a payee"));
+ xact_t * matching = NULL;
+ journal_t& journal(*report.session.journal.get());
std::auto_ptr<xact_t> added(new xact_t);
- xacts_list::reverse_iterator j;
-
- for (j = journal.xacts.rbegin();
+ for (xacts_list::reverse_iterator j = journal.xacts.rbegin();
j != journal.xacts.rend();
j++) {
if (tmpl.payee_mask.match((*j)->payee)) {
matching = *j;
+ DEBUG("derive.xact",
+ "Found payee match: transaction on line " << (*j)->beg_line);
break;
}
}
- if (! tmpl.date)
+ if (! tmpl.date) {
added->_date = CURRENT_DATE();
- else
+ DEBUG("derive.xact", "Setting date to current date");
+ } else {
added->_date = tmpl.date;
+ DEBUG("derive.xact", "Setting date to template date: " << *tmpl.date);
+ }
added->set_state(item_t::UNCLEARED);
@@ -269,17 +271,32 @@ namespace {
added->payee = matching->payee;
added->code = matching->code;
added->note = matching->note;
+
+#if defined(DEBUG_ON)
+ DEBUG("derive.xact", "Setting payee from match: " << added->payee);
+ if (added->code)
+ DEBUG("derive.xact", "Setting code from match: " << *added->code);
+ if (added->note)
+ DEBUG("derive.xact", "Setting note from match: " << *added->note);
+#endif
} else {
added->payee = tmpl.payee_mask.expr.str();
+ DEBUG("derive.xact", "Setting payee from template: " << added->payee);
}
- if (tmpl.code)
+ if (tmpl.code) {
added->code = tmpl.code;
- if (tmpl.note)
+ DEBUG("derive.xact", "Now setting code from template: " << *added->code);
+ }
+ if (tmpl.note) {
added->note = tmpl.note;
+ DEBUG("derive.xact", "Now setting note from template: " << *added->note);
+ }
if (tmpl.posts.empty()) {
if (matching) {
+ DEBUG("derive.xact", "Template had no postings, copying from match");
+
foreach (post_t * post, matching->posts) {
added->add_post(new post_t(*post));
added->posts.back()->set_state(item_t::UNCLEARED);
@@ -290,9 +307,12 @@ namespace {
<< tmpl.payee_mask);
}
} else {
+ DEBUG("derive.xact", "Template had postings");
+
bool any_post_has_amount = false;
foreach (xact_template_t::post_template_t& post, tmpl.posts) {
if (post.amount) {
+ DEBUG("derive.xact", " and at least one has an amount specified");
any_post_has_amount = true;
break;
}
@@ -305,90 +325,149 @@ namespace {
if (matching) {
if (post.account_mask) {
+ DEBUG("derive.xact",
+ "Looking for matching posting based on account mask");
+
foreach (post_t * x, matching->posts) {
if (post.account_mask->match(x->account->fullname())) {
new_post.reset(new post_t(*x));
+ DEBUG("derive.xact",
+ "Founding posting from line " << x->beg_line);
break;
}
}
} else {
- if (post.from)
- new_post.reset(new post_t(*matching->posts.back()));
- else
- new_post.reset(new post_t(*matching->posts.front()));
+ if (post.from) {
+ for (posts_list::reverse_iterator j = matching->posts.rbegin();
+ j != matching->posts.rend();
+ j++) {
+ if ((*j)->must_balance()) {
+ new_post.reset(new post_t(**j));
+ DEBUG("derive.xact",
+ "Copied last real posting from matching");
+ break;
+ }
+ }
+ } else {
+ for (posts_list::iterator j = matching->posts.begin();
+ j != matching->posts.end();
+ j++) {
+ if ((*j)->must_balance()) {
+ new_post.reset(new post_t(**j));
+ DEBUG("derive.xact",
+ "Copied first real posting from matching");
+ break;
+ }
+ }
+ }
}
}
- if (! new_post.get())
+ if (! new_post.get()) {
new_post.reset(new post_t);
+ DEBUG("derive.xact", "New posting was NULL, creating a blank one");
+ }
if (! new_post->account) {
+ DEBUG("derive.xact", "New posting still needs an account");
+
if (post.account_mask) {
+ DEBUG("derive.xact", "The template has an account mask");
+
account_t * acct = NULL;
- if (! acct)
+ if (! acct) {
acct = journal.find_account_re(post.account_mask->expr.str());
- if (! acct)
+#if defined(DEBUG_ON)
+ if (acct)
+ DEBUG("derive.xact", "Found account as a regular expression");
+#endif
+ }
+ if (! acct) {
acct = journal.find_account(post.account_mask->expr.str());
+#if defined(DEBUG_ON)
+ if (acct)
+ DEBUG("derive.xact", "Found (or created) account by name");
+#endif
+ }
// Find out the default commodity to use by looking at the last
// commodity used in that account
- xacts_list::reverse_iterator j;
-
- for (j = journal.xacts.rbegin();
+ for (xacts_list::reverse_iterator j = journal.xacts.rbegin();
j != journal.xacts.rend();
j++) {
foreach (post_t * x, (*j)->posts) {
if (x->account == acct && ! x->amount.is_null()) {
new_post.reset(new post_t(*x));
+ DEBUG("derive.xact",
+ "Found account in journal postings, setting new posting");
break;
}
}
}
- if (! new_post.get())
- new_post.reset(new post_t);
new_post->account = acct;
+ DEBUG("derive.xact",
+ "Set new posting's account to: " << acct->fullname());
} else {
-
- if (post.from)
+ if (post.from) {
new_post->account = journal.find_account(_("Liabilities:Unknown"));
- else
+ DEBUG("derive.xact",
+ "Set new posting's account to: Liabilities:Unknown");
+ } else {
new_post->account = journal.find_account(_("Expenses:Unknown"));
+ DEBUG("derive.xact",
+ "Set new posting's account to: Expenses:Unknown");
+ }
}
}
if (new_post.get() && ! new_post->amount.is_null()) {
found_commodity = &new_post->amount.commodity();
- if (any_post_has_amount)
+ if (any_post_has_amount) {
new_post->amount = amount_t();
- else
+ DEBUG("derive.xact", "New posting has an amount, but we cleared it");
+ } else {
any_post_has_amount = true;
+ DEBUG("derive.xact", "New posting has an amount, and we're using it");
+ }
}
if (post.amount) {
new_post->amount = *post.amount;
- if (post.from)
+ DEBUG("derive.xact", "Copied over posting amount");
+
+ if (post.from) {
new_post->amount.in_place_negate();
+ DEBUG("derive.xact", "Negated new posting amount");
+ }
}
if (found_commodity &&
! new_post->amount.is_null() &&
! new_post->amount.has_commodity()) {
new_post->amount.set_commodity(*found_commodity);
+ DEBUG("derive.xact", "Set posting amount commodity to: "
+ << new_post->amount.commodity());
+
new_post->amount = new_post->amount.rounded();
+ DEBUG("derive.xact",
+ "Rounded posting amount to: " << new_post->amount);
}
added->add_post(new_post.release());
added->posts.back()->set_state(item_t::UNCLEARED);
+
+ DEBUG("derive.xact", "Added new posting to derived entry");
}
}
if (! journal.xact_finalize_hooks.run_hooks(*added.get(), false) ||
! added->finalize() ||
- ! journal.xact_finalize_hooks.run_hooks(*added.get(), true))
+ ! journal.xact_finalize_hooks.run_hooks(*added.get(), true)) {
throw_(std::runtime_error,
_("Failed to finalize derived transaction (check commodities)"));
+ }
return added.release();
}
@@ -423,7 +502,8 @@ value_t xact_command(call_scope_t& args)
xact_template_t tmpl = args_to_xact_template(begin, end);
std::auto_ptr<xact_t> new_xact(derive_xact_from_template(tmpl, report));
- report.HANDLER(limit_).on("actual"); // jww (2009-02-27): make this more general
+ // jww (2009-02-27): make this more general
+ report.HANDLER(limit_).on(string("#xact"), "actual");
report.xact_report(post_handler_ptr
(new format_posts(report,
diff --git a/src/filters.cc b/src/filters.cc
index bb2681c6..967e0843 100644
--- a/src/filters.cc
+++ b/src/filters.cc
@@ -142,6 +142,7 @@ namespace {
buf.width(8);
buf.fill('0');
buf << std::hex << message_digest[i];
+ break; // only output the first dword
}
return buf.str();
}
@@ -175,13 +176,31 @@ void anonymize_posts::operator()(post_t& post)
post_t& temp = post_temps.back();
temp.xact = &xact;
- sha.Reset();
- sha << post.account->fullname().c_str();
- sha.Result(message_digest);
+ std::list<string> account_names;
+ account_t * new_account = NULL;
+
+ for (account_t * acct = post.account;
+ acct;
+ acct = acct->parent) {
+ if (! acct->parent) {
+ new_account = acct;
+ break;
+ }
+
+ sha.Reset();
+ sha << acct->name.c_str();
+ sha.Result(message_digest);
+
+ account_names.push_front(to_hex(message_digest));
+ }
+ assert(new_account);
+
+ foreach (const string& name, account_names)
+ new_account = new_account->find_account(name);
temp.copy_details(post);
- temp.account = post.xact->journal->find_account(to_hex(message_digest));
+ temp.account = new_account;
temp.note = none;
temp.add_flags(ITEM_TEMP);
@@ -388,6 +407,15 @@ void related_posts::flush()
item_handler<post_t>::flush();
}
+void changed_value_posts::flush()
+{
+ if (last_post && last_post->date() <= report.terminus.date()) {
+ output_revaluation(last_post, report.terminus.date());
+ last_post = NULL;
+ }
+ item_handler<post_t>::flush();
+}
+
void changed_value_posts::output_revaluation(post_t * post, const date_t& date)
{
if (is_valid(date))
@@ -875,8 +903,7 @@ void forecast_posts::flush()
item_handler<post_t>::operator()(temp);
- if (temp.has_xdata() &&
- temp.xdata().has_flags(POST_EXT_MATCHES)) {
+ if (temp.has_xdata() && temp.xdata().has_flags(POST_EXT_MATCHES)) {
bind_scope_t bound_scope(context, temp);
if (! pred(bound_scope))
break;
diff --git a/src/filters.h b/src/filters.h
index 14b97c23..050e3dcf 100644
--- a/src/filters.h
+++ b/src/filters.h
@@ -468,13 +468,7 @@ public:
clear_xacts_posts(xact_temps);
}
- virtual void flush() {
- if (last_post && last_post->date() <= CURRENT_DATE()) {
- output_revaluation(last_post, CURRENT_DATE());
- last_post = NULL;
- }
- item_handler<post_t>::flush();
- }
+ virtual void flush();
void output_revaluation(post_t * post, const date_t& current);
void output_rounding(post_t * post);
diff --git a/src/format.cc b/src/format.cc
index 4066cabd..ecac1133 100644
--- a/src/format.cc
+++ b/src/format.cc
@@ -38,6 +38,9 @@
namespace ledger {
+format_t::elision_style_t format_t::default_style = TRUNCATE_TRAILING;
+bool format_t::default_style_changed = false;
+
void format_t::element_t::dump(std::ostream& out) const
{
out << "Element: ";
@@ -342,7 +345,8 @@ void format_t::format(std::ostream& out_str, scope_t& scope)
}
DEBUG("format.expr", "value = (" << value << ")");
- value.print(out, elem->min_width);
+ value.print(out, elem->min_width, -1,
+ ! elem->has_flags(ELEMENT_ALIGN_LEFT));
}
catch (const calc_error&) {
add_error_context(_("While calculating format expression:"));
@@ -386,22 +390,22 @@ string format_t::truncate(const unistring& ustr, std::size_t width,
std::ostringstream buf;
- elision_style_t style = TRUNCATE_TRAILING;
- if (account_abbrev_length > 0)
+ elision_style_t style = default_style;
+ if (account_abbrev_length > 0 && ! default_style_changed)
style = ABBREVIATE;
switch (style) {
case TRUNCATE_LEADING:
// This method truncates at the beginning.
- buf << ".." << ustr.extract(len - width, width);
+ buf << ".." << ustr.extract(len - (width - 2), width - 2);
break;
case TRUNCATE_MIDDLE:
// This method truncates in the middle.
- buf << ustr.extract(0, width / 2)
+ buf << ustr.extract(0, (width - 2) / 2)
<< ".."
- << ustr.extract(len - (width / 2 + width % 2),
- width / 2 + width % 2);
+ << ustr.extract(len - ((width - 2) / 2 + (width - 2) % 2),
+ (width - 2) / 2 + (width - 2) % 2);
break;
case ABBREVIATE:
diff --git a/src/format.h b/src/format.h
index 13dd0876..03ed28c7 100644
--- a/src/format.h
+++ b/src/format.h
@@ -107,12 +107,14 @@ class format_t : public noncopyable
scoped_ptr<element_t> elements;
public:
- enum elision_style_t {
+ static enum elision_style_t {
TRUNCATE_TRAILING,
TRUNCATE_MIDDLE,
TRUNCATE_LEADING,
ABBREVIATE
- };
+ } default_style;
+
+ static bool default_style_changed;
private:
static element_t * parse_elements(const string& fmt);
diff --git a/src/global.cc b/src/global.cc
index a258d0bb..24716fae 100644
--- a/src/global.cc
+++ b/src/global.cc
@@ -39,9 +39,12 @@
#endif
#include "item.h"
#include "journal.h"
+#include "pool.h"
namespace ledger {
+static bool args_only = false;
+
global_scope_t::global_scope_t(char ** envp)
{
TRACE_CTOR(global_scope_t, "");
@@ -74,11 +77,13 @@ global_scope_t::global_scope_t(char ** envp)
// Before processing command-line options, we must notify the session object
// that such options are beginning, since options like -f cause a complete
// override of files found anywhere else.
- if (! HANDLED(args_only)) {
+ if (! args_only) {
session().set_flush_on_next_data_file(true);
read_environment_settings(envp);
session().set_flush_on_next_data_file(true);
read_init();
+ } else {
+ session().HANDLER(price_db_).off();
}
}
@@ -138,7 +143,7 @@ void global_scope_t::report_error(const std::exception& err)
string context = error_context();
if (! context.empty())
std::cerr << context << std::endl;
-
+
std::cerr << _("Error: ") << err.what() << std::endl;
} else {
caught_signal = NONE_CAUGHT;
@@ -179,8 +184,6 @@ void global_scope_t::execute_command(strings_list args, bool at_repl)
if (bool(command = look_for_precommand(bound_scope, verb)))
is_precommand = true;
- else if (! bool(command = look_for_command(bound_scope, verb)))
- throw_(std::logic_error, _("Unrecognized command '%1'") << verb);
// If it is not a pre-command, then parse the user's ledger data at this
// time if not done alreday (i.e., if not at a REPL). Then patch up the
@@ -189,7 +192,11 @@ void global_scope_t::execute_command(strings_list args, bool at_repl)
if (! is_precommand) {
if (! at_repl)
session().read_journal_files();
+
normalize_report_options(verb);
+
+ if (! bool(command = look_for_command(bound_scope, verb)))
+ throw_(std::logic_error, _("Unrecognized command '%1'") << verb);
}
// Create the output stream (it might be a file, the console or a PAGER
@@ -204,6 +211,12 @@ void global_scope_t::execute_command(strings_list args, bool at_repl)
optional<path>(path(report().HANDLER(pager_).str())) :
optional<path>());
+ // Now that the output stream is initialized, report the options that will
+ // participate in this report, if the user specified --options
+
+ if (HANDLED(options))
+ report_options(report(), report().output_stream);
+
// Create an argument scope containing the report command's arguments, and
// then invoke the command. The bound scope causes lookups to happen
// first in the global scope, and then in the report scope.
@@ -239,6 +252,29 @@ int global_scope_t::execute_command_wrapper(strings_list args, bool at_repl)
return status;
}
+void global_scope_t::report_options(report_t& report, std::ostream& out)
+{
+ out << "<=============================================================================>"
+ << std::endl;
+ out << "[Global scope options]" << std::endl;
+
+ HANDLER(args_only).report(out);
+ HANDLER(debug_).report(out);
+ HANDLER(init_file_).report(out);
+ HANDLER(script_).report(out);
+ HANDLER(trace_).report(out);
+ HANDLER(verbose).report(out);
+ HANDLER(verify).report(out);
+
+ out << std::endl << "[Session scope options]" << std::endl;
+ report.session.report_options(out);
+
+ out << std::endl << "[Report scope options]" << std::endl;
+ report.report_options(out);
+ out << "<=============================================================================>"
+ << std::endl;
+}
+
option_t<global_scope_t> * global_scope_t::lookup_option(const char * p)
{
switch (*p) {
@@ -260,6 +296,9 @@ option_t<global_scope_t> * global_scope_t::lookup_option(const char * p)
case 'i':
OPT(init_file_);
break;
+ case 'o':
+ OPT(options);
+ break;
case 's':
OPT(script_);
break;
@@ -287,13 +326,13 @@ expr_t::ptr_op_t global_scope_t::lookup(const string& name)
break;
case 'p':
- if (WANT_PRECMD()) { p += PRECMD_PREFIX_LEN;
- switch (*p) {
+ if (WANT_PRECMD()) { const char * q = p + PRECMD_PREFIX_LEN;
+ switch (*q) {
case 'p':
- if (is_eq(p, "push"))
- MAKE_FUNCTOR(global_scope_t::push_command);
- else if (is_eq(p, "pop"))
- MAKE_FUNCTOR(global_scope_t::pop_command);
+ if (is_eq(q, "push"))
+ return MAKE_FUNCTOR(global_scope_t::push_command);
+ else if (is_eq(q, "pop"))
+ return MAKE_FUNCTOR(global_scope_t::pop_command);
break;
}
}
@@ -316,18 +355,18 @@ void global_scope_t::read_environment_settings(char * envp[])
if (const char * p = std::getenv("LEDGER")) {
if (! std::getenv("LEDGER_FILE"))
- process_option("file", report(), p, "LEDGER");
+ process_option("environ", "file", report(), p, "LEDGER");
}
if (const char * p = std::getenv("LEDGER_INIT")) {
if (! std::getenv("LEDGER_INIT_FILE"))
- process_option("init-file", report(), p, "LEDGER_INIT");
+ process_option("environ", "init-file", report(), p, "LEDGER_INIT");
}
if (const char * p = std::getenv("PRICE_HIST")) {
if (! std::getenv("LEDGER_PRICEDB"))
- process_option("price-db", report(), p, "PRICE_HIST");
+ process_option("environ", "price-db", report(), p, "PRICE_HIST");
}
if (const char * p = std::getenv("PRICE_EXP"))
- process_option("price-exp", report(), p, "PRICE_EXP");
+ process_option("environ", "price-exp", report(), p, "PRICE_EXP");
#endif
TRACE_FINISH(environment, 1);
@@ -379,9 +418,19 @@ void global_scope_t::normalize_report_options(const string& verb)
report_t& rep(report());
- // jww (2009-02-09): These global are a hack, but hard to avoid.
- item_t::use_effective_date = rep.HANDLED(effective);
- rep.session.commodity_pool->keep_base = rep.HANDLED(base);
+ // jww (2009-02-09): These globals are a hack, but hard to avoid.
+ item_t::use_effective_date = rep.HANDLED(effective);
+ rep.session.commodity_pool->keep_base = rep.HANDLED(base);
+ rep.session.commodity_pool->get_quotes = rep.session.HANDLED(download);
+
+ if (rep.session.HANDLED(price_exp_))
+ rep.session.commodity_pool->quote_leeway =
+ rep.session.HANDLER(price_exp_).value.as_long();
+
+ if (rep.session.HANDLED(price_db_))
+ rep.session.commodity_pool->price_db = rep.session.HANDLER(price_db_).str();
+ else
+ rep.session.commodity_pool->price_db = none;
if (rep.HANDLED(date_format_)) {
output_datetime_format = rep.HANDLER(date_format_).str() + " %H:%M:%S";
@@ -397,29 +446,75 @@ void global_scope_t::normalize_report_options(const string& verb)
// I might be able to do it with command objects, like register_t, which
// each know how to adjust the report based on its current option settings.
if (verb == "print" || verb == "xact" || verb == "dump") {
- rep.HANDLER(related).on_only();
- rep.HANDLER(related_all).on_only();
+ rep.HANDLER(related).on_only(string("?normalize"));
+ rep.HANDLER(related_all).on_only(string("?normalize"));
}
else if (verb == "equity") {
- rep.HANDLER(equity).on_only();
+ rep.HANDLER(equity).on_only(string("?normalize"));
}
else if (rep.HANDLED(related)) {
if (verb[0] == 'r') {
- rep.HANDLER(invert).on_only();
+ rep.HANDLER(invert).on_only(string("?normalize"));
} else {
- rep.HANDLER(subtotal).on_only();
- rep.HANDLER(related_all).on_only();
+ rep.HANDLER(subtotal).on_only(string("?normalize"));
+ rep.HANDLER(related_all).on_only(string("?normalize"));
}
}
+ if (verb == "print")
+ rep.HANDLER(limit_).on(string("?normalize"), "actual");
+
if (! rep.HANDLED(empty))
- rep.HANDLER(display_).on("amount|(!post&total)");
+ rep.HANDLER(display_).on(string("?normalize"), "amount|(!post&total)");
if (verb[0] != 'b' && verb[0] != 'r')
- rep.HANDLER(base).on_only();
+ rep.HANDLER(base).on_only(string("?normalize"));
+
+ // If a time period was specified with -p, check whether it also gave a
+ // begin and/or end to the report period (though these can be overridden
+ // using -b or -e). Then, if no _duration_ was specified (such as monthly),
+ // then ignore the period since the begin/end are the only interesting
+ // details.
+ if (rep.HANDLED(period_)) {
+ if (! rep.HANDLED(sort_all_))
+ rep.HANDLER(sort_xacts_).on_only(string("?normalize"));
+
+ date_interval_t interval(rep.HANDLER(period_).str());
+
+ if (! rep.HANDLED(begin_) && interval.start) {
+ string predicate =
+ "date>=[" + to_iso_extended_string(*interval.start) + "]";
+ rep.HANDLER(limit_).on(string("?normalize"), predicate);
+ }
+ if (! rep.HANDLED(end_) && interval.end) {
+ string predicate =
+ "date<[" + to_iso_extended_string(*interval.end) + "]";
+ rep.HANDLER(limit_).on(string("?normalize"), predicate);
+ }
+
+ if (! interval.duration)
+ rep.HANDLER(period_).off();
+ }
+
+ // If -j or -J were specified, set the appropriate format string now so as
+ // to avoid option ordering issues were we to have done it during the
+ // initial parsing of the options.
+ if (rep.HANDLED(amount_data)) {
+ rep.HANDLER(format_)
+ .on_with(string("?normalize"), rep.HANDLER(plot_amount_format_).value);
+ }
+ else if (rep.HANDLED(total_data)) {
+ rep.HANDLER(format_)
+ .on_with(string("?normalize"), rep.HANDLER(plot_total_format_).value);
+ }
- if (rep.HANDLED(period_) && ! rep.HANDLED(sort_all_))
- rep.HANDLER(sort_xacts_).on_only();
+ // If the --exchange (-X) option was used, parse out any final price
+ // settings that may be there.
+ if (rep.HANDLED(exchange_) &&
+ rep.HANDLER(exchange_).str().find('=') != string::npos) {
+ value_t(0L).exchange_commodities(rep.HANDLER(exchange_).str(), true,
+ rep.terminus);
+ }
long cols = 0;
if (rep.HANDLED(columns_))
@@ -465,15 +560,15 @@ void global_scope_t::normalize_report_options(const string& verb)
}
if (! rep.HANDLER(date_width_).specified)
- rep.HANDLER(date_width_).on_with(date_width);
+ rep.HANDLER(date_width_).on_with(string("?normalize"), date_width);
if (! rep.HANDLER(payee_width_).specified)
- rep.HANDLER(payee_width_).on_with(payee_width);
+ rep.HANDLER(payee_width_).on_with(string("?normalize"), payee_width);
if (! rep.HANDLER(account_width_).specified)
- rep.HANDLER(account_width_).on_with(account_width);
+ rep.HANDLER(account_width_).on_with(string("?normalize"), account_width);
if (! rep.HANDLER(amount_width_).specified)
- rep.HANDLER(amount_width_).on_with(amount_width);
+ rep.HANDLER(amount_width_).on_with(string("?normalize"), amount_width);
if (! rep.HANDLER(total_width_).specified)
- rep.HANDLER(total_width_).on_with(total_width);
+ rep.HANDLER(total_width_).on_with(string("?normalize"), total_width);
}
}
@@ -500,7 +595,10 @@ void handle_debug_options(int argc, char * argv[])
{
for (int i = 1; i < argc; i++) {
if (argv[i][0] == '-') {
- if (std::strcmp(argv[i], "--verify") == 0) {
+ if (std::strcmp(argv[i], "--args-only") == 0) {
+ args_only = true;
+ }
+ else if (std::strcmp(argv[i], "--verify") == 0) {
#if defined(VERIFY_ON)
verify_enabled = true; // global in utils.h
#endif
diff --git a/src/global.h b/src/global.h
index 2ef5a545..df9845d5 100644
--- a/src/global.h
+++ b/src/global.h
@@ -113,6 +113,8 @@ See LICENSE file included with the distribution for details and disclaimer.");
out << std::endl;
}
+ void report_options(report_t& report, std::ostream& out);
+
option_t<global_scope_t> * lookup_option(const char * p);
virtual expr_t::ptr_op_t lookup(const string& name);
@@ -133,11 +135,12 @@ See LICENSE file included with the distribution for details and disclaimer.");
CTOR(global_scope_t, init_file_) {
if (const char * home_var = std::getenv("HOME"))
- on((path(home_var) / ".ledgerrc").string());
+ on(none, (path(home_var) / ".ledgerrc").string());
else
- on(path("./.ledgerrc").string());
+ on(none, path("./.ledgerrc").string());
});
+ OPTION(global_scope_t, options);
OPTION(global_scope_t, script_);
OPTION(global_scope_t, trace_);
OPTION(global_scope_t, verbose);
diff --git a/src/interactive.cc b/src/interactive.cc
index 528ef9f2..1ae976e7 100644
--- a/src/interactive.cc
+++ b/src/interactive.cc
@@ -75,7 +75,13 @@ void interactive_t::verify_arguments() const
break;
case 'd':
label = _("a date");
- wrong_arg = ! next_arg->is_date();
+ wrong_arg = (! next_arg->is_date() &&
+ ! next_arg->is_datetime());
+ break;
+ case 't':
+ label = _("a date/time");
+ wrong_arg = (! next_arg->is_date() &&
+ ! next_arg->is_datetime());
break;
case 'i':
case 'l':
@@ -106,11 +112,6 @@ void interactive_t::verify_arguments() const
label = _("a string");
wrong_arg = ! next_arg->is_string();
break;
- case 't':
- label = _("a date or time");
- wrong_arg = (! next_arg->is_date() &&
- ! next_arg->is_datetime());
- break;
case 'v':
label = _("any value");
wrong_arg = false;
diff --git a/src/item.cc b/src/item.cc
index 758ce254..9e297052 100644
--- a/src/item.cc
+++ b/src/item.cc
@@ -247,6 +247,10 @@ namespace {
return 0L;
}
+ value_t ignore(item_t&) {
+ return false;
+ }
+
template <value_t (*Func)(item_t&)>
value_t get_wrapper(call_scope_t& scope) {
return (*Func)(find_scope<item_t>(scope));
@@ -258,10 +262,12 @@ value_t get_comment(item_t& item)
if (! item.note) {
return string_value("");
} else {
- // jww (2009-03-01): If the comment is a short one-liner, put it at the
- // end of the post/xact
std::ostringstream buf;
- buf << "\n ;";
+ if (item.note->length() > 15)
+ buf << "\n ;";
+ else
+ buf << " ;";
+
bool need_separator = false;
for (const char * p = item.note->c_str(); *p; p++) {
if (*p == '\n') {
@@ -328,6 +334,11 @@ expr_t::ptr_op_t item_t::lookup(const string& name)
return WRAP_FUNCTOR(ledger::has_tag);
break;
+ case 'i':
+ if (name == "is_account")
+ return WRAP_FUNCTOR(get_wrapper<&ignore>);
+ break;
+
case 'm':
if (name == "meta")
return WRAP_FUNCTOR(ledger::get_tag);
@@ -341,6 +352,8 @@ expr_t::ptr_op_t item_t::lookup(const string& name)
case 'p':
if (name == "pending")
return WRAP_FUNCTOR(get_wrapper<&get_pending>);
+ else if (name == "parent")
+ return WRAP_FUNCTOR(get_wrapper<&ignore>);
break;
case 's':
diff --git a/src/iterators.cc b/src/iterators.cc
index 9b07ace3..63cbb9b2 100644
--- a/src/iterators.cc
+++ b/src/iterators.cc
@@ -101,7 +101,7 @@ void posts_commodities_iterator::reset(journal_t& journal)
foreach (commodity_t::base_t::history_map::value_type hpair,
pair.second.prices) {
xact_t * xact;
- string symbol = hpair.second.commodity().symbol();
+ string symbol = hpair.second.commodity().symbol();
std::map<string, xact_t *>::iterator i =
xacts_by_commodity.find(symbol);
@@ -123,6 +123,8 @@ void posts_commodities_iterator::reset(journal_t& journal)
temp.amount = hpair.second;
temp.set_flags(ITEM_GENERATED | ITEM_TEMP);
+ temp.xdata().datetime = hpair.first;
+
xact->add_post(&temp);
}
}
diff --git a/src/iterators.h b/src/iterators.h
index 67293c5c..ae2ddaf9 100644
--- a/src/iterators.h
+++ b/src/iterators.h
@@ -175,7 +175,7 @@ protected:
std::list<post_t> post_temps;
std::list<account_t> acct_temps;
- xacts_list xact_temps;
+ xacts_list xact_temps;
public:
posts_commodities_iterator() {
diff --git a/src/ledger.h b/src/ledger.h
deleted file mode 100644
index d37fddaa..00000000
--- a/src/ledger.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (c) 2003-2009, 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.
- */
-
-/**
- * @file ledger.h
- * @author John Wiegley
- *
- * @mainpage Ledger Accounting Tool
- *
- * There are essentially nine steps involved in realizing a ledger reporting
- * session, with steps 5 and 8 -- which relate to the user's journal file --
- * being optional in the case of "pre-commands", since they do not require the
- * user's data to be read.
- *
- * \section global_init Initialize the global environment
- *
- * \section create_objs Create session and report objects
- *
- * jww (2009-02-02): Set the "session context".
- *
- * \section process_opts Process user options
- *
- * This configures session and report objects
- *
- * - environment
- * - initialization file
- * - command-line options
- *
- * \section lookup_cmd Locate object relating to command verb
- *
- * \section parse_data Parse the user's journal files
- *
- * \section create_out Create the output stream
- *
- * \section invoke_cmd Invoke the command object
- *
- * \section shutdown Wrap up, closing everything and releasing memory
- */
-#ifndef _LEDGER_H
-#define _LEDGER_H
-
-
-
-
-
-
-#if defined(HAVE_BOOST_PYTHON)
-#define LEDGER_SESSION_T python_interpreter_t
-#else
-#define LEDGER_SESSION_T session_t
-#endif
-
-#endif // _LEDGER_H
diff --git a/src/op.cc b/src/op.cc
index f80d444a..559db616 100644
--- a/src/op.cc
+++ b/src/op.cc
@@ -34,6 +34,7 @@
#include "op.h"
#include "scope.h"
#include "commodity.h"
+#include "pool.h"
namespace ledger {
@@ -166,10 +167,15 @@ value_t expr_t::op_t::calc(scope_t& scope, ptr_op_t * locus)
call_scope_t call_args(scope);
if (value_t obj = left()->left()->as_function()(call_args)) {
if (obj.is_pointer()) {
- scope_t& objscope(obj.as_ref_lval<scope_t>());
- if (ptr_op_t member = objscope.lookup(right()->as_ident())) {
- result = member->calc(objscope);
- break;
+ if (obj.as_pointer_lval<scope_t>() == NULL) {
+ throw_(calc_error,
+ _("Left operand of . operator is NULL"));
+ } else {
+ scope_t& objscope(obj.as_ref_lval<scope_t>());
+ if (ptr_op_t member = objscope.lookup(right()->as_ident())) {
+ result = member->calc(objscope);
+ break;
+ }
}
}
}
diff --git a/src/option.cc b/src/option.cc
index 7d5b272d..1275e270 100644
--- a/src/option.cc
+++ b/src/option.cc
@@ -76,11 +76,13 @@ namespace {
return op_bool_tuple(scope.lookup(buf), false);
}
- void process_option(const function_t& opt, scope_t& scope,
- const char * arg, const string& name)
+ void process_option(const string& whence, const function_t& opt,
+ scope_t& scope, const char * arg, const string& name)
{
try {
call_scope_t args(scope);
+
+ args.push_back(string_value(whence));
if (arg)
args.push_back(string_value(arg));
@@ -97,12 +99,12 @@ namespace {
}
}
-void process_option(const string& name, scope_t& scope,
+void process_option(const string& whence, const string& name, scope_t& scope,
const char * arg, const string& varname)
{
op_bool_tuple opt(find_option(scope, name));
if (opt.first)
- process_option(opt.first->as_function(), scope, arg, varname);
+ process_option(whence, opt.first->as_function(), scope, arg, varname);
}
void process_environment(const char ** envp, const string& tag,
@@ -129,7 +131,7 @@ void process_environment(const char ** envp, const string& tag,
try {
string value = string(*p, q - *p);
if (! value.empty())
- process_option(string(buf), scope, q + 1, value);
+ process_option(string("$") + buf, string(buf), scope, q + 1, value);
}
catch (const std::exception& err) {
add_error_context(_("While parsing environment variable option '%1':")
@@ -201,7 +203,8 @@ strings_list process_arguments(strings_list args, scope_t& scope)
if (value == NULL)
throw_(option_error, _("Missing option argument for --%1") << name);
}
- process_option(opt.first->as_function(), scope, value,
+ process_option(string("--") + name,
+ opt.first->as_function(), scope, value,
string("--") + name);
}
else if ((*i)[1] == '\0') {
@@ -230,7 +233,8 @@ strings_list process_arguments(strings_list args, scope_t& scope)
throw_(option_error,
_("Missing option argument for -%1") << o.ch);
}
- process_option(o.op->as_function(), scope, value, string("-") + o.ch);
+ process_option(string("-") + o.ch, o.op->as_function(), scope, value,
+ string("-") + o.ch);
}
}
}
diff --git a/src/option.h b/src/option.h
index 86899931..6809a27c 100644
--- a/src/option.h
+++ b/src/option.h
@@ -56,10 +56,11 @@ template <typename T>
class option_t
{
protected:
- const char * name;
- std::size_t name_len;
- const char ch;
- bool handled;
+ const char * name;
+ std::size_t name_len;
+ const char ch;
+ bool handled;
+ optional<string> source;
option_t& operator=(const option_t&);
@@ -73,6 +74,7 @@ public:
handled(false), parent(NULL), value(),
wants_arg(name[name_len - 1] == '_') {
TRACE_CTOR(option_t, "const char *, const char");
+ DEBUG("option.names", "Option: " << name);
}
option_t(const option_t& other)
: name(other.name),
@@ -90,6 +92,18 @@ public:
TRACE_DTOR(option_t);
}
+ void report(std::ostream& out) const {
+ if (handled && source) {
+ if (wants_arg) {
+ out << desc() << " => ";
+ value.dump(out);
+ } else {
+ out << desc();
+ }
+ out << " <" << *source << ">" << std::endl;
+ }
+ }
+
string desc() const {
std::ostringstream out;
out << "--";
@@ -117,31 +131,42 @@ public:
return value.as_string_lval();
}
- void on_only() {
+ string str() const {
+ assert(handled);
+ if (! value)
+ throw_(std::runtime_error, _("No argument provided for %1") << desc());
+ return value.as_string();
+ }
+
+ void on_only(const optional<string>& whence) {
handled = true;
+ source = whence;
}
- void on(const string& str) {
- on_with(string_value(str));
+ void on(const optional<string>& whence, const string& str) {
+ on_with(whence, string_value(str));
}
- virtual void on_with(const value_t& val) {
+ virtual void on_with(const optional<string>& whence,
+ const value_t& val) {
handled = true;
value = val;
+ source = whence;
}
void off() {
handled = false;
value = value_t();
+ source = none;
}
virtual void handler_thunk(call_scope_t&) {}
virtual void handler(call_scope_t& args) {
if (wants_arg) {
- if (args.empty())
+ if (args.empty() || args.size() == 1)
throw_(std::runtime_error, _("No argument provided for %1") << desc());
- on_with(args[0]);
+ on_with(args[0].as_string(), args[1]);
} else {
- on_only();
+ on_only(args[0].as_string());
}
handler_thunk(args);
@@ -270,7 +295,7 @@ inline bool is_eq(const char * p, const char * n) {
#define WANT_DIR() \
(std::strncmp(p, DIR_PREFIX, DIR_PREFIX_LEN) == 0)
-void process_option(const string& name, scope_t& scope,
+void process_option(const string& whence, const string& name, scope_t& scope,
const char * arg, const string& varname);
void process_environment(const char ** envp, const string& tag,
diff --git a/src/output.cc b/src/output.cc
index 63d8c919..92824714 100644
--- a/src/output.cc
+++ b/src/output.cc
@@ -46,7 +46,7 @@ format_posts::format_posts(report_t& _report,
: report(_report), last_xact(NULL), last_post(NULL),
print_raw(_print_raw)
{
- TRACE_CTOR(format_posts, "report&, const string&");
+ TRACE_CTOR(format_posts, "report&, const string&, bool");
const char * f = format.c_str();
@@ -179,7 +179,9 @@ format_accounts::mark_accounts(account_t& account, const bool flat)
(account.has_flags(ACCOUNT_EXT_VISITED) || (! flat && visited > 0))) {
bind_scope_t bound_scope(report, account);
if ((! flat && to_display > 1) ||
- (disp_pred(bound_scope) && (flat || to_display != 1))) {
+ ((flat || to_display != 1 ||
+ account.has_flags(ACCOUNT_EXT_VISITED)) &&
+ disp_pred(bound_scope))) {
account.xdata().add_flags(ACCOUNT_EXT_TO_DISPLAY);
DEBUG("account.display", "Marking account as TO_DISPLAY");
to_display = 1;
@@ -207,7 +209,8 @@ void format_accounts::flush()
foreach (account_t * account, posted_accounts)
displayed += post_account(*account);
- if (! report.HANDLED(no_total) && displayed > 1) {
+ if (displayed > 1 &&
+ ! report.HANDLED(no_total) && ! report.HANDLED(percent)) {
bind_scope_t bound_scope(report, *report.session.master);
separator_format.format(out, bound_scope);
total_line_format.format(out, bound_scope);
diff --git a/src/parser.cc b/src/parser.cc
index 1c78e30d..39004758 100644
--- a/src/parser.cc
+++ b/src/parser.cc
@@ -44,23 +44,10 @@ expr_t::parser_t::parse_value_term(std::istream& in,
token_t& tok = next_token(in, tflags);
switch (tok.kind) {
- case token_t::VALUE: {
+ case token_t::VALUE:
node = new op_t(op_t::VALUE);
node->set_value(tok.value);
-
- token_t& postfix_tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT));
- if (postfix_tok.kind == token_t::PERCENT) {
- ptr_op_t prev(node);
- node = new op_t(op_t::O_DIV);
- node->set_left(prev);
- ptr_op_t hundred = new op_t(op_t::VALUE);
- hundred->set_value(100L);
- node->set_right(hundred);
- } else {
- push_token(postfix_tok);
- }
break;
- }
case token_t::IDENT: {
string ident = tok.value.as_string();
diff --git a/src/pool.cc b/src/pool.cc
new file mode 100644
index 00000000..68ba3ed7
--- /dev/null
+++ b/src/pool.cc
@@ -0,0 +1,361 @@
+/*
+ * Copyright (c) 2003-2009, 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.
+ */
+
+#include <system.hh>
+
+#include "amount.h"
+#include "commodity.h"
+#include "annotate.h"
+#include "pool.h"
+#include "quotes.h"
+
+namespace ledger {
+
+commodity_pool_t::commodity_pool_t()
+ : default_commodity(NULL), keep_base(false),
+ quote_leeway(86400), get_quotes(false),
+ get_commodity_quote(commodity_quote_from_script)
+{
+ TRACE_CTOR(commodity_pool_t, "");
+ null_commodity = create("");
+ null_commodity->add_flags(COMMODITY_BUILTIN | COMMODITY_NOMARKET);
+}
+
+commodity_t * commodity_pool_t::create(const string& symbol)
+{
+ shared_ptr<commodity_t::base_t>
+ base_commodity(new commodity_t::base_t(symbol));
+ std::auto_ptr<commodity_t> commodity(new commodity_t(this, base_commodity));
+
+ DEBUG("amounts.commodities", "Creating base commodity " << symbol);
+
+ // Create the "qualified symbol" version of this commodity's symbol
+ if (commodity_t::symbol_needs_quotes(symbol)) {
+ commodity->qualified_symbol = "\"";
+ *commodity->qualified_symbol += symbol;
+ *commodity->qualified_symbol += "\"";
+ }
+
+ DEBUG("amounts.commodities",
+ "Creating commodity '" << commodity->symbol() << "'");
+
+ std::pair<commodities_map::iterator, bool> result
+ = commodities.insert(commodities_map::value_type(commodity->mapping_key(),
+ commodity.get()));
+ assert(result.second);
+
+ return commodity.release();
+}
+
+commodity_t * commodity_pool_t::find_or_create(const string& symbol)
+{
+ DEBUG("amounts.commodities", "Find-or-create commodity " << symbol);
+
+ commodity_t * commodity = find(symbol);
+ if (commodity)
+ return commodity;
+ return create(symbol);
+}
+
+commodity_t * commodity_pool_t::find(const string& symbol)
+{
+ DEBUG("amounts.commodities", "Find commodity " << symbol);
+
+ commodities_map::const_iterator i = commodities.find(symbol);
+ if (i != commodities.end())
+ return (*i).second;
+ return NULL;
+}
+
+commodity_t *
+commodity_pool_t::create(const string& symbol, const annotation_t& details)
+{
+ commodity_t * new_comm = create(symbol);
+ if (! new_comm)
+ return NULL;
+
+ if (details)
+ return find_or_create(*new_comm, details);
+ else
+ return new_comm;
+}
+
+namespace {
+ string make_qualified_name(const commodity_t& comm,
+ const annotation_t& details)
+ {
+ assert(details);
+
+ if (details.price && details.price->sign() < 0)
+ throw_(amount_error, _("A commodity's price may not be negative"));
+
+ std::ostringstream name;
+ comm.print(name);
+ details.print(name, comm.parent().keep_base);
+
+ DEBUG("amounts.commodities", "make_qualified_name for "
+ << *comm.qualified_symbol << std::endl << details);
+ DEBUG("amounts.commodities", "qualified_name is " << name.str());
+
+ return name.str();
+ }
+}
+
+commodity_t *
+commodity_pool_t::find(const string& symbol, const annotation_t& details)
+{
+ commodity_t * comm = find(symbol);
+ if (! comm)
+ return NULL;
+
+ if (details) {
+ string name = make_qualified_name(*comm, details);
+
+ if (commodity_t * ann_comm = find(name)) {
+ assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details);
+ return ann_comm;
+ }
+ return NULL;
+ } else {
+ return comm;
+ }
+}
+
+commodity_t *
+commodity_pool_t::find_or_create(const string& symbol,
+ const annotation_t& details)
+{
+ commodity_t * comm = find(symbol);
+ if (! comm)
+ return NULL;
+
+ if (details)
+ return find_or_create(*comm, details);
+ else
+ return comm;
+}
+
+commodity_t *
+commodity_pool_t::create(commodity_t& comm,
+ const annotation_t& details,
+ const string& mapping_key)
+{
+ assert(comm);
+ assert(details);
+ assert(! mapping_key.empty());
+
+ std::auto_ptr<commodity_t> commodity
+ (new annotated_commodity_t(&comm, details));
+
+ commodity->qualified_symbol = comm.symbol();
+ assert(! commodity->qualified_symbol->empty());
+
+ DEBUG("amounts.commodities", "Creating annotated commodity "
+ << "symbol " << commodity->symbol()
+ << " key " << mapping_key << std::endl << details);
+
+ // Add the fully annotated name to the map, so that this symbol may
+ // quickly be found again.
+ commodity->mapping_key_ = mapping_key;
+
+ std::pair<commodities_map::iterator, bool> result
+ = commodities.insert(commodities_map::value_type(mapping_key,
+ commodity.get()));
+ assert(result.second);
+
+ return commodity.release();
+}
+
+commodity_t * commodity_pool_t::find_or_create(commodity_t& comm,
+ const annotation_t& details)
+{
+ assert(comm);
+ assert(details);
+
+ string name = make_qualified_name(comm, details);
+ assert(! name.empty());
+
+ if (commodity_t * ann_comm = find(name)) {
+ assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details);
+ return ann_comm;
+ }
+ return create(comm, details, name);
+}
+
+void commodity_pool_t::exchange(commodity_t& commodity,
+ const amount_t& per_unit_cost,
+ const datetime_t& moment)
+{
+ DEBUG("commodity.prices.add", "exchanging commodity " << commodity
+ << " at per unit cost " << per_unit_cost << " on " << moment);
+
+ commodity_t& base_commodity
+ (commodity.annotated ?
+ as_annotated_commodity(commodity).referent() : commodity);
+
+ base_commodity.add_price(moment, per_unit_cost);
+}
+
+cost_breakdown_t
+commodity_pool_t::exchange(const amount_t& amount,
+ const amount_t& cost,
+ const bool is_per_unit,
+ const optional<datetime_t>& moment,
+ const optional<string>& tag)
+{
+ DEBUG("commodity.prices.add", "exchange: " << amount << " for " << cost);
+ DEBUG("commodity.prices.add", "exchange: is-per-unit = " << is_per_unit);
+#if defined(DEBUG_ON)
+ if (moment)
+ DEBUG("commodity.prices.add", "exchange: moment = " << *moment);
+ if (tag)
+ DEBUG("commodity.prices.add", "exchange: tag = " << *tag);
+#endif
+
+ commodity_t& commodity(amount.commodity());
+
+ annotation_t * current_annotation = NULL;
+ if (commodity.annotated)
+ current_annotation = &as_annotated_commodity(commodity).details;
+
+ amount_t per_unit_cost =
+ (is_per_unit || amount.is_realzero() ? cost : cost / amount).abs();
+
+ DEBUG("commodity.prices.add", "exchange: per-unit-cost = " << per_unit_cost);
+
+ if (! per_unit_cost.is_realzero())
+ exchange(commodity, per_unit_cost, moment ? *moment : CURRENT_TIME());
+
+ cost_breakdown_t breakdown;
+ breakdown.final_cost = ! is_per_unit ? cost : cost * amount;
+
+ DEBUG("commodity.prices.add",
+ "exchange: final-cost = " << breakdown.final_cost);
+
+ if (current_annotation && current_annotation->price)
+ breakdown.basis_cost
+ = (*current_annotation->price * amount).unrounded();
+ else
+ breakdown.basis_cost = breakdown.final_cost;
+
+ DEBUG("commodity.prices.add",
+ "exchange: basis-cost = " << breakdown.basis_cost);
+
+ annotation_t annotation(per_unit_cost, moment ?
+ moment->date() : optional<date_t>(), tag);
+
+ annotation.add_flags(ANNOTATION_PRICE_CALCULATED);
+ if (moment)
+ annotation.add_flags(ANNOTATION_DATE_CALCULATED);
+ if (tag)
+ annotation.add_flags(ANNOTATION_TAG_CALCULATED);
+
+ breakdown.amount = amount_t(amount, annotation);
+
+ DEBUG("commodity.prices.add",
+ "exchange: amount = " << breakdown.amount);
+
+ return breakdown;
+}
+
+optional<price_point_t> commodity_pool_t::parse_price_directive(char * line)
+{
+ char * date_field_ptr = line;
+ char * time_field_ptr = next_element(date_field_ptr);
+ if (! time_field_ptr) return none;
+ string date_field = date_field_ptr;
+
+ char * symbol_and_price;
+ datetime_t datetime;
+
+ if (std::isdigit(time_field_ptr[0])) {
+ symbol_and_price = next_element(time_field_ptr);
+ if (! symbol_and_price) return none;
+
+ datetime = parse_datetime(date_field + " " + time_field_ptr);
+ }
+ else if (std::isdigit(date_field_ptr[0])) {
+ symbol_and_price = time_field_ptr;
+ datetime = datetime_t(parse_date(date_field));
+ }
+ else {
+ symbol_and_price = date_field_ptr;
+ datetime = CURRENT_TIME();
+ }
+
+ string symbol;
+ commodity_t::parse_symbol(symbol_and_price, symbol);
+
+ price_point_t point;
+ point.when = datetime;
+ point.price.parse(symbol_and_price);
+ VERIFY(point.price.valid());
+
+ DEBUG("commodity.download", "Looking up symbol: " << symbol);
+ if (commodity_t * commodity =
+ amount_t::current_pool->find_or_create(symbol)) {
+ DEBUG("commodity.download", "Adding price for " << symbol << ": "
+ << point.when << " " << point.price);
+ commodity->add_price(point.when, point.price, true);
+ commodity->add_flags(COMMODITY_KNOWN);
+ return point;
+ }
+
+ return none;
+}
+
+commodity_t *
+commodity_pool_t::parse_price_expression(const std::string& str,
+ const bool add_prices,
+ const optional<datetime_t>& moment)
+{
+ scoped_array<char> buf(new char[str.length() + 1]);
+
+ std::strcpy(buf.get(), str.c_str());
+
+ char * price = std::strchr(buf.get(), '=');
+ if (price)
+ *price++ = '\0';
+
+ if (commodity_t * commodity = find_or_create(trim_ws(buf.get()))) {
+ if (price && add_prices) {
+ for (char * p = std::strtok(price, ";");
+ p;
+ p = std::strtok(NULL, ";")) {
+ commodity->add_price(moment ? *moment : CURRENT_TIME(), amount_t(p));
+ }
+ }
+ return commodity;
+ }
+ return NULL;
+}
+
+} // namespace ledger
diff --git a/src/pool.h b/src/pool.h
new file mode 100644
index 00000000..378163e9
--- /dev/null
+++ b/src/pool.h
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2003-2009, 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.
+ */
+
+/**
+ * @addtogroup math
+ */
+
+/**
+ * @file pool.h
+ * @author John Wiegley
+ *
+ * @ingroup math
+ *
+ * @brief Types for managing commodity pools
+ *
+ * Long.
+ */
+#ifndef _POOL_H
+#define _POOL_H
+
+namespace ledger {
+
+/**
+ * @brief Brief
+ *
+ * Long.
+ */
+struct cost_breakdown_t
+{
+ amount_t amount;
+ amount_t final_cost;
+ amount_t basis_cost;
+};
+
+/**
+ * @brief Brief
+ *
+ * Long.
+ */
+class commodity_pool_t : public noncopyable
+{
+ /**
+ * The commodities collection in commodity_pool_t maintains pointers to all
+ * the commodities which have ever been created by the user, whether
+ * explicitly by calling the create methods of commodity_pool_t, or
+ * implicitly by parsing a commoditized amount.
+ */
+ typedef std::map<string, commodity_t *> commodities_map;
+
+public:
+ commodities_map commodities;
+ commodity_t * null_commodity;
+ commodity_t * default_commodity;
+
+ bool keep_base; // --base
+
+ optional<path> price_db; // --price-db=
+ long quote_leeway; // --leeway=
+ bool get_quotes; // --download
+
+ function<optional<price_point_t>
+ (commodity_t& commodity, const optional<commodity_t&>& in_terms_of)>
+ get_commodity_quote;
+
+ explicit commodity_pool_t();
+
+ ~commodity_pool_t() {
+ TRACE_DTOR(commodity_pool_t);
+ foreach (commodities_map::value_type pair, commodities)
+ checked_delete(pair.second);
+ }
+
+ commodity_t * create(const string& symbol);
+ commodity_t * find(const string& name);
+ commodity_t * find_or_create(const string& symbol);
+
+ commodity_t * create(const string& symbol, const annotation_t& details);
+ commodity_t * find(const string& symbol, const annotation_t& details);
+ commodity_t * find_or_create(const string& symbol,
+ const annotation_t& details);
+
+ commodity_t * create(commodity_t& comm,
+ const annotation_t& details,
+ const string& mapping_key);
+
+ commodity_t * find_or_create(commodity_t& comm,
+ const annotation_t& details);
+
+ // Exchange one commodity for another, while recording the factored price.
+
+ void exchange(commodity_t& commodity,
+ const amount_t& per_unit_cost,
+ const datetime_t& moment);
+
+ cost_breakdown_t exchange(const amount_t& amount,
+ const amount_t& cost,
+ const bool is_per_unit = false,
+ const optional<datetime_t>& moment = none,
+ const optional<string>& tag = none);
+
+ // Parse commodity prices from a textual representation
+
+ optional<price_point_t> parse_price_directive(char * line);
+
+ commodity_t *
+ parse_price_expression(const std::string& str,
+ const bool add_prices = true,
+ const optional<datetime_t>& moment = none);
+};
+
+} // namespace ledger
+
+#endif // _POOL_H
diff --git a/src/post.cc b/src/post.cc
index 3a0f3cc4..9b416434 100644
--- a/src/post.cc
+++ b/src/post.cc
@@ -244,6 +244,10 @@ namespace {
return long(post.reported_account()->depth);
}
+ value_t get_datetime(post_t& post) {
+ return post.xdata().datetime;
+ }
+
template <value_t (*Func)(post_t&)>
value_t get_wrapper(call_scope_t& scope) {
return (*Func)(find_scope<post_t>(scope));
@@ -282,6 +286,8 @@ expr_t::ptr_op_t post_t::lookup(const string& name)
case 'd':
if (name == "depth")
return WRAP_FUNCTOR(get_wrapper<&get_account_depth>);
+ else if (name == "datetime")
+ return WRAP_FUNCTOR(get_wrapper<&get_datetime>);
break;
case 'h':
@@ -296,6 +302,8 @@ expr_t::ptr_op_t post_t::lookup(const string& name)
return WRAP_FUNCTOR(get_wrapper<&get_payee>);
else if (name == "primary")
return WRAP_FUNCTOR(get_wrapper<&get_commodity_is_primary>);
+ else if (name == "parent")
+ return WRAP_FUNCTOR(get_wrapper<&get_xact>);
break;
case 'r':
diff --git a/src/post.h b/src/post.h
index dc062164..f651d88d 100644
--- a/src/post.h
+++ b/src/post.h
@@ -140,6 +140,7 @@ public:
value_t total;
std::size_t count;
date_t date;
+ datetime_t datetime;
account_t * account;
void * ptr;
diff --git a/src/predicate.h b/src/predicate.h
index 47eba04b..3e9fc6b1 100644
--- a/src/predicate.h
+++ b/src/predicate.h
@@ -48,6 +48,7 @@
#include "expr.h"
#include "commodity.h"
+#include "annotate.h"
namespace ledger {
diff --git a/src/py_amount.cc b/src/py_amount.cc
index a5a34a0b..68fd8698 100644
--- a/src/py_amount.cc
+++ b/src/py_amount.cc
@@ -35,6 +35,8 @@
#include "pyutils.h"
#include "pyfstream.h"
#include "commodity.h"
+#include "annotate.h"
+#include "pool.h"
namespace ledger {
diff --git a/src/py_journal.cc b/src/py_journal.cc
index 65eca838..fb693ffc 100644
--- a/src/py_journal.cc
+++ b/src/py_journal.cc
@@ -171,16 +171,6 @@ account_t& accounts_getitem(account_t& account, int i)
return *(*elem).second;
}
-PyObject * py_account_get_data(account_t& account)
-{
- return (PyObject *) account.data;
-}
-
-void py_account_set_data(account_t& account, PyObject * obj)
-{
- account.data = obj;
-}
-
account_t * py_find_account_1(journal_t& journal, const string& name)
{
return journal.find_account(name);
@@ -342,7 +332,6 @@ void export_journal()
.def_readwrite("name", &account_t::name)
.def_readwrite("note", &account_t::note)
.def_readonly("depth", &account_t::depth)
- .add_property("data", py_account_get_data, py_account_set_data)
.def_readonly("ident", &account_t::ident)
.def("fullname", &account_t::fullname)
diff --git a/src/py_value.cc b/src/py_value.cc
index c7847d00..8e579104 100644
--- a/src/py_value.cc
+++ b/src/py_value.cc
@@ -34,6 +34,7 @@
#include "pyinterp.h"
#include "pyutils.h"
#include "commodity.h"
+#include "annotate.h"
namespace ledger {
diff --git a/src/pyledger.h b/src/pyledger.h
deleted file mode 100644
index 3374e895..00000000
--- a/src/pyledger.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (c) 2003-2009, 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.
- */
-
-#ifndef _PYLEDGER_H
-#define _PYLEDGER_H
-
-
-#endif // _PYLEDGER_H
diff --git a/src/quotes.cc b/src/quotes.cc
index 5dbbc962..d4be462e 100644
--- a/src/quotes.cc
+++ b/src/quotes.cc
@@ -31,46 +31,39 @@
#include <system.hh>
+#include "amount.h"
+#include "commodity.h"
+#include "pool.h"
#include "quotes.h"
namespace ledger {
-#if 0
-void quotes_by_script::operator()(commodity_base_t& commodity,
- const datetime_t& moment,
- const datetime_t& date,
- const datetime_t& last,
- amount_t& price)
+optional<price_point_t>
+commodity_quote_from_script(commodity_t& commodity,
+ const optional<commodity_t&>& exchange_commodity)
{
- DEBUG_CLASS("ledger.quotes.download");
-
- DEBUG_("commodity: " << commodity.symbol);
- DEBUG_TIME_(current_moment);
- DEBUG_TIME_(moment);
- DEBUG_TIME_(date);
- DEBUG_TIME_(last);
- if (commodity.history)
- DEBUG_TIME_(commodity.history->last_lookup);
- DEBUG_("pricing_leeway is " << pricing_leeway);
-
- if ((commodity.history &&
- (current_moment - commodity.history->last_lookup) < pricing_leeway) ||
- (current_moment - last) < pricing_leeway ||
- (price && moment > date && (moment - date) <= pricing_leeway))
- return;
-
- using namespace std;
-
- DEBUG_("downloading quote for symbol " << commodity.symbol);
+ DEBUG("commodity.download", "downloading quote for symbol " << commodity.symbol());
+#if defined(DEBUG_ON)
+ if (exchange_commodity)
+ DEBUG("commodity.download",
+ " in terms of commodity " << exchange_commodity->symbol());
+#endif
char buf[256];
buf[0] = '\0';
- bool success = true;
+ string getquote_cmd("getquote \"");
+ getquote_cmd += commodity.symbol();
+ getquote_cmd += "\" \"";
+ if (exchange_commodity)
+ getquote_cmd += exchange_commodity->symbol();
+ getquote_cmd += "\"";
+
+ DEBUG("commodity.download", "invoking command: " << getquote_cmd);
- if (FILE * fp = popen((string("getquote \"") +
- commodity.symbol + "\"").c_str(), "r")) {
- if (feof(fp) || ! fgets(buf, 255, fp))
+ bool success = true;
+ if (FILE * fp = popen(getquote_cmd.c_str(), "r")) {
+ if (std::feof(fp) || ! std::fgets(buf, 255, fp))
success = false;
if (pclose(fp) != 0)
success = false;
@@ -79,31 +72,41 @@ void quotes_by_script::operator()(commodity_base_t& commodity,
}
if (success && buf[0]) {
- char * p = strchr(buf, '\n');
- if (p) *p = '\0';
-
- DEBUG_("downloaded quote: " << buf);
-
- price.parse(buf);
- commodity.add_price(current_moment, price);
-
- commodity.history->last_lookup = current_moment;
+ if (char * p = std::strchr(buf, '\n')) *p = '\0';
+ DEBUG("commodity.download", "downloaded quote: " << buf);
- if (price && ! price_db.empty()) {
+ if (optional<price_point_t> point =
+ amount_t::current_pool->parse_price_directive(buf)) {
+ if (amount_t::current_pool->price_db) {
#if defined(__GNUG__) && __GNUG__ < 3
- ofstream database(price_db.c_str(), ios::out | ios::app);
+ ofstream database(*amount_t::current_pool->price_db,
+ ios::out | ios::app);
#else
- ofstream database(price_db.c_str(), ios_base::out | ios_base::app);
+ ofstream database(*amount_t::current_pool->price_db,
+ std::ios_base::out | std::ios_base::app);
#endif
- database << "P " << current_moment.to_string("%Y/%m/%d %H:%M:%S")
- << " " << commodity.symbol << " " << price << endl;
+ database << "P "
+ << format_datetime(point->when, string("%Y/%m/%d %H:%M:%S"))
+ << " " << commodity.symbol()
+ << " " << point->price
+ << std::endl;
+ }
+ return point;
}
} else {
- throw_(std::runtime_error,
- _("Failed to download price for '%1' (command: \"getquote %1\")")
- << commodity.symbol);
+ DEBUG("commodity.download",
+ "Failed to download price for '" << commodity.symbol() <<
+ "' (command: \"getquote " << commodity.symbol() <<
+ " " << (exchange_commodity ?
+ exchange_commodity->symbol() : "''") << "\")");
+
+ // Don't try to download this commodity again.
+
+ // jww (2009-06-24): This flag should be removed in order to try again
+ // when using a GUI.
+ commodity.add_flags(COMMODITY_NOMARKET);
}
+ return none;
}
-#endif
} // namespace ledger
diff --git a/src/quotes.h b/src/quotes.h
index 68c7cade..5db69d1f 100644
--- a/src/quotes.h
+++ b/src/quotes.h
@@ -46,39 +46,11 @@
#ifndef _QUOTES_H
#define _QUOTES_H
-
namespace ledger {
-#if 0
-/**
- * @brief Brief
- *
- * Long.
- */
-class quotes_by_script : public noncopyable, public commodity_t::base_t::updater_t
-{
- string price_db;
- std::size_t pricing_leeway;
-
- quotes_by_script();
-
-public:
- quotes_by_script(path _price_db,
- std::size_t _pricing_leeway)
- : price_db(_price_db), pricing_leeway(_pricing_leeway) {
- TRACE_CTOR(quotes_by_script, "path, std::size_t, bool&");
- }
- ~quotes_by_script() throw() {
- TRACE_DTOR(quotes_by_script);
- }
-
- virtual void operator()(commodity_base_t& commodity,
- const datetime_t& moment,
- const datetime_t& date,
- const datetime_t& last,
- amount_t& price);
-};
-#endif
+optional<price_point_t>
+commodity_quote_from_script(commodity_t& commodity,
+ const optional<commodity_t&>& exchange_commodity);
} // namespace ledger
diff --git a/src/report.cc b/src/report.cc
index be1e47ee..4c8a40e6 100644
--- a/src/report.cc
+++ b/src/report.cc
@@ -55,12 +55,15 @@ void report_t::posts_report(post_handler_ptr handler)
void report_t::generate_report(post_handler_ptr handler)
{
- HANDLER(limit_).on("actual"); // jww (2009-02-27): make this more general
+ // jww (2009-02-27): make this more general
+ HANDLER(limit_).on(string("#generate"), "actual");
+
generate_posts_iterator walker
(session, HANDLED(seed_) ?
static_cast<unsigned int>(HANDLER(seed_).value.to_long()) : 0,
HANDLED(head_) ?
static_cast<unsigned int>(HANDLER(head_).value.to_long()) : 50);
+
pass_down_posts(chain_post_handlers(*this, handler), walker);
}
@@ -126,33 +129,20 @@ value_t report_t::fn_market(call_scope_t& scope)
{
interactive_t args(scope, "a&ts");
- if (args.has(2)) {
- scoped_array<char> buf(new char[args.get<string>(2).length() + 1]);
- std::strcpy(buf.get(), args.get<string>(2).c_str());
-
- for (char * p = std::strtok(buf.get(), ",");
- p;
- p = std::strtok(NULL, ",")) {
- if (commodity_t * commodity = amount_t::current_pool->find(trim_ws(p))) {
- DEBUG("report.market", "Searching for value of " << args.value_at(0)
- << " in terms of commodity " << commodity->symbol());
- value_t result =
- args.value_at(0).value(false, args.has(1) ?
+ value_t result;
+ optional<datetime_t> moment = (args.has(1) ?
args.get<datetime_t>(1) :
- optional<datetime_t>(), *commodity);
- if (! result.is_null()) {
- DEBUG("report.market", "Market value is = " << result);
- return result;
- }
- }
- }
- } else {
- value_t result =
- args.value_at(0).value(true, args.has(1) ?
- args.get<datetime_t>(1) : optional<datetime_t>());
- if (! result.is_null())
- return result;
- }
+ optional<datetime_t>());
+ if (args.has(2))
+ result = args.value_at(0).exchange_commodities(args.get<string>(2),
+ /* add_prices= */ false,
+ moment);
+ else
+ result = args.value_at(0).value(true, moment);
+
+ if (! result.is_null())
+ return result;
+
return args.value_at(0);
}
@@ -227,13 +217,14 @@ value_t report_t::fn_truncated(call_scope_t& scope)
value_t report_t::fn_justify(call_scope_t& scope)
{
- interactive_t args(scope, "vl&lbs");
+ interactive_t args(scope, "vl&lbbs");
std::ostringstream out;
args.value_at(0)
.print(out, args.get<long>(1),
args.has(2) ? args.get<long>(2) : -1,
- args.has(3),
- args.has(4) ? args.get<string>(4) :
+ args.has(3) ? args.get<bool>(3) : false,
+ args.has(4) ? args.get<bool>(4) : false,
+ args.has(5) ? args.get<string>(5) :
(HANDLED(date_format_) ?
HANDLER(date_format_).str() : optional<string>()));
return string_value(out.str());
@@ -255,22 +246,26 @@ value_t report_t::fn_quoted(call_scope_t& args)
return string_value(out.str());
}
-value_t report_t::fn_join(call_scope_t& args)
+value_t report_t::fn_join(call_scope_t& scope)
{
+ interactive_t args(scope, "s");
+
std::ostringstream out;
- foreach (const char ch, args[0].to_string())
+ foreach (const char ch, args.get<string>(0)) {
if (ch != '\n')
out << ch;
else
out << "\\n";
-
+ }
return string_value(out.str());
}
-value_t report_t::fn_format_date(call_scope_t& args)
+value_t report_t::fn_format_date(call_scope_t& scope)
{
- return string_value(format_date(args[0].to_date(), args[1].to_string()));
+ interactive_t args(scope, "ds");
+ return string_value(format_date(args.get<date_t>(0),
+ args.get<string>(1)));
}
value_t report_t::fn_ansify_if(call_scope_t& scope)
@@ -299,6 +294,19 @@ value_t report_t::fn_ansify_if(call_scope_t& scope)
}
}
+value_t report_t::fn_percent(call_scope_t& scope)
+{
+ interactive_t args(scope, "aa");
+ return (amount_t("100.00%") *
+ (args.get<amount_t>(0) / args.get<amount_t>(1)).number());
+}
+
+value_t report_t::fn_price(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ return args.value_at(0).price();
+}
+
namespace {
value_t fn_black(call_scope_t&) {
return string_value("black");
@@ -349,10 +357,12 @@ namespace {
shared_ptr<item_handler<Type> > handler;
report_t& report;
+ string whence;
public:
- reporter(item_handler<Type> * _handler, report_t& _report)
- : handler(_handler), report(_report) {}
+ reporter(item_handler<Type> * _handler, report_t& _report,
+ const string& _whence)
+ : handler(_handler), report(_report), whence(_whence) {}
value_t operator()(call_scope_t& args)
{
@@ -365,7 +375,7 @@ namespace {
string limit = args_to_predicate_expr(begin, end);
if (! limit.empty())
- report.HANDLER(limit_).on(limit);
+ report.HANDLER(limit_).on(whence, limit);
DEBUG("report.predicate",
"Predicate = " << report.HANDLER(limit_).str());
@@ -375,7 +385,7 @@ namespace {
display = args_to_predicate_expr(begin, end);
if (! display.empty())
- report.HANDLER(display_).on(display);
+ report.HANDLER(display_).on(whence, display);
DEBUG("report.predicate",
"Display predicate = " << report.HANDLER(display_).str());
@@ -408,7 +418,7 @@ option_t<report_t> * report_t::lookup_option(const char * p)
{
switch (*p) {
case '%':
- OPT_CH(percentage);
+ OPT_CH(percent);
break;
case 'A':
OPT_CH(average);
@@ -473,9 +483,6 @@ option_t<report_t> * report_t::lookup_option(const char * p)
case 'Y':
OPT_CH(yearly);
break;
- case 'Z':
- OPT_CH(price_exp_);
- break;
case 'a':
OPT(abbrev_len_);
else OPT(account_);
@@ -556,7 +563,6 @@ option_t<report_t> * report_t::lookup_option(const char * p)
else OPT(lots);
else OPT(lots_actual);
else OPT_ALT(tail_, last_);
- else OPT_ALT(price_exp_, leeway_);
break;
case 'm':
OPT(market);
@@ -564,6 +570,7 @@ option_t<report_t> * report_t::lookup_option(const char * p)
break;
case 'n':
OPT_CH(collapse);
+ else OPT(no_color);
else OPT(no_total);
break;
case 'o':
@@ -574,13 +581,12 @@ option_t<report_t> * report_t::lookup_option(const char * p)
OPT(pager_);
else OPT(payee_as_account);
else OPT(pending);
- else OPT(percentage);
+ else OPT(percent);
else OPT_(period_);
- else OPT(period_sort_);
+ else OPT_ALT(sort_xacts_, period_sort_);
else OPT(plot_amount_format_);
else OPT(plot_total_format_);
else OPT(price);
- else OPT(price_exp_);
else OPT(prices_format_);
else OPT(pricesdb_format_);
else OPT(print_format_);
@@ -598,11 +604,14 @@ option_t<report_t> * report_t::lookup_option(const char * p)
else OPT(related_all);
else OPT(revalued);
else OPT(revalued_only);
+#if 0
+ // This option is not available to users
+ else OPT(revalued_total_);
+#endif
break;
case 's':
OPT(set_account_);
else OPT(set_payee_);
- else OPT(set_price_);
else OPT(sort_);
else OPT(sort_all_);
else OPT(sort_xacts_);
@@ -675,7 +684,7 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
return expr_t::op_t::wrap_functor
(reporter<account_t, acct_handler_ptr, &report_t::accounts_report>
(new format_accounts(*this, report_format(HANDLER(balance_format_))),
- *this));
+ *this, "#balance"));
break;
case 'c':
@@ -683,7 +692,7 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
return WRAP_FUNCTOR
(reporter<>
(new format_posts(*this, report_format(HANDLER(csv_format_))),
- *this));
+ *this, "#csv"));
break;
case 'e':
@@ -691,12 +700,12 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
return WRAP_FUNCTOR
(reporter<>
(new format_posts(*this, report_format(HANDLER(print_format_))),
- *this));
- else if (is_eq(q, "xact") || is_eq(q, "entry"))
+ *this, "#equity"));
+ else if (is_eq(q, "entry"))
return WRAP_FUNCTOR(xact_command);
else if (is_eq(q, "emacs"))
return WRAP_FUNCTOR
- (reporter<>(new format_emacs_posts(output_stream), *this));
+ (reporter<>(new format_emacs_posts(output_stream), *this, "#emacs"));
break;
case 'p':
@@ -704,17 +713,17 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
return WRAP_FUNCTOR
(reporter<>
(new format_posts(*this, report_format(HANDLER(print_format_)),
- HANDLED(raw)), *this));
+ HANDLED(raw)), *this, "#print"));
else if (is_eq(q, "prices"))
return expr_t::op_t::wrap_functor
(reporter<post_t, post_handler_ptr, &report_t::commodities_report>
(new format_posts(*this, report_format(HANDLER(prices_format_))),
- *this));
+ *this, "#prices"));
else if (is_eq(q, "pricesdb"))
return expr_t::op_t::wrap_functor
(reporter<post_t, post_handler_ptr, &report_t::commodities_report>
(new format_posts(*this, report_format(HANDLER(pricesdb_format_))),
- *this));
+ *this, "#pricesdb"));
else if (is_eq(q, "python") && maybe_import("ledger.interp"))
return session.lookup(string(CMD_PREFIX) + "python");
break;
@@ -724,7 +733,7 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
return WRAP_FUNCTOR
(reporter<>
(new format_posts(*this, report_format(HANDLER(register_format_))),
- *this));
+ *this, "#register"));
else if (is_eq(q, "reload"))
return MAKE_FUNCTOR(report_t::reload_command);
break;
@@ -736,6 +745,11 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
if (is_eq(q, "server") && maybe_import("ledger.server"))
return session.lookup(string(CMD_PREFIX) + "server");
break;
+
+ case 'x':
+ if (is_eq(q, "xact"))
+ return WRAP_FUNCTOR(xact_command);
+ break;
}
}
else if (is_eq(p, "cyan"))
@@ -747,6 +761,8 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
return MAKE_FUNCTOR(report_t::fn_display_amount);
else if (is_eq(p, "display_total"))
return MAKE_FUNCTOR(report_t::fn_display_total);
+ else if (is_eq(p, "date"))
+ return MAKE_FUNCTOR(report_t::fn_now);
break;
case 'f':
@@ -783,6 +799,8 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
case 'n':
if (is_eq(p, "null"))
return WRAP_FUNCTOR(fn_null);
+ else if (is_eq(p, "now"))
+ return MAKE_FUNCTOR(report_t::fn_now);
break;
case 'o':
@@ -815,7 +833,7 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
return expr_t::op_t::wrap_functor
(reporter<post_t, post_handler_ptr, &report_t::generate_report>
(new format_posts(*this, report_format(HANDLER(print_format_)),
- false), *this));
+ false), *this, "#generate"));
case 'h':
if (is_eq(q, "hello") && maybe_import("ledger.hello"))
return session.lookup(string(PRECMD_PREFIX) + "hello");
@@ -834,6 +852,10 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
}
else if (is_eq(p, "post"))
return WRAP_FUNCTOR(fn_false);
+ else if (is_eq(p, "percent"))
+ return MAKE_FUNCTOR(report_t::fn_percent);
+ else if (is_eq(p, "price"))
+ return MAKE_FUNCTOR(report_t::fn_price);
break;
case 'q':
@@ -862,6 +884,8 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
return MAKE_FUNCTOR(report_t::fn_truncated);
else if (is_eq(p, "total_expr"))
return MAKE_FUNCTOR(report_t::fn_total_expr);
+ else if (is_eq(p, "today"))
+ return MAKE_FUNCTOR(report_t::fn_today);
break;
case 'u':
diff --git a/src/report.h b/src/report.h
index 5eb2a706..71dc4ca2 100644
--- a/src/report.h
+++ b/src/report.h
@@ -52,6 +52,8 @@
#include "stream.h"
#include "option.h"
#include "commodity.h"
+#include "annotate.h"
+#include "format.h"
namespace ledger {
@@ -119,10 +121,12 @@ public:
#define BUDGET_BUDGETED 0x01
#define BUDGET_UNBUDGETED 0x02
+ datetime_t terminus;
uint_least8_t budget_flags;
explicit report_t(session_t& _session)
- : session(_session), budget_flags(BUDGET_NO_BUDGET) {}
+ : session(_session), terminus(CURRENT_TIME()),
+ budget_flags(BUDGET_NO_BUDGET) {}
virtual ~report_t() {
output_stream.close();
@@ -153,6 +157,15 @@ public:
value_t fn_join(call_scope_t& scope);
value_t fn_format_date(call_scope_t& scope);
value_t fn_ansify_if(call_scope_t& scope);
+ value_t fn_percent(call_scope_t& scope);
+ value_t fn_price(call_scope_t& scope);
+
+ value_t fn_now(call_scope_t&) {
+ return terminus;
+ }
+ value_t fn_today(call_scope_t&) {
+ return terminus.date();
+ }
value_t fn_options(call_scope_t&) {
return value_t(static_cast<scope_t *>(this));
@@ -176,6 +189,110 @@ public:
bool maybe_import(const string& module);
+ void report_options(std::ostream& out)
+ {
+ HANDLER(abbrev_len_).report(out);
+ HANDLER(account_).report(out);
+ HANDLER(actual).report(out);
+ HANDLER(add_budget).report(out);
+ HANDLER(amount_).report(out);
+ HANDLER(amount_data).report(out);
+ HANDLER(anon).report(out);
+ HANDLER(average).report(out);
+ HANDLER(balance_format_).report(out);
+ HANDLER(base).report(out);
+ HANDLER(basis).report(out);
+ HANDLER(begin_).report(out);
+ HANDLER(budget).report(out);
+ HANDLER(by_payee).report(out);
+ HANDLER(cleared).report(out);
+ HANDLER(code_as_payee).report(out);
+ HANDLER(comm_as_payee).report(out);
+ HANDLER(code_as_account).report(out);
+ HANDLER(comm_as_account).report(out);
+ HANDLER(color).report(out);
+ HANDLER(collapse).report(out);
+ HANDLER(collapse_if_zero).report(out);
+ HANDLER(columns_).report(out);
+ HANDLER(csv_format_).report(out);
+ HANDLER(current).report(out);
+ HANDLER(daily).report(out);
+ HANDLER(date_format_).report(out);
+ HANDLER(depth_).report(out);
+ HANDLER(deviation).report(out);
+ HANDLER(display_).report(out);
+ HANDLER(display_amount_).report(out);
+ HANDLER(display_total_).report(out);
+ HANDLER(dow).report(out);
+ HANDLER(effective).report(out);
+ HANDLER(empty).report(out);
+ HANDLER(end_).report(out);
+ HANDLER(equity).report(out);
+ HANDLER(exact).report(out);
+ HANDLER(exchange_).report(out);
+ HANDLER(flat).report(out);
+ HANDLER(forecast_while_).report(out);
+ HANDLER(format_).report(out);
+ HANDLER(gain).report(out);
+ HANDLER(head_).report(out);
+ HANDLER(invert).report(out);
+ HANDLER(limit_).report(out);
+ HANDLER(lot_dates).report(out);
+ HANDLER(lot_prices).report(out);
+ HANDLER(lot_tags).report(out);
+ HANDLER(lots).report(out);
+ HANDLER(lots_actual).report(out);
+ HANDLER(market).report(out);
+ HANDLER(monthly).report(out);
+ HANDLER(no_total).report(out);
+ HANDLER(only_).report(out);
+ HANDLER(output_).report(out);
+ HANDLER(pager_).report(out);
+ HANDLER(payee_as_account).report(out);
+ HANDLER(pending).report(out);
+ HANDLER(percent).report(out);
+ HANDLER(period_).report(out);
+ HANDLER(plot_amount_format_).report(out);
+ HANDLER(plot_total_format_).report(out);
+ HANDLER(price).report(out);
+ HANDLER(prices_format_).report(out);
+ HANDLER(pricesdb_format_).report(out);
+ HANDLER(print_format_).report(out);
+ HANDLER(quantity).report(out);
+ HANDLER(quarterly).report(out);
+ HANDLER(raw).report(out);
+ HANDLER(real).report(out);
+ HANDLER(register_format_).report(out);
+ HANDLER(related).report(out);
+ HANDLER(related_all).report(out);
+ HANDLER(revalued).report(out);
+ HANDLER(revalued_only).report(out);
+ HANDLER(revalued_total_).report(out);
+ HANDLER(seed_).report(out);
+ HANDLER(set_account_).report(out);
+ HANDLER(set_payee_).report(out);
+ HANDLER(sort_).report(out);
+ HANDLER(sort_all_).report(out);
+ HANDLER(sort_xacts_).report(out);
+ HANDLER(start_of_week_).report(out);
+ HANDLER(subtotal).report(out);
+ HANDLER(tail_).report(out);
+ HANDLER(total_).report(out);
+ HANDLER(total_data).report(out);
+ HANDLER(truncate_).report(out);
+ HANDLER(unbudgeted).report(out);
+ HANDLER(uncleared).report(out);
+ HANDLER(unround).report(out);
+ HANDLER(weekly).report(out);
+ HANDLER(wide).report(out);
+ HANDLER(yearly).report(out);
+ HANDLER(date_width_).report(out);
+ HANDLER(payee_width_).report(out);
+ HANDLER(account_width_).report(out);
+ HANDLER(amount_width_).report(out);
+ HANDLER(total_width_).report(out);
+ }
+
option_t<report_t> * lookup_option(const char * p);
virtual void define(const string& name, expr_t::ptr_op_t def);
@@ -187,11 +304,11 @@ public:
*/
OPTION__(report_t, abbrev_len_,
- CTOR(report_t, abbrev_len_) { on_with(2L); });
+ CTOR(report_t, abbrev_len_) { on_with(none, 2L); });
OPTION(report_t, account_);
OPTION_(report_t, actual, DO() { // -L
- parent->HANDLER(limit_).on("actual");
+ parent->HANDLER(limit_).on(string("--actual"), "actual");
});
OPTION_(report_t, add_budget, DO() {
@@ -202,53 +319,50 @@ public:
(report_t, amount_, // -t
expr_t expr;
CTOR(report_t, amount_) {
- set_expr("amount");
+ set_expr(none, "amount");
}
- void set_expr(const string& str) {
+ void set_expr(const optional<string>& whence, const string& str) {
expr = str;
- on(str);
+ on(whence, str);
}
DO_(args) {
- set_expr(args[0].to_string());
+ set_expr(args[0].to_string(), args[1].to_string());
});
- OPTION_(report_t, amount_data, DO() { // -j
- parent->HANDLER(format_).on_with(parent->HANDLER(plot_amount_format_).value);
- });
-
+ OPTION(report_t, amount_data);
OPTION(report_t, anon);
OPTION_(report_t, average, DO() { // -A
- parent->HANDLER(display_total_).set_expr("total_expr/count");
+ parent->HANDLER(display_total_)
+ .set_expr(string("--average"), "total_expr/count");
});
OPTION__(report_t, balance_format_, CTOR(report_t, balance_format_) {
- on("%(ansify_if(justify(scrub(display_total), 20, -1, true), "
- " red if color & scrub(display_total) < 0))"
+ on(none,
+ "%(justify(scrub(display_total), 20, -1, true, color))"
" %(!options.flat ? depth_spacer : \"\")"
"%-(ansify_if(partial_account(options.flat), blue if color))\n%/"
- "%(ansify_if(justify(scrub(display_total), 20, -1, true), "
- " red if color & scrub(display_total) < 0))\n%/"
+ "%(justify(scrub(display_total), 20, -1, true, color))\n%/"
"--------------------\n");
});
OPTION(report_t, base);
OPTION_(report_t, basis, DO() { // -B
- parent->HANDLER(revalued).on_only();
- parent->HANDLER(amount_).set_expr("rounded(cost)");
+ parent->HANDLER(revalued).on_only(string("--basis"));
+ parent->HANDLER(amount_).set_expr(string("--basis"), "rounded(cost)");
});
OPTION_(report_t, begin_, DO_(args) { // -b
- date_interval_t interval(args[0].to_string());
+ date_interval_t interval(args[1].to_string());
if (! interval.start)
throw_(std::invalid_argument,
_("Could not determine beginning of period '%1'")
- << args[0].to_string());
+ << args[1].to_string());
string predicate =
"date>=[" + to_iso_extended_string(*interval.start) + "]";
- parent->HANDLER(limit_).on(predicate);
+ parent->HANDLER(limit_).on(string("--begin"), predicate);
});
OPTION_(report_t, budget, DO() {
@@ -258,7 +372,7 @@ public:
OPTION(report_t, by_payee); // -P
OPTION_(report_t, cleared, DO() { // -C
- parent->HANDLER(limit_).on("cleared");
+ parent->HANDLER(limit_).on(string("--cleared"), "cleared");
});
OPTION(report_t, code_as_payee);
@@ -270,17 +384,18 @@ public:
OPTION_(report_t, collapse, DO() { // -n
// Make sure that balance reports are collapsed too, but only apply it
// to account xacts
- parent->HANDLER(display_).on("post|depth<=1");
+ parent->HANDLER(display_).on(string("--collapse"), "post|depth<=1");
});
OPTION_(report_t, collapse_if_zero, DO() {
- parent->HANDLER(collapse).on_only();
+ parent->HANDLER(collapse).on_only(string("--collapse-if-zero"));
});
OPTION(report_t, columns_);
OPTION__(report_t, csv_format_, CTOR(report_t, csv_format_) {
- on("%(quoted(date)),"
+ on(none,
+ "%(quoted(date)),"
"%(quoted(payee)),"
"%(quoted(account)),"
"%(quoted(scrub(display_amount))),"
@@ -290,35 +405,38 @@ public:
});
OPTION_(report_t, current, DO() { // -c
- parent->HANDLER(limit_).on("date<=today");
+ parent->HANDLER(limit_).on(string("--current"), "date<=today");
});
OPTION_(report_t, daily, DO() {
- parent->HANDLER(period_).on("daily");
+ parent->HANDLER(period_).on(string("--daily"), "daily");
});
OPTION__(report_t, date_format_, // -y
CTOR(report_t, date_format_) {
- on("%y-%b-%d");
+ on(none, "%y-%b-%d");
});
OPTION_(report_t, depth_, DO_(scope) {
- interactive_t args(scope, "l");
- parent->HANDLER(display_).on(string("depth<=") + args.get<string>(0));
+ interactive_t args(scope, "sl");
+ parent->HANDLER(display_).on(string("--depth"),
+ string("depth<=") + args.get<string>(1));
});
OPTION_(report_t, deviation, DO() { // -D
- parent->HANDLER(display_total_).set_expr("amount_expr-total_expr/count");
+ parent->HANDLER(display_total_)
+ .set_expr(string("--deviation"), "amount_expr-total_expr/count");
});
OPTION__
(report_t, display_, // -d
CTOR(report_t, display_) {}
- virtual void on_with(const value_t& text) {
+ virtual void on_with(const optional<string>& whence, const value_t& text) {
if (! handled)
- option_t<report_t>::on_with(text);
+ option_t<report_t>::on_with(whence, text);
else
- option_t<report_t>::on_with(string_value(string("(") + str() + ")&(" +
+ option_t<report_t>::on_with(whence,
+ string_value(string("(") + str() + ")&(" +
text.as_string() + ")"));
});
@@ -326,28 +444,28 @@ public:
(report_t, display_amount_,
expr_t expr;
CTOR(report_t, display_amount_) {
- set_expr("amount_expr");
+ set_expr(none, "amount_expr");
}
- void set_expr(const string& str) {
+ void set_expr(const optional<string>& whence, const string& str) {
expr = str;
- on(str);
+ on(whence, str);
}
DO_(args) {
- set_expr(args[0].to_string());
+ set_expr(args[0].to_string(), args[1].to_string());
});
OPTION__
(report_t, display_total_,
expr_t expr;
CTOR(report_t, display_total_) {
- set_expr("total_expr");
+ set_expr(none, "total_expr");
}
- void set_expr(const string& str) {
+ void set_expr(const optional<string>& whence, const string& str) {
expr = str;
- on(str);
+ on(whence, str);
}
DO_(args) {
- set_expr(args[0].to_string());
+ set_expr(args[0].to_string(), args[1].to_string());
});
OPTION(report_t, dow);
@@ -355,26 +473,26 @@ public:
OPTION(report_t, empty); // -E
OPTION_(report_t, end_, DO_(args) { // -e
- date_interval_t interval(args[0].to_string());
+ date_interval_t interval(args[1].to_string());
if (! interval.start)
throw_(std::invalid_argument,
_("Could not determine end of period '%1'")
- << args[0].to_string());
+ << args[1].to_string());
string predicate =
"date<[" + to_iso_extended_string(*interval.start) + "]";
- parent->HANDLER(limit_).on(predicate);
-#if 0
- terminus = interval.begin;
-#endif
+ parent->HANDLER(limit_).on(string("--end"), predicate);
+
+ parent->terminus = datetime_t(*interval.start);
});
OPTION(report_t, equity);
OPTION(report_t, exact);
OPTION_(report_t, exchange_, DO_(args) { // -X
- on_with(args[0]);
+ on_with(args[0].as_string(), args[1]);
call_scope_t no_args(*parent);
+ no_args.push_back(args[0]);
parent->HANDLER(market).parent = parent;
parent->HANDLER(market).handler(no_args);
});
@@ -384,21 +502,24 @@ public:
OPTION(report_t, format_); // -F
OPTION_(report_t, gain, DO() { // -G
- parent->HANDLER(revalued).on_only();
- parent->HANDLER(amount_).set_expr("(amount, cost)");
+ parent->HANDLER(revalued).on_only(string("--gain"));
+ parent->HANDLER(amount_).set_expr(string("--gain"), "(amount, cost)");
// Since we are displaying the amounts of revalued postings, they
// will end up being composite totals, and hence a pair of pairs.
parent->HANDLER(display_amount_)
- .set_expr("use_direct_amount ? amount :"
+ .set_expr(string("--gain"),
+ "use_direct_amount ? amount :"
" (is_seq(get_at(amount_expr, 0)) ?"
" get_at(get_at(amount_expr, 0), 0) :"
" market(get_at(amount_expr, 0), date, exchange)"
" - get_at(amount_expr, 1))");
parent->HANDLER(revalued_total_)
- .set_expr("(market(get_at(total_expr, 0), date, exchange), "
+ .set_expr(string("--gain"),
+ "(market(get_at(total_expr, 0), date, exchange), "
"get_at(total_expr, 1))");
parent->HANDLER(display_total_)
- .set_expr("use_direct_amount ? total_expr :"
+ .set_expr(string("--gain"),
+ "use_direct_amount ? total_expr :"
" market(get_at(total_expr, 0), date, exchange)"
" - get_at(total_expr, 1)");
});
@@ -406,17 +527,18 @@ public:
OPTION(report_t, head_);
OPTION_(report_t, invert, DO() {
- parent->HANDLER(amount_).set_expr("-amount");
+ parent->HANDLER(amount_).set_expr(string("--invert"), "-amount");
});
OPTION__
(report_t, limit_, // -l
CTOR(report_t, limit_) {}
- virtual void on_with(const value_t& text) {
+ virtual void on_with(const optional<string>& whence, const value_t& text) {
if (! handled)
- option_t<report_t>::on_with(text);
+ option_t<report_t>::on_with(whence, text);
else
- option_t<report_t>::on_with(string_value(string("(") + str() + ")&(" +
+ option_t<report_t>::on_with(whence,
+ string_value(string("(") + str() + ")&(" +
text.as_string() + ")"));
});
@@ -427,15 +549,19 @@ public:
OPTION(report_t, lots_actual);
OPTION_(report_t, market, DO() { // -V
- parent->HANDLER(revalued).on_only();
+ parent->HANDLER(revalued).on_only(string("--market"));
parent->HANDLER(display_amount_)
- .set_expr("market(amount_expr, date, exchange)");
+ .set_expr(string("--market"), "market(amount_expr, date, exchange)");
parent->HANDLER(display_total_)
- .set_expr("market(total_expr, date, exchange)");
+ .set_expr(string("--market"), "market(total_expr, date, exchange)");
});
OPTION_(report_t, monthly, DO() { // -M
- parent->HANDLER(period_).on("monthly");
+ parent->HANDLER(period_).on(string("--monthly"), "monthly");
+ });
+
+ OPTION_(report_t, no_color, DO() {
+ parent->HANDLER(color).off();
});
OPTION(report_t, no_total);
@@ -443,11 +569,12 @@ public:
OPTION__
(report_t, only_,
CTOR(report_t, only_) {}
- virtual void on_with(const value_t& text) {
+ virtual void on_with(const optional<string>& whence, const value_t& text) {
if (! handled)
- option_t<report_t>::on_with(text);
+ option_t<report_t>::on_with(whence, text);
else
- option_t<report_t>::on_with(string_value(string("(") + str() + ")&(" +
+ option_t<report_t>::on_with(whence,
+ string_value(string("(") + str() + ")&(" +
text.as_string() + ")"));
});
@@ -456,48 +583,57 @@ public:
OPTION(report_t, payee_as_account);
OPTION_(report_t, pending, DO() { // -C
- parent->HANDLER(limit_).on("pending");
+ parent->HANDLER(limit_).on(string("--pending"), "pending");
});
- OPTION(report_t, percentage); // -%
+ OPTION_(report_t, percent, DO() { // -%
+ parent->HANDLER(total_)
+ .set_expr(string("--percent"),
+ "is_account&parent&parent.total&percent(total, parent.total)");
+ });
OPTION__
(report_t, period_, // -p
CTOR(report_t, period_) {}
- virtual void on_with(const value_t& text) {
+ virtual void on_with(const optional<string>& whence, const value_t& text) {
if (! handled)
- option_t<report_t>::on_with(text);
+ option_t<report_t>::on_with(whence, text);
else
- option_t<report_t>::on_with(string_value(text.as_string() + " " + str()));
+ option_t<report_t>::on_with(whence,
+ string_value(text.as_string() + " " + str()));
});
- OPTION(report_t, period_sort_);
-
OPTION__(report_t, plot_amount_format_, CTOR(report_t, plot_amount_format_) {
- on("%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_amount)))\n");
+ on(none,
+ "%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_amount)))\n");
});
OPTION__(report_t, plot_total_format_, CTOR(report_t, plot_total_format_) {
- on("%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_total)))\n");
+ on(none,
+ "%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_total)))\n");
});
OPTION_(report_t, price, DO() { // -I
- parent->HANDLER(revalued).off();
- parent->HANDLER(amount_).set_expr("price");
+ parent->HANDLER(display_amount_)
+ .set_expr(string("--price"), "price(amount_expr)");
+ parent->HANDLER(display_total_)
+ .set_expr(string("--price"), "price(total_expr)");
});
- OPTION(report_t, price_exp_); // -Z
-
OPTION__(report_t, prices_format_, CTOR(report_t, prices_format_) {
- on("%-.9(date) %-8(account) %12(scrub(display_amount))\n");
+ on(none,
+ "%-.9(date) %-8(account) %(justify(scrub(display_amount), 12, "
+ " 2 + 9 + 8 + 12, true, color))\n");
});
OPTION__(report_t, pricesdb_format_, CTOR(report_t, pricesdb_format_) {
- on("P %[%Y/%m/%d %H:%M:%S] %A %t\n");
+ on(none,
+ "P %(datetime) %(account) %(scrub(display_amount))\n");
});
OPTION__(report_t, print_format_, CTOR(report_t, print_format_) {
- on("%(format_date(xact.date, \"%Y/%m/%d\"))"
+ on(none,
+ "%(format_date(xact.date, \"%Y/%m/%d\"))"
"%(!effective & xact.effective_date ?"
" \"=\" + format_date(xact.effective_date, \"%Y/%m/%d\") : \"\")"
"%(xact.cleared ? \" *\" : (xact.pending ? \" !\" : \"\"))"
@@ -521,45 +657,50 @@ public:
OPTION_(report_t, quantity, DO() { // -O
parent->HANDLER(revalued).off();
- parent->HANDLER(amount_).set_expr("amount");
- parent->HANDLER(total_).set_expr("total");
+ parent->HANDLER(amount_).set_expr(string("--quantity"), "amount");
+ parent->HANDLER(total_).set_expr(string("--quantity"), "total");
});
OPTION_(report_t, quarterly, DO() {
- parent->HANDLER(period_).on("quarterly");
+ parent->HANDLER(period_).on(string("--quarterly"), "quarterly");
});
OPTION(report_t, raw);
OPTION_(report_t, real, DO() { // -R
- parent->HANDLER(limit_).on("real");
+ parent->HANDLER(limit_).on(string("--real"), "real");
});
OPTION__(report_t, register_format_, CTOR(report_t, register_format_) {
- on("%(ansify_if(justify(date, date_width), green if color & date > today))"
+ on(none,
+ "%(ansify_if(justify(date, date_width), green if color & date > today))"
" %(ansify_if(justify(truncated(payee, payee_width), payee_width), "
" bold if color & !cleared))"
" %(ansify_if(justify(truncated(account, account_width, abbrev_len), "
" account_width), blue if color))"
- " %(ansify_if(justify(scrub(display_amount), amount_width, "
- " 3 + date_width + payee_width + account_width + amount_width, true), "
- " red if color & scrub(display_amount) < 0))"
- " %(ansify_if(justify(scrub(display_total), total_width, "
+ " %(justify(scrub(display_amount), amount_width, "
+ " 3 + date_width + payee_width + account_width + amount_width, "
+ " true, color))"
+ " %(justify(scrub(display_total), total_width, "
" 4 + date_width + payee_width + account_width + amount_width "
- " + total_width, true), red if color & scrub(display_amount) < 0))\n%/"
+ " + total_width, true, color))\n%/"
"%(justify(\" \", 2 + date_width + payee_width))"
"%(ansify_if(justify(truncated(account, account_width, abbrev_len), "
" account_width), blue if color))"
- " %(ansify_if(justify(scrub(display_amount), amount_width, "
- " 3 + date_width + payee_width + account_width + amount_width, true), "
- " red if color & scrub(display_amount) < 0))"
- " %(ansify_if(justify(scrub(display_total), total_width, "
+ " %(justify(scrub(display_amount), amount_width, "
+ " 3 + date_width + payee_width + account_width + amount_width, "
+ " true, color))"
+ " %(justify(scrub(display_total), total_width, "
" 4 + date_width + payee_width + account_width + amount_width "
- " + total_width, true), red if color & scrub(display_amount) < 0))\n");
+ " + total_width, true, color))\n");
});
OPTION(report_t, related); // -r
- OPTION(report_t, related_all);
+
+ OPTION_(report_t, related_all, DO() {
+ parent->HANDLER(related).on_only(string("--related-all"));
+ });
+
OPTION(report_t, revalued);
OPTION(report_t, revalued_only);
@@ -567,32 +708,31 @@ public:
(report_t, revalued_total_,
expr_t expr;
CTOR(report_t, revalued_total_) {}
- void set_expr(const string& str) {
+ void set_expr(const optional<string>& whence, const string& str) {
expr = str;
- on(str);
+ on(whence, str);
}
DO_(args) {
- set_expr(args[0].to_string());
+ set_expr(args[0].to_string(), args[1].to_string());
});
OPTION(report_t, seed_);
OPTION(report_t, set_account_);
OPTION(report_t, set_payee_);
- OPTION(report_t, set_price_);
OPTION_(report_t, sort_, DO_(args) { // -S
- on_with(args[0]);
+ on_with(args[0].as_string(), args[1]);
parent->HANDLER(sort_xacts_).off();
parent->HANDLER(sort_all_).off();
});
OPTION_(report_t, sort_all_, DO_(args) {
- parent->HANDLER(sort_).on_with(args[0]);
+ parent->HANDLER(sort_).on_with(string("--sort-all"), args[1]);
parent->HANDLER(sort_xacts_).off();
});
OPTION_(report_t, sort_xacts_, DO_(args) {
- parent->HANDLER(sort_).on_with(args[0]);
+ parent->HANDLER(sort_).on_with(string("--sort-xacts"), args[1]);
parent->HANDLER(sort_all_).off();
});
@@ -604,32 +744,30 @@ public:
(report_t, total_, // -T
expr_t expr;
CTOR(report_t, total_) {
- set_expr("total");
+ set_expr(none, "total");
}
- void set_expr(const string& str) {
+ void set_expr(const optional<string>& whence, const string& str) {
expr = str;
- on(str);
+ on(whence, str);
}
DO_(args) {
- set_expr(args[0].to_string());
+ set_expr(args[0].to_string(), args[1].to_string());
});
- OPTION_(report_t, total_data, DO() { // -J
- parent->HANDLER(format_).on_with(parent->HANDLER(plot_total_format_).value);
- });
+ OPTION(report_t, total_data);
- OPTION_(report_t, truncate_, DO() {
-#if 0
- string style(args[0].to_string());
+ OPTION_(report_t, truncate_, DO_(args) {
+ string style(args[1].to_string());
if (style == "leading")
- format_t::elision_style = format_t::TRUNCATE_LEADING;
+ format_t::default_style = format_t::TRUNCATE_LEADING;
else if (style == "middle")
- format_t::elision_style = format_t::TRUNCATE_MIDDLE;
+ format_t::default_style = format_t::TRUNCATE_MIDDLE;
else if (style == "trailing")
- format_t::elision_style = format_t::TRUNCATE_TRAILING;
- else if (style == "abbrev")
- format_t::elision_style = format_t::ABBREVIATE;
-#endif
+ format_t::default_style = format_t::TRUNCATE_TRAILING;
+ else
+ throw_(std::invalid_argument,
+ _("Unrecognized truncation style: '%1'") << style);
+ format_t::default_style_changed = true;
});
OPTION_(report_t, unbudgeted, DO() {
@@ -637,54 +775,72 @@ public:
});
OPTION_(report_t, uncleared, DO() { // -U
- parent->HANDLER(limit_).on("uncleared|pending");
+ parent->HANDLER(limit_).on(string("--uncleared"), "uncleared|pending");
});
OPTION_(report_t, unround, DO() {
- parent->HANDLER(amount_).set_expr("unrounded(amount)");
+ parent->HANDLER(display_amount_)
+ .set_expr(string("--unround"), "unrounded(amount_expr)");
+ parent->HANDLER(display_total_)
+ .set_expr(string("--unround"), "unrounded(total_expr)");
});
OPTION_(report_t, weekly, DO() { // -W
- parent->HANDLER(period_).on("weekly");
+ parent->HANDLER(period_).on(string("--weekly"), "weekly");
});
OPTION_(report_t, wide, DO() { // -w
- parent->HANDLER(date_width_).on_with(9L);
+ parent->HANDLER(date_width_).on_with(string("--wide"), 9L);
parent->HANDLER(date_width_).specified = true;
- parent->HANDLER(payee_width_).on_with(35L);
+ parent->HANDLER(payee_width_).on_with(string("--wide"), 35L);
parent->HANDLER(payee_width_).specified = true;
- parent->HANDLER(account_width_).on_with(39L);
+ parent->HANDLER(account_width_).on_with(string("--wide"), 39L);
parent->HANDLER(account_width_).specified = true;
- parent->HANDLER(amount_width_).on_with(22L);
+ parent->HANDLER(amount_width_).on_with(string("--wide"), 22L);
parent->HANDLER(amount_width_).specified = true;
- parent->HANDLER(total_width_).on_with(22L);
+ parent->HANDLER(total_width_).on_with(string("--wide"), 22L);
parent->HANDLER(total_width_).specified = true;
});
OPTION_(report_t, yearly, DO() { // -Y
- parent->HANDLER(period_).on("yearly");
+ parent->HANDLER(period_).on(string("--yearly"), "yearly");
});
OPTION__(report_t, date_width_,
bool specified;
- CTOR(report_t, date_width_) { on_with(9L); specified = false; }
- DO_(args) { value = args[0].to_long(); specified = true; });
+ CTOR(report_t, date_width_) {
+ on_with(none, 9L);
+ specified = false;
+ }
+ DO_(args) { value = args[1].to_long(); specified = true; });
OPTION__(report_t, payee_width_,
bool specified;
- CTOR(report_t, payee_width_) { on_with(20L); specified = false; }
- DO_(args) { value = args[0].to_long(); specified = true; });
+ CTOR(report_t, payee_width_) {
+ on_with(none, 20L);
+ specified = false;
+ }
+ DO_(args) { value = args[1].to_long(); specified = true; });
OPTION__(report_t, account_width_,
bool specified;
- CTOR(report_t, account_width_) { on_with(23L); specified = false; }
- DO_(args) { value = args[0].to_long(); specified = true; });
+ CTOR(report_t, account_width_) {
+ on_with(none, 23L);
+ specified = false;
+ }
+ DO_(args) { value = args[1].to_long(); specified = true; });
OPTION__(report_t, amount_width_,
bool specified;
- CTOR(report_t, amount_width_) { on_with(12L); specified = false; }
- DO_(args) { value = args[0].to_long(); specified = true; });
+ CTOR(report_t, amount_width_) {
+ on_with(none, 12L);
+ specified = false;
+ }
+ DO_(args) { value = args[1].to_long(); specified = true; });
OPTION__(report_t, total_width_,
bool specified;
- CTOR(report_t, total_width_) { on_with(12L); specified = false; }
- DO_(args) { value = args[0].to_long(); specified = true; });
+ CTOR(report_t, total_width_) {
+ on_with(none, 12L);
+ specified = false;
+ }
+ DO_(args) { value = args[1].to_long(); specified = true; });
};
} // namespace ledger
diff --git a/src/session.cc b/src/session.cc
index bbc196df..f7a8655b 100644
--- a/src/session.cc
+++ b/src/session.cc
@@ -33,6 +33,7 @@
#include "session.h"
#include "commodity.h"
+#include "pool.h"
#include "xact.h"
#include "account.h"
#include "journal.h"
@@ -71,9 +72,9 @@ session_t::session_t()
TRACE_CTOR(session_t, "");
if (const char * home_var = std::getenv("HOME"))
- HANDLER(price_db_).on((path(home_var) / ".pricedb").string());
+ HANDLER(price_db_).on(none, (path(home_var) / ".pricedb").string());
else
- HANDLER(price_db_).on(path("./.pricedb").string());
+ HANDLER(price_db_).on(none, path("./.pricedb").string());
// Add time commodity conversions, so that timelog's may be parsed
// in terms of seconds, but reported as minutes or hours.
@@ -81,6 +82,12 @@ session_t::session_t()
commodity->add_flags(COMMODITY_BUILTIN | COMMODITY_NOMARKET);
else
assert(false);
+
+ // Add a "percentile" commodity
+ if (commodity_t * commodity = commodity_pool->create("%"))
+ commodity->add_flags(COMMODITY_BUILTIN | COMMODITY_NOMARKET);
+ else
+ assert(false);
}
std::size_t session_t::read_journal(std::istream& in,
@@ -213,6 +220,12 @@ void session_t::clean_accounts()
option_t<session_t> * session_t::lookup_option(const char * p)
{
switch (*p) {
+ case 'Q':
+ OPT_CH(download); // -Q
+ break;
+ case 'Z':
+ OPT_CH(price_exp_);
+ break;
case 'a':
OPT_(account_); // -a
break;
@@ -226,17 +239,15 @@ option_t<session_t> * session_t::lookup_option(const char * p)
OPT(input_date_format_);
break;
case 'l':
- OPT(leeway_);
+ OPT_ALT(price_exp_, leeway_);
break;
case 'p':
OPT(price_db_);
+ else OPT(price_exp_);
break;
case 's':
OPT(strict);
break;
- case 'Q':
- OPT_CH(download); // -Q
- break;
}
return NULL;
}
@@ -245,27 +256,12 @@ expr_t::ptr_op_t session_t::lookup(const string& name)
{
const char * p = name.c_str();
switch (*p) {
- case 'd':
- if (is_eq(p, "date"))
- return MAKE_FUNCTOR(session_t::fn_today);
- break;
-
- case 'n':
- if (is_eq(p, "now"))
- return MAKE_FUNCTOR(session_t::fn_now);
- break;
-
case 'o':
if (WANT_OPT()) { p += OPT_PREFIX_LEN;
if (option_t<session_t> * handler = lookup_option(p))
return MAKE_OPT_HANDLER(session_t, handler);
}
break;
-
- case 't':
- if (is_eq(p, "today"))
- return MAKE_FUNCTOR(session_t::fn_today);
- break;
}
// Check if they are trying to access an option's setting or value.
diff --git a/src/session.h b/src/session.h
index 1d700d1b..5d6a12b9 100644
--- a/src/session.h
+++ b/src/session.h
@@ -101,11 +101,15 @@ public:
clean_accounts();
}
- value_t fn_now(call_scope_t&) {
- return CURRENT_TIME();
- }
- value_t fn_today(call_scope_t&) {
- return CURRENT_DATE();
+ void report_options(std::ostream& out)
+ {
+ HANDLER(account_).report(out);
+ HANDLER(download).report(out);
+ HANDLER(file_).report(out);
+ HANDLER(input_date_format_).report(out);
+ HANDLER(price_db_).report(out);
+ HANDLER(price_exp_).report(out);
+ HANDLER(strict).report(out);
}
option_t<session_t> * lookup_option(const char * p);
@@ -120,10 +124,10 @@ public:
OPTION(session_t, download); // -Q
OPTION__
- (session_t, leeway_,
- CTOR(session_t, leeway_) { value = 24L * 3600L; }
+ (session_t, price_exp_, // -Z
+ CTOR(session_t, price_exp_) { value = 24L * 3600L; }
DO_(args) {
- value = args[0].to_long() * 60L;
+ value = args[1].to_long() * 60L;
});
OPTION__
@@ -131,15 +135,20 @@ public:
std::list<path> data_files;
CTOR(session_t, file_) {}
DO_(args) {
- assert(args.size() == 1);
+ assert(args.size() == 2);
if (parent->flush_on_next_data_file) {
data_files.clear();
parent->flush_on_next_data_file = false;
}
- data_files.push_back(args[0].as_string());
+ data_files.push_back(args[1].as_string());
});
- OPTION(session_t, input_date_format_);
+ OPTION_(session_t, input_date_format_, DO_(args) {
+ // This changes the global variable inside times.h, which affects the
+ // basic date parser
+ input_date_format = args[1].as_string();
+ });
+
OPTION(session_t, price_db_);
OPTION(session_t, strict);
};
diff --git a/src/textual.cc b/src/textual.cc
index 0c92f7bb..35fa0028 100644
--- a/src/textual.cc
+++ b/src/textual.cc
@@ -37,6 +37,7 @@
#include "account.h"
#include "option.h"
#include "pstream.h"
+#include "pool.h"
#define TIMELOG_SUPPORT 1
#if defined(TIMELOG_SUPPORT)
@@ -455,67 +456,18 @@ void instance_t::price_conversion_directive(char * line)
}
}
-namespace {
- void parse_symbol(char *& p, string& symbol)
- {
- if (*p == '"') {
- char * q = std::strchr(p + 1, '"');
- if (! q)
- throw parse_error(_("Quoted commodity symbol lacks closing quote"));
- symbol = string(p + 1, 0, q - p - 1);
- p = q + 2;
- } else {
- char * q = next_element(p);
- symbol = p;
- if (q)
- p = q;
- else
- p += symbol.length();
- }
- if (symbol.empty())
- throw parse_error(_("Failed to parse commodity"));
- }
-}
-
void instance_t::price_xact_directive(char * line)
{
- char * date_field_ptr = skip_ws(line + 1);
- char * time_field_ptr = next_element(date_field_ptr);
- if (! time_field_ptr) return;
- string date_field = date_field_ptr;
-
- char * symbol_and_price;
- datetime_t datetime;
-
- if (std::isdigit(time_field_ptr[0])) {
- symbol_and_price = next_element(time_field_ptr);
- if (! symbol_and_price) return;
- datetime = parse_datetime(date_field + " " + time_field_ptr,
- current_year);
- } else {
- symbol_and_price = time_field_ptr;
- datetime = parse_datetime(date_field, current_year);
- }
-
- string symbol;
- parse_symbol(symbol_and_price, symbol);
- amount_t price(symbol_and_price);
- VERIFY(price.valid());
-
- if (commodity_t * commodity =
- amount_t::current_pool->find_or_create(symbol)) {
- commodity->add_price(datetime, price, true);
- commodity->add_flags(COMMODITY_KNOWN);
- } else {
- assert(false);
- }
+ optional<price_point_t> point =
+ amount_t::current_pool->parse_price_directive(skip_ws(line + 1));
+ assert(point);
}
void instance_t::nomarket_directive(char * line)
{
char * p = skip_ws(line + 1);
string symbol;
- parse_symbol(p, symbol);
+ commodity_t::parse_symbol(p, symbol);
if (commodity_t * commodity =
amount_t::current_pool->find_or_create(symbol))
@@ -535,7 +487,7 @@ void instance_t::option_directive(char * line)
if (p)
*p++ = '\0';
}
- process_option(line + 2, session_scope, p, line);
+ process_option(pathname.string(), line + 2, session_scope, p, line);
}
void instance_t::automated_xact_directive(char * line)
@@ -863,6 +815,9 @@ post_t * instance_t::parse_post(char * line,
char * next = next_element(p, true);
char * e = p + std::strlen(p);
+ while (e > p && std::isspace(*(e - 1)))
+ e--;
+
if ((*p == '[' && *(e - 1) == ']') || (*p == '(' && *(e - 1) == ')')) {
post->add_flags(POST_VIRTUAL);
DEBUG("textual.parse", "line " << linenum << ": "
@@ -966,8 +921,14 @@ post_t * instance_t::parse_post(char * line,
post->cost->in_place_unround();
- if (per_unit)
+ if (per_unit) {
+ // For the sole case where the cost might be uncommoditized,
+ // guarantee that the commodity of the cost after multiplication
+ // is the same as it was before.
+ commodity_t& cost_commodity(post->cost->commodity());
*post->cost *= post->amount;
+ post->cost->set_commodity(cost_commodity);
+ }
DEBUG("textual.parse", "line " << linenum << ": "
<< "Total cost is " << *post->cost);
@@ -1014,7 +975,8 @@ post_t * instance_t::parse_post(char * line,
<< "POST assign: parsed amt = " << *post->assigned_amount);
amount_t& amt(*post->assigned_amount);
- value_t account_total(post->account->self_total(false));
+ value_t account_total(post->account->self_total(false)
+ .strip_annotations(keep_details_t()));
DEBUG("post.assign", "line " << linenum << ": "
"account balance = " << account_total);
diff --git a/src/times.cc b/src/times.cc
index 360f786a..deb51058 100644
--- a/src/times.cc
+++ b/src/times.cc
@@ -649,15 +649,19 @@ void date_interval_t::parse(std::istream& in)
if (! end)
end = *start + gregorian::days(1);
} else {
+ bool overwrite_end = false;
+
if (year) {
start = date_t(*year, 1, 1);
- if (! end)
+ if (! end) {
end = *start + gregorian::years(1);
+ overwrite_end = true;
+ }
}
if (mon) {
start = date_t(start->year(), *mon, 1);
- if (! end)
+ if (! end || overwrite_end)
end = *start + gregorian::months(1);
}
}
diff --git a/src/token.cc b/src/token.cc
index d173c7ec..fde6a0e3 100644
--- a/src/token.cc
+++ b/src/token.cc
@@ -278,11 +278,6 @@ void expr_t::token_t::next(std::istream& in, const uint_least8_t pflags)
kind = STAR;
break;
- case '%':
- in.get(c);
- kind = PERCENT;
- break;
-
case '?':
in.get(c);
kind = QUERY;
diff --git a/src/token.h b/src/token.h
index 85374f6e..a9b75dd4 100644
--- a/src/token.h
+++ b/src/token.h
@@ -84,9 +84,9 @@ struct expr_t::token_t : public noncopyable
KW_DIV, // div
EXCLAM, // !, not
- PERCENT, // %
KW_AND, // &, &&, and
KW_OR, // |, ||, or
+ KW_MOD, // %
KW_IF, // if
KW_ELSE, // else
diff --git a/src/unistring.h b/src/unistring.h
index b3086c39..5467a151 100644
--- a/src/unistring.h
+++ b/src/unistring.h
@@ -99,10 +99,14 @@ public:
inline void justify(std::ostream& out,
const std::string& str,
int width,
- bool right = false)
+ bool right = false,
+ bool redden = false)
{
- if (! right)
+ if (! right) {
+ if (redden) out << "\e[31m";
out << str;
+ if (redden) out << "\e[0m";
+ }
unistring temp(str);
@@ -110,8 +114,11 @@ inline void justify(std::ostream& out,
while (spacing-- > 0)
out << ' ';
- if (right)
+ if (right) {
+ if (redden) out << "\e[31m";
out << str;
+ if (redden) out << "\e[0m";
+ }
}
} // namespace ledger
diff --git a/src/value.cc b/src/value.cc
index 53e2bdeb..702b889e 100644
--- a/src/value.cc
+++ b/src/value.cc
@@ -33,6 +33,8 @@
#include "value.h"
#include "commodity.h"
+#include "annotate.h"
+#include "pool.h"
#include "unistring.h"
namespace ledger {
@@ -573,6 +575,7 @@ value_t& value_t::operator*=(const value_t& val)
return *this;
case AMOUNT:
if (as_amount().commodity() == val.as_amount().commodity() ||
+ ! as_amount().has_commodity() ||
! val.as_amount().has_commodity()) {
as_amount_lval() *= val.as_amount();
return *this;
@@ -1227,6 +1230,39 @@ value_t value_t::value(const bool primary_only,
return NULL_VALUE;
}
+value_t value_t::price() const
+{
+ switch (type()) {
+ case AMOUNT:
+ return as_amount().price();
+ case BALANCE:
+ return as_balance().price();
+ default:
+ return *this;
+ }
+}
+
+value_t value_t::exchange_commodities(const std::string& commodities,
+ const bool add_prices,
+ const optional<datetime_t>& moment)
+{
+ scoped_array<char> buf(new char[commodities.length() + 1]);
+
+ std::strcpy(buf.get(), commodities.c_str());
+
+ for (char * p = std::strtok(buf.get(), ",");
+ p;
+ p = std::strtok(NULL, ",")) {
+ if (commodity_t * commodity =
+ amount_t::current_pool->parse_price_expression(p, add_prices, moment)) {
+ value_t result = value(false, moment, *commodity);
+ if (! result.is_null())
+ return result;
+ }
+ }
+ return *this;
+}
+
void value_t::in_place_reduce()
{
switch (type()) {
@@ -1336,10 +1372,10 @@ void value_t::in_place_unround()
case INTEGER:
return;
case AMOUNT:
- as_amount_lval().unrounded();
+ as_amount_lval().in_place_unround();
return;
case BALANCE:
- as_balance_lval().unrounded();
+ as_balance_lval().in_place_unround();
return;
case SEQUENCE: {
value_t temp;
@@ -1423,10 +1459,12 @@ void value_t::print(std::ostream& out,
const int first_width,
const int latter_width,
const bool right_justify,
+ const bool colorize,
const optional<string>& date_format) const
{
if (first_width > 0 &&
- ! is_amount() && ! is_balance() && ! is_string()) {
+ (! is_amount() || as_amount().is_zero()) &&
+ ! is_balance() && ! is_string()) {
out.width(first_width);
if (right_justify)
@@ -1459,17 +1497,20 @@ void value_t::print(std::ostream& out,
break;
case INTEGER:
- out << std::right << as_long();
+ if (colorize && as_long() < 0)
+ justify(out, to_string(), first_width, right_justify, true);
+ else
+ out << as_long();
break;
case AMOUNT: {
if (as_amount().is_zero()) {
- out.width(first_width);
- out << (right_justify ? std::right : std::left) << 0;
+ out << 0;
} else {
std::ostringstream buf;
buf << as_amount();
- justify(out, buf.str(), first_width, right_justify);
+ justify(out, buf.str(), first_width, right_justify,
+ colorize && as_amount().sign() < 0);
}
break;
}
@@ -1492,14 +1533,15 @@ void value_t::print(std::ostream& out,
out << ", ";
value.print(out, first_width, latter_width, right_justify,
- date_format);
+ colorize, date_format);
}
out << ')';
break;
}
case BALANCE:
- as_balance().print(out, first_width, latter_width, right_justify);
+ as_balance().print(out, first_width, latter_width, right_justify,
+ colorize);
break;
case POINTER:
@@ -1549,7 +1591,21 @@ void value_t::dump(std::ostream& out, const bool relaxed) const
break;
case STRING:
- out << '"' << as_string() << '"';
+ out << '"';
+ foreach (const char& ch, as_string()) {
+ switch (ch) {
+ case '"':
+ out << "\\\"";
+ break;
+ case '\\':
+ out << "\\\\";
+ break;
+ default:
+ out << ch;
+ break;
+ }
+ }
+ out << '"';
break;
case MASK:
diff --git a/src/value.h b/src/value.h
index 1e972558..62943e62 100644
--- a/src/value.h
+++ b/src/value.h
@@ -449,6 +449,12 @@ public:
const optional<datetime_t>& moment = none,
const optional<commodity_t&>& in_terms_of = none) const;
+ value_t price() const;
+
+ value_t exchange_commodities(const std::string& commodities,
+ const bool add_prices = false,
+ const optional<datetime_t>& moment = none);
+
/**
* Truth tests.
*/
@@ -915,6 +921,7 @@ public:
const int first_width = -1,
const int latter_width = -1,
const bool right_justify = false,
+ const bool colorize = false,
const optional<string>& date_format = none) const;
void dump(std::ostream& out, const bool relaxed = true) const;
diff --git a/src/xact.cc b/src/xact.cc
index 9e5322fa..bd8a5955 100644
--- a/src/xact.cc
+++ b/src/xact.cc
@@ -35,6 +35,7 @@
#include "post.h"
#include "account.h"
#include "journal.h"
+#include "pool.h"
namespace ledger {
@@ -269,9 +270,9 @@ bool xact_base_t::finalize()
throw_(balance_error,
_("A posting's cost must be of a different commodity than its amount"));
- commodity_t::cost_breakdown_t breakdown =
- commodity_t::exchange(post->amount, *post->cost, false,
- datetime_t(date(), time_duration(0, 0, 0, 0)));
+ cost_breakdown_t breakdown =
+ amount_t::current_pool->exchange(post->amount, *post->cost, false,
+ datetime_t(date(), time_duration(0, 0, 0, 0)));
if (post->amount.is_annotated() &&
breakdown.basis_cost.commodity() ==
@@ -387,10 +388,16 @@ expr_t::ptr_op_t xact_t::lookup(const string& name)
bool xact_t::valid() const
{
- if (! _date || ! journal) {
- DEBUG("ledger.validate", "xact_t: ! _date || ! journal");
+ if (! _date) {
+ DEBUG("ledger.validate", "xact_t: ! _date");
return false;
}
+#if 0
+ if (! journal) {
+ DEBUG("ledger.validate", "xact_t: ! journal");
+ return false;
+ }
+#endif
foreach (post_t * post, posts)
if (post->xact != this || ! post->valid()) {
diff --git a/src/xact.h b/src/xact.h
index 174a8cea..b7b4576b 100644
--- a/src/xact.h
+++ b/src/xact.h
@@ -206,7 +206,13 @@ class period_xact_t : public xact_base_t
}
virtual bool valid() const {
- return period.is_valid();
+#if 0
+ if (! period.is_valid()) {
+ DEBUG("ledger.validate", "period_xact_t: ! period.is_valid()");
+ return false;
+ }
+#endif
+ return true;
}
};
diff --git a/test/baseline/feat-balance-assignments.test b/test/baseline/feat-balance-assignments.test
new file mode 100644
index 00000000..74fe9ddb
--- /dev/null
+++ b/test/baseline/feat-balance-assignments.test
@@ -0,0 +1,37 @@
+bal
+<<<
+2009/01/01 Entry
+ Assets:Cash $10,000.00
+ Equity:Opening Balances
+
+2009/02/01 Entry
+ Expenses:Cash $100.00
+ Assets:Cash
+
+2009/02/02 Entry
+ Expenses:Cash $100.00
+ Assets:Cash
+
+2009/02/03 Entry
+ Expenses:Cash $100.00
+ Assets:Cash $-100.00 = $9,700.00
+
+2009/02/04 Entry
+ Expenses:Cash $100.00
+ Assets:Cash $-100.00 = $9,600.00
+
+2009/02/05 Entry
+ Expenses:Cash $100.00
+ Assets:Cash
+
+2009/02/05 Entry
+ Expenses:Cash
+ Assets:Cash = ($4,000.00 + $100.00)
+>>>1
+ $4,100.00 Assets:Cash
+ $-10,000.00 Equity:Opening Balances
+ $5,900.00 Expenses:Cash
+--------------------
+ 0
+>>>2
+=== 0
diff --git a/test/baseline/feature-fixated-prices.test b/test/baseline/feat-fixated-prices.test
index 11330dea..11330dea 100644
--- a/test/baseline/feature-fixated-prices.test
+++ b/test/baseline/feat-fixated-prices.test
diff --git a/test/baseline/opt-anon.test b/test/baseline/opt-anon.test
index fb8acd64..f9244451 100644
--- a/test/baseline/opt-anon.test
+++ b/test/baseline/opt-anon.test
@@ -4,8 +4,8 @@ reg --anon
Assets:Investments:Vanguard:VMMXX 0.350 VMMXX @ $1.00
Income:Dividends:Vanguard:VMMXX $-0.35
>>>1
-07-Feb-02 6a93dcb30aa7722e967.. ..cfb2807d6a978b431fc7 0.350 VMMXX 0.350 VMMXX
- ..4fbac0065bfcc300a24e $-0.35 $-0.35
+07-Feb-02 6a93dcb3 20:5d:27:988a9c3a 0.350 VMMXX 0.350 VMMXX
+ 1c:b6:27:988a9c3a $-0.35 $-0.35
0.350 VMMXX
>>>2
=== 0
diff --git a/test/baseline/opt-color.test b/test/baseline/opt-color.test
new file mode 100644
index 00000000..169c46ae
--- /dev/null
+++ b/test/baseline/opt-color.test
@@ -0,0 +1,13 @@
+bal --color
+<<<
+2007/02/02 RD VMMXX
+ Assets:Investments:Vanguard:VMMXX 0.350 VMMXX @ $1.00
+ Income:Dividends:Vanguard:VMMXX $-0.35
+>>>1
+ 0.350 VMMXX Assets:Investments:Vanguard:VMMXX
+ $-0.35 Income:Dividends:Vanguard:VMMXX
+--------------------
+ $-0.35
+ 0.350 VMMXX
+>>>2
+=== 0
diff --git a/test/baseline/opt-comm-as-account.test b/test/baseline/opt-comm-as-account.test
new file mode 100644
index 00000000..c92a47fd
--- /dev/null
+++ b/test/baseline/opt-comm-as-account.test
@@ -0,0 +1,11 @@
+reg --comm-as-account
+<<<
+2007/02/02 RD VMMXX
+ Assets:Investments:Vanguard:VMMXX 0.350 VMMXX @ $1.00
+ Income:Dividends:Vanguard:VMMXX $-0.35
+>>>1
+07-Feb-02 RD VMMXX VMMXX 0.350 VMMXX 0.350 VMMXX
+07-Feb-02 RD VMMXX $ $-0.35 $-0.35
+ 0.350 VMMXX
+>>>2
+=== 0
diff --git a/test/baseline/opt-comm-as-payee.test b/test/baseline/opt-comm-as-payee.test
new file mode 100644
index 00000000..94b37b85
--- /dev/null
+++ b/test/baseline/opt-comm-as-payee.test
@@ -0,0 +1,11 @@
+reg --comm-as-payee
+<<<
+2007/02/02 RD VMMXX
+ Assets:Investments:Vanguard:VMMXX 0.350 VMMXX @ $1.00
+ Income:Dividends:Vanguard:VMMXX $-0.35
+>>>1
+07-Feb-02 VMMXX As:In:Vanguard:VMMXX 0.350 VMMXX 0.350 VMMXX
+07-Feb-02 $ In:Di:Vanguard:VMMXX $-0.35 $-0.35
+ 0.350 VMMXX
+>>>2
+=== 0
diff --git a/test/baseline/opt-input-date-format.test b/test/baseline/opt-input-date-format.test
new file mode 100644
index 00000000..0ab5e5c9
--- /dev/null
+++ b/test/baseline/opt-input-date-format.test
@@ -0,0 +1,11 @@
+reg --input-date-format='%m%%%d%%%Y'
+<<<
+02%02%2007 RD VMMXX
+ Assets:Investments:Vanguard:VMMXX 0.350 VMMXX @ $1.00
+ Income:Dividends:Vanguard:VMMXX $-0.35
+>>>1
+07-Feb-02 RD VMMXX As:In:Vanguard:VMMXX 0.350 VMMXX 0.350 VMMXX
+ In:Di:Vanguard:VMMXX $-0.35 $-0.35
+ 0.350 VMMXX
+>>>2
+=== 0
diff --git a/test/baseline/opt-limit.test b/test/baseline/opt-limit.test
new file mode 100644
index 00000000..dd302d1b
--- /dev/null
+++ b/test/baseline/opt-limit.test
@@ -0,0 +1,212 @@
+reg --limit='account =~ /Books/ and amount < 50'
+<<<
+2008/01/01 January
+ Expenses:Books $10.00
+ Assets:Cash
+
+2008/01/31 End of January
+ Expenses:Books $10.00
+ Assets:Cash
+
+2008/02/01 February
+ Expenses:Books $20.00
+ Assets:Cash
+
+2008/02/28 End of February
+ Expenses:Books $20.00
+ Assets:Cash
+
+2008/03/01 March
+ Expenses:Books $30.00
+ Assets:Cash
+
+2008/03/31 End of March
+ Expenses:Books $30.00
+ Assets:Cash
+
+2008/04/01 April
+ Expenses:Books $40.00
+ Assets:Cash
+
+2008/04/30 End of April
+ Expenses:Books $40.00
+ Assets:Cash
+
+2008/05/01 May
+ Expenses:Books $50.00
+ Assets:Cash
+
+2008/05/31 End of May
+ Expenses:Books $50.00
+ Assets:Cash
+
+2008/06/01 June
+ Expenses:Books $60.00
+ Assets:Cash
+
+2008/06/30 End of June
+ Expenses:Books $60.00
+ Assets:Cash
+
+2008/07/01 July
+ Expenses:Books $70.00
+ Assets:Cash
+
+2008/07/31 End of July
+ Expenses:Books $70.00
+ Assets:Cash
+
+2008/08/01 August
+ Expenses:Books $80.00
+ Assets:Cash
+
+2008/08/31 End of August
+ Expenses:Books $80.00
+ Assets:Cash
+
+2008/09/01 September
+ Expenses:Books $90.00
+ Assets:Cash
+
+2008/09/30 End of September
+ Expenses:Books $90.00
+ Assets:Cash
+
+2008/10/01 October
+ Expenses:Books $100.00
+ Assets:Cash
+
+2008/10/31 End of October
+ Expenses:Books $100.00
+ Assets:Cash
+
+2008/11/01 November
+ Expenses:Books $110.00
+ Assets:Cash
+
+2008/11/30 End of November
+ Expenses:Books $110.00
+ Assets:Cash
+
+2008/12/01 December
+ Expenses:Books $120.00
+ Assets:Cash
+
+2008/12/31 End of December
+ Expenses:Books $120.00
+ Assets:Cash
+
+2009/01/01 January
+ Expenses:Books $10.00
+ Assets:Cash
+
+2009/01/31 End of January
+ Expenses:Books $10.00
+ Assets:Cash
+
+2009/02/01 February
+ Expenses:Books $20.00
+ Assets:Cash
+
+2009/02/28 End of February
+ Expenses:Books $20.00
+ Assets:Cash
+
+2009/03/01 March
+ Expenses:Books $30.00
+ Assets:Cash
+
+2009/03/31 End of March
+ Expenses:Books $30.00
+ Assets:Cash
+
+2009/04/01 April
+ Expenses:Books $40.00
+ Assets:Cash
+
+2009/04/30 End of April
+ Expenses:Books $40.00
+ Assets:Cash
+
+2009/05/01 May
+ Expenses:Books $50.00
+ Assets:Cash
+
+2009/05/31 End of May
+ Expenses:Books $50.00
+ Assets:Cash
+
+2009/06/01 June
+ Expenses:Books $60.00
+ Assets:Cash
+
+2009/06/30 End of June
+ Expenses:Books $60.00
+ Assets:Cash
+
+2009/07/01 July
+ Expenses:Books $70.00
+ Assets:Cash
+
+2009/07/31 End of July
+ Expenses:Books $70.00
+ Assets:Cash
+
+2009/08/01 August
+ Expenses:Books $80.00
+ Assets:Cash
+
+2009/08/31 End of August
+ Expenses:Books $80.00
+ Assets:Cash
+
+2009/09/01 September
+ Expenses:Books $90.00
+ Assets:Cash
+
+2009/09/30 End of September
+ Expenses:Books $90.00
+ Assets:Cash
+
+2009/10/01 October
+ Expenses:Books $100.00
+ Assets:Cash
+
+2009/10/31 End of October
+ Expenses:Books $100.00
+ Assets:Cash
+
+2009/11/01 November
+ Expenses:Books $110.00
+ Assets:Cash
+
+2009/11/30 End of November
+ Expenses:Books $110.00
+ Assets:Cash
+
+2009/12/01 December
+ Expenses:Books $120.00
+ Assets:Cash
+
+2009/12/31 End of December
+ Expenses:Books $120.00
+ Assets:Cash
+>>>1
+08-Jan-01 January Expenses:Books $10.00 $10.00
+08-Jan-31 End of January Expenses:Books $10.00 $20.00
+08-Feb-01 February Expenses:Books $20.00 $40.00
+08-Feb-28 End of February Expenses:Books $20.00 $60.00
+08-Mar-01 March Expenses:Books $30.00 $90.00
+08-Mar-31 End of March Expenses:Books $30.00 $120.00
+08-Apr-01 April Expenses:Books $40.00 $160.00
+08-Apr-30 End of April Expenses:Books $40.00 $200.00
+09-Jan-01 January Expenses:Books $10.00 $210.00
+09-Jan-31 End of January Expenses:Books $10.00 $220.00
+09-Feb-01 February Expenses:Books $20.00 $240.00
+09-Feb-28 End of February Expenses:Books $20.00 $260.00
+09-Mar-01 March Expenses:Books $30.00 $290.00
+09-Mar-31 End of March Expenses:Books $30.00 $320.00
+09-Apr-01 April Expenses:Books $40.00 $360.00
+09-Apr-30 End of April Expenses:Books $40.00 $400.00
+>>>2
+=== 0
diff --git a/test/baseline/opt-lot-tags.test b/test/baseline/opt-lot-tags.test
new file mode 100644
index 00000000..d77aea33
--- /dev/null
+++ b/test/baseline/opt-lot-tags.test
@@ -0,0 +1,12 @@
+bal --lot-tags tajer
+<<<
+D 1.00c
+
+2006/03/14 Opening Balances
+ Assets:Tajer 1339829c (TAG)
+ Assets:Gruulmorg 248720c
+ Equity:Gold
+>>>1
+ 1339829.00c (TAG) Assets:Tajer
+>>>2
+=== 0
diff --git a/test/baseline/opt-lots-actual.test b/test/baseline/opt-lots-actual.test
new file mode 100644
index 00000000..39a27363
--- /dev/null
+++ b/test/baseline/opt-lots-actual.test
@@ -0,0 +1,27 @@
+reg --format '%(justify(scrub(total_expr), 40, 40, true))\n' --lots
+<<<
+D 1.0000s
+
+2006/03/14 Opening Balances
+ Assets:Tajer 1339829c @ 1.86590975416s
+ Assets:Gruulmorg 248720c {10.051463493s}
+ Equity:Gold -5000000s
+>>>1
+ 1339829c {1.8659s} [2006/03/14]
+ 1339829c {1.8659s} [2006/03/14]
+ 248720c {10.0515s}
+ 1339829c {1.8659s} [2006/03/14]
+ 248720c {10.0515s}
+ -1388.9h
+>>>2
+=== 0
+reg --format '%(justify(scrub(total_expr), 40, 40, true))\n' --lots-actual
+>>>1
+ 1339829c
+ 1339829c
+ 248720c {10.0515s}
+ 1339829c
+ 248720c {10.0515s}
+ -1388.9h
+>>>2
+=== 0
diff --git a/test/baseline/opt-no-total.test b/test/baseline/opt-no-total.test
new file mode 100644
index 00000000..d1a11485
--- /dev/null
+++ b/test/baseline/opt-no-total.test
@@ -0,0 +1,10 @@
+bal --no-total
+<<<
+2007/02/02 RD VMMXX
+ Assets:Investments:Vanguard:VMMXX 0.350 VMMXX @ $1.00
+ Income:Dividends:Vanguard:VMMXX $-0.35
+>>>1
+ 0.350 VMMXX Assets:Investments:Vanguard:VMMXX
+ $-0.35 Income:Dividends:Vanguard:VMMXX
+>>>2
+=== 0
diff --git a/test/baseline/opt-only.test b/test/baseline/opt-only.test
new file mode 100644
index 00000000..9728edae
--- /dev/null
+++ b/test/baseline/opt-only.test
@@ -0,0 +1,236 @@
+reg books --monthly --limit='amount > $100'
+<<<
+2008/01/01 January
+ Expenses:Books $10.00
+ Assets:Cash
+
+2008/01/31 End of January
+ Expenses:Books $10.00
+ Assets:Cash
+
+2008/02/01 February
+ Expenses:Books $20.00
+ Assets:Cash
+
+2008/02/28 End of February
+ Expenses:Books $20.00
+ Assets:Cash
+
+2008/03/01 March
+ Expenses:Books $30.00
+ Assets:Cash
+
+2008/03/31 End of March
+ Expenses:Books $30.00
+ Assets:Cash
+
+2008/04/01 April
+ Expenses:Books $40.00
+ Assets:Cash
+
+2008/04/30 End of April
+ Expenses:Books $40.00
+ Assets:Cash
+
+2008/05/01 May
+ Expenses:Books $50.00
+ Assets:Cash
+
+2008/05/31 End of May
+ Expenses:Books $50.00
+ Assets:Cash
+
+2008/06/01 June
+ Expenses:Books $60.00
+ Assets:Cash
+
+2008/06/30 End of June
+ Expenses:Books $60.00
+ Assets:Cash
+
+2008/07/01 July
+ Expenses:Books $70.00
+ Assets:Cash
+
+2008/07/31 End of July
+ Expenses:Books $70.00
+ Assets:Cash
+
+2008/08/01 August
+ Expenses:Books $80.00
+ Assets:Cash
+
+2008/08/31 End of August
+ Expenses:Books $80.00
+ Assets:Cash
+
+2008/09/01 September
+ Expenses:Books $90.00
+ Assets:Cash
+
+2008/09/30 End of September
+ Expenses:Books $90.00
+ Assets:Cash
+
+2008/10/01 October
+ Expenses:Books $100.00
+ Assets:Cash
+
+2008/10/31 End of October
+ Expenses:Books $100.00
+ Assets:Cash
+
+2008/11/01 November
+ Expenses:Books $110.00
+ Assets:Cash
+
+2008/11/30 End of November
+ Expenses:Books $110.00
+ Assets:Cash
+
+2008/12/01 December
+ Expenses:Books $120.00
+ Assets:Cash
+
+2008/12/31 End of December
+ Expenses:Books $120.00
+ Assets:Cash
+
+2009/01/01 January
+ Expenses:Books $10.00
+ Assets:Cash
+
+2009/01/31 End of January
+ Expenses:Books $10.00
+ Assets:Cash
+
+2009/02/01 February
+ Expenses:Books $20.00
+ Assets:Cash
+
+2009/02/28 End of February
+ Expenses:Books $20.00
+ Assets:Cash
+
+2009/03/01 March
+ Expenses:Books $30.00
+ Assets:Cash
+
+2009/03/31 End of March
+ Expenses:Books $30.00
+ Assets:Cash
+
+2009/04/01 April
+ Expenses:Books $40.00
+ Assets:Cash
+
+2009/04/30 End of April
+ Expenses:Books $40.00
+ Assets:Cash
+
+2009/05/01 May
+ Expenses:Books $50.00
+ Assets:Cash
+
+2009/05/31 End of May
+ Expenses:Books $50.00
+ Assets:Cash
+
+2009/06/01 June
+ Expenses:Books $60.00
+ Assets:Cash
+
+2009/06/30 End of June
+ Expenses:Books $60.00
+ Assets:Cash
+
+2009/07/01 July
+ Expenses:Books $70.00
+ Assets:Cash
+
+2009/07/31 End of July
+ Expenses:Books $70.00
+ Assets:Cash
+
+2009/08/01 August
+ Expenses:Books $80.00
+ Assets:Cash
+
+2009/08/31 End of August
+ Expenses:Books $80.00
+ Assets:Cash
+
+2009/09/01 September
+ Expenses:Books $90.00
+ Assets:Cash
+
+2009/09/30 End of September
+ Expenses:Books $90.00
+ Assets:Cash
+
+2009/10/01 October
+ Expenses:Books $100.00
+ Assets:Cash
+
+2009/10/31 End of October
+ Expenses:Books $100.00
+ Assets:Cash
+
+2009/11/01 November
+ Expenses:Books $110.00
+ Assets:Cash
+
+2009/11/30 End of November
+ Expenses:Books $110.00
+ Assets:Cash
+
+2009/12/01 December
+ Expenses:Books $120.00
+ Assets:Cash
+
+2009/12/31 End of December
+ Expenses:Books $120.00
+ Assets:Cash
+>>>1
+08-Nov-01 - 08-Nov-30 Expenses:Books $220.00 $220.00
+08-Dec-01 - 08-Dec-31 Expenses:Books $240.00 $460.00
+09-Nov-01 - 09-Nov-30 Expenses:Books $220.00 $680.00
+09-Dec-01 - 09-Dec-31 Expenses:Books $240.00 $920.00
+>>>2
+=== 0
+reg books --monthly --only='amount > $100'
+>>>1
+08-Jun-01 - 08-Jun-30 Expenses:Books $120.00 $120.00
+08-Jul-01 - 08-Jul-31 Expenses:Books $140.00 $260.00
+08-Aug-01 - 08-Aug-31 Expenses:Books $160.00 $420.00
+08-Sep-01 - 08-Sep-30 Expenses:Books $180.00 $600.00
+08-Oct-01 - 08-Oct-31 Expenses:Books $200.00 $800.00
+08-Nov-01 - 08-Nov-30 Expenses:Books $220.00 $1020.00
+08-Dec-01 - 08-Dec-31 Expenses:Books $240.00 $1260.00
+09-Jun-01 - 09-Jun-30 Expenses:Books $120.00 $1380.00
+09-Jul-01 - 09-Jul-31 Expenses:Books $140.00 $1520.00
+09-Aug-01 - 09-Aug-31 Expenses:Books $160.00 $1680.00
+09-Sep-01 - 09-Sep-30 Expenses:Books $180.00 $1860.00
+09-Oct-01 - 09-Oct-31 Expenses:Books $200.00 $2060.00
+09-Nov-01 - 09-Nov-30 Expenses:Books $220.00 $2280.00
+09-Dec-01 - 09-Dec-31 Expenses:Books $240.00 $2520.00
+>>>2
+=== 0
+reg books --monthly --display='amount > $100'
+>>>1
+08-Jun-01 - 08-Jun-30 Expenses:Books $120.00 $420.00
+08-Jul-01 - 08-Jul-31 Expenses:Books $140.00 $560.00
+08-Aug-01 - 08-Aug-31 Expenses:Books $160.00 $720.00
+08-Sep-01 - 08-Sep-30 Expenses:Books $180.00 $900.00
+08-Oct-01 - 08-Oct-31 Expenses:Books $200.00 $1100.00
+08-Nov-01 - 08-Nov-30 Expenses:Books $220.00 $1320.00
+08-Dec-01 - 08-Dec-31 Expenses:Books $240.00 $1560.00
+09-Jun-01 - 09-Jun-30 Expenses:Books $120.00 $1980.00
+09-Jul-01 - 09-Jul-31 Expenses:Books $140.00 $2120.00
+09-Aug-01 - 09-Aug-31 Expenses:Books $160.00 $2280.00
+09-Sep-01 - 09-Sep-30 Expenses:Books $180.00 $2460.00
+09-Oct-01 - 09-Oct-31 Expenses:Books $200.00 $2660.00
+09-Nov-01 - 09-Nov-30 Expenses:Books $220.00 $2880.00
+09-Dec-01 - 09-Dec-31 Expenses:Books $240.00 $3120.00
+>>>2
+=== 0
diff --git a/test/baseline/opt-percent.test b/test/baseline/opt-percent.test
new file mode 100644
index 00000000..1adb0a8a
--- /dev/null
+++ b/test/baseline/opt-percent.test
@@ -0,0 +1,94 @@
+bal --percent
+<<<
+2008/01/11 LIAT
+ Expenses:Travel:Airfare $40.00
+ Liabilities:MasterCard
+
+2008/01/14 cheaptickets.com
+ Expenses:Travel:Airfare $182.19
+ Liabilities:MasterCard
+
+2008/02/05 CTX
+ Expenses:Travel:Auto $240.38
+ Liabilities:MasterCard
+
+2008/02/05 UNITED
+ Expenses:Travel:Airfare $238.80
+ Liabilities:MasterCard
+
+2008/02/05 UNITED
+ Expenses:Travel:Airfare $238.80
+ Liabilities:MasterCard
+
+2008/02/22 BUDGET RENT-A-CAR
+ Expenses:Travel:Auto $40.59
+ Liabilities:MasterCard
+
+2008/03/16 IBERIA
+ Expenses:Travel:Airfare $1,231.60
+ Liabilities:MasterCard
+
+2008/03/16 IBERIA
+ Expenses:Travel:Airfare $1,231.60
+ Liabilities:MasterCard
+
+2008/04/03 AMERICAN
+ Expenses:Travel:Airfare $155.86
+ Liabilities:MasterCard
+
+2008/04/03 AMERICAN
+ Expenses:Travel:Airfare $155.86
+ Liabilities:MasterCard
+
+2008/04/30 UNITED
+ Expenses:Travel:Airfare $437.21
+ Liabilities:MasterCard
+
+2008/04/30 UNITED
+ Expenses:Travel:Airfare $437.21
+ Liabilities:MasterCard
+
+2008/08/08 BCIS I-131 FILING FEE-
+ Expenses:Travel:Passport $170.00
+ Liabilities:MasterCard
+
+2008/09/06 AMERICAN
+ Expenses:Travel:Airfare $912.60
+ Liabilities:MasterCard
+
+2008/09/06 AMERICAN
+ Expenses:Travel:Airfare $912.60
+ Liabilities:MasterCard
+
+2008/09/22 AGNT FEE
+ Expenses:Travel:Airfare $70.00
+ Liabilities:MasterCard
+
+2008/09/22 DELTA
+ Expenses:Travel:Airfare $806.20
+ Liabilities:MasterCard
+
+2008/09/22 DELTA
+ Expenses:Travel:Airfare $806.20
+ Liabilities:MasterCard
+
+2008/09/22 LIAT 1974 LIMITED
+ Expenses:Travel:Airfare $418.34
+ Liabilities:MasterCard
+
+2008/12/26 U.S. Department of State
+ Expenses:Travel:Passport $127.00
+ Assets:Checking
+
+2008/12/26 U.S. Department of State
+ Expenses:Travel:Passport $127.00
+ Assets:Checking
+>>>1
+ 100.00% Assets:Checking
+ 100.00% Expenses:Travel
+ 92.15% Airfare
+ 3.13% Auto
+ 4.72% Passport
+ 100.00% Liabilities:MasterCard
+>>>2
+=== 0
diff --git a/test/baseline/opt-period.test b/test/baseline/opt-period.test
new file mode 100644
index 00000000..09e7e355
--- /dev/null
+++ b/test/baseline/opt-period.test
@@ -0,0 +1,290 @@
+reg -p "january 2008"
+<<<
+2008/01/01 January
+ Expenses:Books $10.00
+ Liabilities:Cards $10.00
+ Assets:Cash
+
+2008/01/31 End of January
+ Expenses:Books $10.00
+ Liabilities:Cards $10.00
+ Assets:Cash
+
+2008/02/01 February
+ Expenses:Books $20.00
+ Liabilities:Cards $20.00
+ Assets:Cash
+
+2008/02/28 End of February
+ Expenses:Books $20.00
+ Liabilities:Cards $20.00
+ Assets:Cash
+
+2008/03/01 March
+ Expenses:Books $30.00
+ Liabilities:Cards $30.00
+ Assets:Cash
+
+2008/03/31 End of March
+ Expenses:Books $30.00
+ Liabilities:Cards $30.00
+ Assets:Cash
+
+2008/04/01 April
+ Expenses:Books $40.00
+ Liabilities:Cards $40.00
+ Assets:Cash
+
+2008/04/30 End of April
+ Expenses:Books $40.00
+ Liabilities:Cards $40.00
+ Assets:Cash
+
+2008/05/01 May
+ Expenses:Books $50.00
+ Liabilities:Cards $50.00
+ Assets:Cash
+
+2008/05/31 End of May
+ Expenses:Books $50.00
+ Liabilities:Cards $50.00
+ Assets:Cash
+
+2008/06/01 June
+ Expenses:Books $60.00
+ Liabilities:Cards $60.00
+ Assets:Cash
+
+2008/06/30 End of June
+ Expenses:Books $60.00
+ Liabilities:Cards $60.00
+ Assets:Cash
+
+2008/07/01 July
+ Expenses:Books $70.00
+ Liabilities:Cards $70.00
+ Assets:Cash
+
+2008/07/31 End of July
+ Expenses:Books $70.00
+ Liabilities:Cards $70.00
+ Assets:Cash
+
+2008/08/01 August
+ Expenses:Books $80.00
+ Liabilities:Cards $80.00
+ Assets:Cash
+
+2008/08/31 End of August
+ Expenses:Books $80.00
+ Liabilities:Cards $80.00
+ Assets:Cash
+
+2008/09/01 September
+ Expenses:Books $90.00
+ Liabilities:Cards $90.00
+ Assets:Cash
+
+2008/09/30 End of September
+ Expenses:Books $90.00
+ Liabilities:Cards $90.00
+ Assets:Cash
+
+2008/10/01 October
+ Expenses:Books $100.00
+ Liabilities:Cards $100.00
+ Assets:Cash
+
+2008/10/31 End of October
+ Expenses:Books $100.00
+ Liabilities:Cards $100.00
+ Assets:Cash
+
+2008/11/01 November
+ Expenses:Books $110.00
+ Liabilities:Cards $110.00
+ Assets:Cash
+
+2008/11/30 End of November
+ Expenses:Books $110.00
+ Liabilities:Cards $110.00
+ Assets:Cash
+
+2008/12/01 December
+ Expenses:Books $120.00
+ Liabilities:Cards $120.00
+ Assets:Cash
+
+2008/12/31 End of December
+ Expenses:Books $120.00
+ Liabilities:Cards $120.00
+ Assets:Cash
+
+2009/01/01 January
+ Expenses:Books $10.00
+ Liabilities:Cards $10.00
+ Assets:Cash
+
+2009/01/31 End of January
+ Expenses:Books $10.00
+ Liabilities:Cards $10.00
+ Assets:Cash
+
+2009/02/01 February
+ Expenses:Books $20.00
+ Liabilities:Cards $20.00
+ Assets:Cash
+
+2009/02/28 End of February
+ Expenses:Books $20.00
+ Liabilities:Cards $20.00
+ Assets:Cash
+
+2009/03/01 March
+ Expenses:Books $30.00
+ Liabilities:Cards $30.00
+ Assets:Cash
+
+2009/03/31 End of March
+ Expenses:Books $30.00
+ Liabilities:Cards $30.00
+ Assets:Cash
+
+2009/04/01 April
+ Expenses:Books $40.00
+ Liabilities:Cards $40.00
+ Assets:Cash
+
+2009/04/30 End of April
+ Expenses:Books $40.00
+ Liabilities:Cards $40.00
+ Assets:Cash
+
+2009/05/01 May
+ Expenses:Books $50.00
+ Liabilities:Cards $50.00
+ Assets:Cash
+
+2009/05/31 End of May
+ Expenses:Books $50.00
+ Liabilities:Cards $50.00
+ Assets:Cash
+
+2009/06/01 June
+ Expenses:Books $60.00
+ Liabilities:Cards $60.00
+ Assets:Cash
+
+2009/06/30 End of June
+ Expenses:Books $60.00
+ Liabilities:Cards $60.00
+ Assets:Cash
+
+2009/07/01 July
+ Expenses:Books $70.00
+ Liabilities:Cards $70.00
+ Assets:Cash
+
+2009/07/31 End of July
+ Expenses:Books $70.00
+ Liabilities:Cards $70.00
+ Assets:Cash
+
+2009/08/01 August
+ Expenses:Books $80.00
+ Liabilities:Cards $80.00
+ Assets:Cash
+
+2009/08/31 End of August
+ Expenses:Books $80.00
+ Liabilities:Cards $80.00
+ Assets:Cash
+
+2009/09/01 September
+ Expenses:Books $90.00
+ Liabilities:Cards $90.00
+ Assets:Cash
+
+2009/09/30 End of September
+ Expenses:Books $90.00
+ Liabilities:Cards $90.00
+ Assets:Cash
+
+2009/10/01 October
+ Expenses:Books $100.00
+ Liabilities:Cards $100.00
+ Assets:Cash
+
+2009/10/31 End of October
+ Expenses:Books $100.00
+ Liabilities:Cards $100.00
+ Assets:Cash
+
+2009/11/01 November
+ Expenses:Books $110.00
+ Liabilities:Cards $110.00
+ Assets:Cash
+
+2009/11/30 End of November
+ Expenses:Books $110.00
+ Liabilities:Cards $110.00
+ Assets:Cash
+
+2009/12/01 December
+ Expenses:Books $120.00
+ Liabilities:Cards $120.00
+ Assets:Cash
+
+2009/12/31 End of December
+ Expenses:Books $120.00
+ Liabilities:Cards $120.00
+ Assets:Cash
+>>>1
+08-Jan-01 January Expenses:Books $10.00 $10.00
+ Liabilities:Cards $10.00 $20.00
+ Assets:Cash $-20.00 0
+08-Jan-31 End of January Expenses:Books $10.00 $10.00
+ Liabilities:Cards $10.00 $20.00
+ Assets:Cash $-20.00 0
+>>>2
+=== 0
+reg -p "monthly january 2008"
+>>>1
+08-Jan-01 - 08-Jan-31 Assets:Cash $-40.00 $-40.00
+ Expenses:Books $20.00 $-20.00
+ Liabilities:Cards $20.00 0
+>>>2
+=== 0
+reg -p "weekly january 2008"
+>>>1
+08-Jan-01 - 08-Jan-05 Assets:Cash $-20.00 $-20.00
+ Expenses:Books $10.00 $-10.00
+ Liabilities:Cards $10.00 0
+08-Jan-29 - 08-Jan-31 Assets:Cash $-20.00 $-20.00
+ Expenses:Books $10.00 $-10.00
+ Liabilities:Cards $10.00 0
+>>>2
+=== 0
+reg -p "yearly 2008"
+>>>1
+08-Jan-01 - 08-Dec-31 Assets:Cash $-3120.00 $-3120.00
+ Expenses:Books $1560.00 $-1560.00
+ Liabilities:Cards $1560.00 0
+>>>2
+=== 0
+reg -p "from 2009/11/01"
+>>>1
+09-Nov-01 November Expenses:Books $110.00 $110.00
+ Liabilities:Cards $110.00 $220.00
+ Assets:Cash $-220.00 0
+09-Nov-30 End of November Expenses:Books $110.00 $110.00
+ Liabilities:Cards $110.00 $220.00
+ Assets:Cash $-220.00 0
+09-Dec-01 December Expenses:Books $120.00 $120.00
+ Liabilities:Cards $120.00 $240.00
+ Assets:Cash $-240.00 0
+09-Dec-31 End of December Expenses:Books $120.00 $120.00
+ Liabilities:Cards $120.00 $240.00
+ Assets:Cash $-240.00 0
+>>>2
+=== 0
diff --git a/test/baseline/opt-plot-amount-format.test b/test/baseline/opt-plot-amount-format.test
new file mode 100644
index 00000000..9c5484af
--- /dev/null
+++ b/test/baseline/opt-plot-amount-format.test
@@ -0,0 +1,10 @@
+reg -j --plot-amount-format='X %(format_date(date, "%Y-%m-%d")) Y %(quantity(scrub(display_amount)))\n'
+<<<
+2007/02/02 RD VMMXX
+ Assets:Investments:Vanguard:VMMXX 0.350 VMMXX @ $1.00
+ Income:Dividends:Vanguard:VMMXX $-0.35
+>>>1
+X 2007-02-02 Y 0.35
+X 2007-02-02 Y -0.35
+>>>2
+=== 0
diff --git a/test/baseline/opt-plot-total-format.test b/test/baseline/opt-plot-total-format.test
new file mode 100644
index 00000000..1065c5ce
--- /dev/null
+++ b/test/baseline/opt-plot-total-format.test
@@ -0,0 +1,10 @@
+reg -J --plot-total-format='X %(format_date(date, "%Y-%m-%d")) Y %(quantity(scrub(display_amount)))\n'
+<<<
+2007/02/02 RD VMMXX
+ Assets:Investments:Vanguard:VMMXX 0.350 VMMXX @ $1.00
+ Income:Dividends:Vanguard:VMMXX $-0.35
+>>>1
+X 2007-02-02 Y 0.35
+X 2007-02-02 Y -0.35
+>>>2
+=== 0
diff --git a/test/baseline/opt-price.test b/test/baseline/opt-price.test
new file mode 100644
index 00000000..06cc7751
--- /dev/null
+++ b/test/baseline/opt-price.test
@@ -0,0 +1,47 @@
+reg equities
+<<<
+2008/01/01 * Purchase Apple shares
+ Equities 1000 AAPL @ $2
+ Cash $-2000
+
+2008/06/30 * Sell some Apple shares
+ Cash $1250
+ Equities -500 AAPL {$2} @ $2.50
+ Income:Gains $-250
+
+P 2008/10/01 02:18:02 AAPL $3
+P 2009/01/31 02:18:02 AAPL $4
+P 3000/01/01 02:18:02 APPL $100
+>>>1
+08-Jan-01 Purchase Apple shares Equities 1000 AAPL 1000 AAPL
+08-Jun-30 Sell some Apple sha.. Equities -500 AAPL 500 AAPL
+>>>2
+=== 0
+reg -B equities
+>>>1
+08-Jan-01 Purchase Apple shares Equities $2000 $2000
+08-Jun-30 Sell some Apple sha.. Equities $-1250 $750
+>>>2
+=== 0
+reg --end 2009/06/26 -V equities
+>>>1
+08-Jan-01 Purchase Apple shares Equities $2000 $2000
+08-Jun-30 Commodities revalued <Revalued> $500 $2500
+08-Jun-30 Sell some Apple sha.. Equities $-1250 $1250
+09-Jun-26 Commodities revalued <Revalued> $750 $2000
+>>>2
+=== 0
+reg --end 2009/06/26 -G equities
+>>>1
+08-Jan-01 Purchase Apple shares Equities 0 0
+08-Jun-30 Commodities revalued <Revalued> $500 $500
+08-Jun-30 Sell some Apple sha.. Equities 0 $500
+09-Jun-26 Commodities revalued <Revalued> $750 $1250
+>>>2
+=== 0
+reg -I equities
+>>>1
+08-Jan-01 Purchase Apple shares Equities $2000 $2000
+08-Jun-30 Sell some Apple sha.. Equities $-1000 $1000
+>>>2
+=== 0
diff --git a/test/baseline/opt-prices-format.test b/test/baseline/opt-prices-format.test
new file mode 100644
index 00000000..ec93d9cd
--- /dev/null
+++ b/test/baseline/opt-prices-format.test
@@ -0,0 +1,19 @@
+prices --prices-format='%(datetime) %(scrub(display_amount))\n'
+<<<
+P 2009/01/01 13:30:00 AAPL $10.00
+P 2009/01/01 14:30:00 AAPL $20.00
+P 2009/01/01 15:30:00 AAPL $30.00
+P 2009/01/01 16:30:00 AAPL $40.00
+P 2009/02/01 17:30:00 AAPL $50.00
+
+2009/03/01 Purchase
+ Assets:Brokerage 100 AAPL
+ Income
+>>>1
+09-Jan-01 13:30:00 $10.00
+09-Jan-01 14:30:00 $20.00
+09-Jan-01 15:30:00 $30.00
+09-Jan-01 16:30:00 $40.00
+09-Feb-01 17:30:00 $50.00
+>>>2
+=== 0
diff --git a/test/baseline/opt-pricesdb-format.test b/test/baseline/opt-pricesdb-format.test
new file mode 100644
index 00000000..28998903
--- /dev/null
+++ b/test/baseline/opt-pricesdb-format.test
@@ -0,0 +1,19 @@
+pricesdb --pricesdb-format='P %(date) %(scrub(display_amount))\n'
+<<<
+P 2009/01/01 13:30:00 AAPL $10.00
+P 2009/01/01 14:30:00 AAPL $20.00
+P 2009/01/01 15:30:00 AAPL $30.00
+P 2009/01/01 16:30:00 AAPL $40.00
+P 2009/02/01 17:30:00 AAPL $50.00
+
+2009/03/01 Purchase
+ Assets:Brokerage 100 AAPL
+ Income
+>>>1
+P 09-Jan-01 $10.00
+P 09-Jan-01 $20.00
+P 09-Jan-01 $30.00
+P 09-Jan-01 $40.00
+P 09-Feb-01 $50.00
+>>>2
+=== 0
diff --git a/test/baseline/opt-print-format.test b/test/baseline/opt-print-format.test
new file mode 100644
index 00000000..103ceb1e
--- /dev/null
+++ b/test/baseline/opt-print-format.test
@@ -0,0 +1,10 @@
+print --print-format='%(amount)\n'
+<<<
+2007/02/02 RD VMMXX
+ Assets:Investments:Vanguard:VMMXX 0.350 VMMXX @ $1.00
+ Income:Dividends:Vanguard:VMMXX $-0.35
+>>>1
+0.350 VMMXX {$1.00} [2007/02/02]
+$-0.35
+>>>2
+=== 0
diff --git a/test/baseline/opt-quantity.test b/test/baseline/opt-quantity.test
new file mode 100644
index 00000000..5de92e84
--- /dev/null
+++ b/test/baseline/opt-quantity.test
@@ -0,0 +1,11 @@
+reg --quantity
+<<<
+2007/02/02 RD VMMXX
+ Assets:Investments:Vanguard:VMMXX 0.350 VMMXX @ $1.00
+ Income:Dividends:Vanguard:VMMXX $-0.35
+>>>1
+07-Feb-02 RD VMMXX As:In:Vanguard:VMMXX 0.350 VMMXX 0.350 VMMXX
+ In:Di:Vanguard:VMMXX $-0.35 $-0.35
+ 0.350 VMMXX
+>>>2
+=== 0
diff --git a/test/baseline/opt-real.test b/test/baseline/opt-real.test
new file mode 100644
index 00000000..b406f29a
--- /dev/null
+++ b/test/baseline/opt-real.test
@@ -0,0 +1,11 @@
+reg --real
+<<<
+2008/01/01 January
+ Expenses:Books $10.00
+ (Liabilities:Cards) $10.00
+ Assets:Cash
+>>>1
+08-Jan-01 January Expenses:Books $10.00 $10.00
+ Assets:Cash $-10.00 0
+>>>2
+=== 0
diff --git a/test/baseline/opt-register-format.test b/test/baseline/opt-register-format.test
new file mode 100644
index 00000000..2384b6e2
--- /dev/null
+++ b/test/baseline/opt-register-format.test
@@ -0,0 +1,10 @@
+reg --register-format='%(amount)\n'
+<<<
+2007/02/02 RD VMMXX
+ Assets:Investments:Vanguard:VMMXX 0.350 VMMXX @ $1.00
+ Income:Dividends:Vanguard:VMMXX $-0.35
+>>>1
+0.350 VMMXX {$1.00} [2007/02/02]
+$-0.35
+>>>2
+=== 0
diff --git a/test/baseline/opt-related-all.test b/test/baseline/opt-related-all.test
new file mode 100644
index 00000000..645f8a91
--- /dev/null
+++ b/test/baseline/opt-related-all.test
@@ -0,0 +1,14 @@
+reg --related-all credit
+<<<
+2009/06/18 This is a Test
+ Expenses:Food $20.00
+ Expenses:Tips $2.00
+ Expenses:Tax $3.00
+ Liabilities:Credit
+>>>1
+09-Jun-18 This is a Test Expenses:Food $20.00 $20.00
+ Expenses:Tips $2.00 $22.00
+ Expenses:Tax $3.00 $25.00
+ Liabilities:Credit $-25.00 0
+>>>2
+=== 0
diff --git a/test/baseline/opt-related.test b/test/baseline/opt-related.test
new file mode 100644
index 00000000..576bc9d2
--- /dev/null
+++ b/test/baseline/opt-related.test
@@ -0,0 +1,13 @@
+reg --related credit
+<<<
+2009/06/18 This is a Test
+ Expenses:Food $20.00
+ Expenses:Tips $2.00
+ Expenses:Tax $3.00
+ Liabilities:Credit
+>>>1
+09-Jun-18 This is a Test Expenses:Food $20.00 $20.00
+ Expenses:Tips $2.00 $22.00
+ Expenses:Tax $3.00 $25.00
+>>>2
+=== 0
diff --git a/test/baseline/opt-seed.test b/test/baseline/opt-seed.test
new file mode 100644
index 00000000..48ca7d13
--- /dev/null
+++ b/test/baseline/opt-seed.test
@@ -0,0 +1,432 @@
+generate --seed=10
+<<<
+>>>1
+2186/12/04=1987/04/07 (gxB) Xcm0wJS7fGh07
+ ; K93x8n5028OV19:n9zv0ksC
+ [MIdo:Y377U9xhRjxQaFFk37:aEaw66xn:9ss914] 411126pQ @ ozgYxo0.578679
+ ; n1:1Bm38PMy6FO51R58pWhXmc1HN08Umw8T
+ (X:S) -936200r @ nijv235713
+ ; j7zUn1U8UPStcn46 72dpS5vd9d2oxQk:R
+ C:27lps3E
+
+2186/12/09=1987/04/08 (B7xDea) yA6fOo:q0A6IaA7v ; LB:l
+ cro FhdXRF:nROGw1H8PqY10Jg 3FQRrcX07 -733399YmHWf @ 0.640803LMtLP ; dVmRkOR
+ [Z2t:31I:2ox:81NC28A0] 263844DKa ; h4I8M355
+ B0VD1MI2073MblRrsPV64O9 EEMQ 816045 @ 0.499731hzxnl ; qAsYFCY
+ [b1X09LHz:AkK5G] U -785951
+ ; gJy3hZ4g75vf84:9Rz R 6Vpy11ZJ:24K m U:Y
+ DI7 3 ; H64
+
+2186/12/11=1987/04/12 ! (Xu:Eo) Cx4o2an 3Upa0Fll:nDjT461:y ; uzZ5uQlLWR4
+ [XKU2Jj6:p6I5kVSPv 3vpG:ShyKRF:dv8y] Tgojw -14862.9
+ [SPOiCDS5Zg:t:Q7vMF01n8P:7h1i] qXxf 437098 @ BmJ866903
+ [reGXB0LK38iD5FM zWxl PUW:eI5F21EN80e] acLq947715
+ (NA LA 11uhxP8y7oBBgXZQ45lwPb1) 14641.5sndK @ 44.2184885 Cw
+ ; P4158:i1xa1eoHUHeF1pS8
+ [CMs:8Yz9t7H1aF3qN] qt250589
+ (B0T) a-715047 @ 0.946357 Ac
+ ; PeUU8DYH3 BxS Uu9y:YfU0isX6ly:sZO6S2xpN
+ V:60vuV
+
+2186/12/12=1987/04/14 * (OEWOu) SLGS
+ ; AuFLS1XOW:z04:gwQ2
+ (VjI Tm79d6) -93666.5xuIH ; A9
+ [UF3xXu kOA ERwiQOh aprUp9gM] -103361jSZeo @ 564002 iJOXN
+ (EzJ 65T295) QCaR134452 @ RuLEEw 0.6292982
+ ; M8916aJx5C7:G9U0I:y4t68
+ W oUJuq66YfULC1 CW95nX2VZch2:qK992pM3fhy manJj -209990 @ c 536098
+ ; yzOvW 28W99zgmW9Y9:j:5w:1:gIX
+ rNUT0rM:rBsW:P87ZVn3d8X
+
+2186/12/17=1987/04/15 * (F7u) sZ:c vZ8U3Sr705
+ T2DT75mS9x7 W727197 @ 1.264289dfiel
+ (Z X9E8W16j8:521) zPFS-334854
+ ; RkIZ27O30b8:yf0pXU
+ (jAmK) 426564AO @ SemS 421251
+ ER lq6N3 q 3317.11 @ RAAii 254.77207569
+ ixS
+
+2186/12/21 * (EVhm) fnA 7GRR710:8:FhSlZRc:4L5J74Q RnH9H4v ; N2
+ (dGg77g:i40RE7J3KnxNNHB0E 7) 262042 z
+ ; N3V98V827I:qaJ2U4E5IR tpHJw0J699:f
+ M8:vCvoagZmVL1Y:543:eucH98:Q 222936 aZtSl @ 1.851056rQWi
+ ; Os2Z7rnOU11Y9vm8pGNvTuK
+ [KWybHT:s] Zc -395512 @ 673430 Q
+ ; JDK FpB 6g 5dBDa
+ [Tk:B1257JrD9:LgqbzmH8GQ] UaS-222545 ; uiM6
+ u335Ic
+
+2186/12/27 ! (GS) EJ99iD7cHek7EJp5aoFYIXz2Wx:hTSIPWjP
+ [rRlyn:21:Jcu292Ea] YuOJ-77111.5 @ ys9.708928
+ ; EI309:2D t66H6NzNv c AXmMzeF4:kR
+ [jn 58DWi8DwD48Ee3:05qkh38Y8754VMaC80OD] 205960LdQ @ iiB 2.206851 ; vTJ9I oLQ9KF1l
+ [zqBE2yO1UWmgM4BL:A9Do4EffRd8V:io] w-53161.6 @ 350764 UclkLw
+ g:R000qI3C 746879 juFf @ hlN448555
+ ; x5:1OGWm WY9Fdtu:8rD09k1:5z 1:8Y:xQ11I
+ A43J7Yjx6xKhN0:TjP ; f:l8Q 1L7RjfBQ
+
+2186/12/29 * (R3:UJ) hN K3YTd:0y:42Ze9ObAaT lgx:yJ:ir1j ; xIIL05IE2p3
+ [y jLigz uChQA Eg4UEf0 70B:94:i4DqcQq] iyg895724 @ 0.510892 mCiSp
+ w8C 7Hx8Rq91QS6z:7b -331711 vDTIl
+ U0987:F:4DTh6V F6 Rkp3p1 CTDATK-782095
+ [kG7R6F:9jPxChWVOObKg045AE80hc87FW] 252408ViNkjs
+ jHM I5:Y
+
+2187/01/02=1987/04/20 (oCi) coD:3Y1ZEF79r:C086
+ ; q:Gk:331:S9 o:0CREm
+ (bML5K:jl7ntsH213:Q:1BjWS8Lrqp:3 qSg2 93) SgXoC 338602
+ [mr7R wS405TMYlnq7:Jx:e7:Qv8uElrM01Ww] 596325 IEVnen
+ ; V220WkzjO p:eVRrzBl473Nht:26U
+ o1h:E
+ ; n:34:ThQmLaTUfgbrNm:71p1C9
+
+2187/01/03=1987/04/24 * (e) S:8O8F8P4672h6 ; UEJwkn
+ (iA:R70:9:Wzp4x:v9skm) SXV 822239
+ o6Vo3X:HmQ:8s94RFZ8:fbxJYY2:xy6 wE 129507 @ 7.102689 hq
+ ; T:Dr0SwdlwU:3gn49
+ TDM8vS3 4:l497yk2f2 d307311 @ 0.344104 xOqmt
+ ; hrZn9 5BaTBOkT16 y:65f848liI558:f9
+ ADHTI7b:417f9NC p:81Dn:dd:2t58QPvkr6 280677 ewaJgw @ OhuZ248193 ; US178U5:E2P
+ JvY8mjt0 p1F ; Er nO
+
+2187/01/09 (u) I55B7BN7Nic0Xf7l:enK6:wZEp
+ [u7n1K7hy3kMKbPG hL] pIYM -959467
+ [Xe3P2P:oUoMA1slU09R] -544505SfH @ 852228 OkXknF
+ ; z uE9:xQ53Sy0 s3p53voVFV:bOP2mL9K4hBf:24
+ YJ5N:el1lEP1rksmQ4af8vXzlix:87 oXtoLV-494724
+ [f28 6:EG5sn 6clN E:689EhZ6rljIS9A05HB] -592178TcaIqX @ 0.52632KqgxJY
+ oiMz51lFxbnN
+
+2187/01/10 * (f:YKS) b152q
+ ; l0728x5Ep97f405oy5sTr4j
+ (X2l:yjc3P5Jf0R8slv7fn:DWa0HB63yUBY) -637008UW
+ b -526513qGuvC
+ (P9QJnILJLlK20j9) VJx -237745
+ ; H0:S1m6o:kpqGBmr0jn4yGp0PPx63680Bo
+ [JpVMi82HvZ8Cjn8R] jVSSg-913619 @ 0.808966 yoc
+ ; jYk3X58298IFBV7M TH3WN4n l0:x73agt4z
+ (HU 0M:e3A:030XdN3:8g) 243401kRXr @ eW 346761
+ ; V6tnx1GMt6B0iT5DL9
+ Eh EjQL:5Rj:68NZ42kd 16djH k883779 @ 652143 KQK
+ hlxM319I2412y
+
+2187/01/14 (p7NK) i56
+ (E0V bMA) -901145n
+ ; I0G5G9axvs32G0R19D72Nj
+ c:6q9 dM:73:5Nc1p5g:c0 q -764271.00 @ 0.310259Pwv
+ [Nf2:jSoF:X79mOgTa:t4 CC8903Ug 0cf Gv4w] -345268RzzzmL @ e 1.692647
+ ; R:KT0Y:WIkiD3UB2Fu6:CK5X6G6K1
+ (d) PDGe 828999
+ ; iFk6G5XkFII6739 31Dq2Qde25a:vfvRL0
+ NDODeT1ud913r9L:s7JDMkkGwNHEoac ; FyMDFsNG
+
+2187/01/20=1987/04/29 ! (uvZ) C79:n5SB4r
+ OV35363j:REM:JjkW -996837ufeCSQ @ 0.47472dq ; EqA:SE2QJpd20a
+ [JTaWorK:018RpRg1y23Eh2FzS:o84BimcBP] 644258 VmNxi
+ uh:l8fPO05YCYi053
+
+2187/01/23 * (M) p4yw23Z7z708H2t 1d108W509F XtQHd
+ ; JrdATcORmZ7F4:RilGAo4nPERSla K8:q319
+ (pgNXjW:7:w c6434RmZg9x39A QgUoDd8NZ) SUO 203170
+ [ob] DHAra-72498.6 @ 2.7968954yeCD
+ ; xTxkz:Qo:27 6146k0
+ [o:1wP7Q2] 77384 dQolH @ 492890 l ; H:d6 Yu
+ [vhjm6FF] -597065 iBP @ brzs0.0717997
+ B1V:H4agb9:aLP9235e2b
+
+2187/01/29 (KQhu) Q7x65f8ls25W88oyPR1H56I
+ (lX 0:3ZQ8e:f:We0b4lAUvFl7lf5x208) 383456kQrwsT
+ ; wa0f6v8ZJ7:on7T
+ [She:qWK1Qaijt7B5ua0] kniumo499909
+ ; Z:CP:A5hK5uN4JI7FMqS3V:vyVS2
+ [Y68R:A5N] 798458a
+ ; x:26gTlXF1:7:R6BMewcn:H:N8UXE
+ (QfdF:4GLhMy) b-547602
+ ; b aPGwAfp Qx:m952ctx495
+ [utb8nOU0r9N] mLHbE223101
+ A6f1R:zEq0Vea3gsKnC9:06 a224 ZmNQ 774115 @ 892757 OQv
+ ; X6g:0Ka:8E735uVV
+ W6zw11QYbiE3awi1d A
+ ; pXFvBnAu8E77qcw:ZUlwkWV5lZ:VkC3:5gZyaERW
+
+2187/02/01=1987/05/01 ! (wv5i) f6n2pmhOP77oyv6:IG5j0TEk9v5pXP9810s
+ V 5w Po:lVghM1g4t3BXKy B B:1Zz gT -123047
+ S3j:Aj4s 791849I
+ [B9:WfB9KIt4vLU51:Jm99I6Q125dMRIqx66vC5z2] 242497 A @ 583994 uXRfdk ; p5H8:j:o:fES
+ lS7d:q -106717 iefO
+ sj
+
+2187/02/04 (s) Z1yWLuX5yE4h:xT0X0Ga:o7:8T:0BbAvP46
+ ; X79u5:K mYCw9Utl
+ (uE:h:1jzdLirp r gP5Z:AF:79I) -711627oRDqJP @ CLpS1.184335 ; DUx7WX5j4nX
+ [t2xBfs53KgY vP:XxCk2bmJC92 3Sx5U4] 313034 GINL
+ ; lLpf64qmRAVY7J943jaE:c:l 4570l4:P
+ u8 84Cpd e3
+ ; g a m05HNW45pto sn0pJPv0U9r5bfT
+
+2187/02/07 (aj:5) X7rkc3PjvxTL l
+ V8aoGZ8p IL 50587.5vBeRJ
+ M6Ai3R:Q 325120 O
+ J5EUo35CK:qrg4E:V RznCQB3:q8MX1tX0CDyp 975643CDIcM @ 855572Twu ; bf5 A
+ [TM5Z] 913463 oimIl
+ YI8S9:X:C6zA 75
+
+2187/02/08=1987/05/03 (u5N2W) X2Lbql6n O:z2U1WB0 iDB8N0:4O286Be4h5q5
+ [q95j7F] Z -886734 @ 931353 bNt ; u0G KuFLh nq68
+ (Y:3LOBz1:S:nA7B) wwL-880685 @ 960618G
+ X9:gud7uR5Qxth7:f1Q0
+ ; F2lUncNAoUU4hWY DVe
+
+2187/02/10 * (E) n07K0X69w9Lm5n:Kas2ErCM6l4 48cR18Y:SnV
+ [R Cv H:6SpHO6:l63Gq:20JXImfc] -667443 y ; Kv
+ [mI9ZfM02qNaD8] 668500 RYSLl @ ddu0.19124
+ d:BPcI1lYVW9CcA6yL:4lhaK6ID717
+ ; N8Ix1H95urPX pLL5m:vz1mg:e A8
+
+2187/02/13=1987/05/05 ! (c) Bb7b10:fG8C 6 6Wx1l
+ [Ef] -85775.1dN @ 315212ejCU
+ (wLjq:0601Iqx:96 HF) 658188HKt
+ suu2OqWMY6sJFv:xm:Tx -638358 jnDT
+ [g:9S f8OoiA87l0I4Kqx3] -601167p @ 43014.8 aLq
+ ; S:2T:i:IzbT:bNP5snh:2Pk:5nu
+ lN 8U973P3983pe32X:A:219N:1
+ ; W:0 BO yy8:w8evC7YmboP ghx2:J9Xo
+
+2187/02/18 * (AN) oUwsLl:0Mm6p66d4leuup0:2x
+ ; c 5kp7U8G1k4:3qemxEP9IIYN8YUxA:8I9q
+ ysc8Prp:BAO3:NmJPIpZli -863446K @ VQ568439 ; tA6
+ (I 4u8PI1L81v:aOP1QE74d 1DDZBY e7) Xl -571100
+ ; C3SV V0zIpW0K6O:Uy1d:Q7dvwz:o5B EQ Nh
+ (L bD:5l2K650y8i9Ja) d-942453
+ ; FjCMw2cXC3T5S1 P7d:d:O63BwR6O
+ (qYLNQ:ApC) P -371484 @ WLbB 716482
+ ; LkfG:wU7kt 6Ayo9H9 ho
+ (AH7Z266:lo9126ooP:ok6IqMf0) -695058SXInq @ Abz 913865
+ ; Lqh76O:sSk3Y9zRR
+ [f:z:n2:bO7fwc230R:2Y7fjX9Yqq:649] RkqnLo 249887 @ vTD 1.7248
+ IW0M187 Ub
+ ; u4h0r2cSYS1kHW 9j6FyRk1y
+
+2187/02/20=1987/05/06 (W95) k
+ [TDrC8nTiD:r4Dx9177sQJvd16Ed9SvU mrE81:oD] bipfJi -629380
+ [cD2:Hy:DV6zL a647Aq7Pp71gTruf:0] BzPmpg 612835
+ ; W3FiFcDe53HIm789:kQN
+ (nP6CPmJd9g7jS72M 41q:t V5ltrdUx169Zu28) 975397 shihH
+ qT7c6 -655081 BXPPc @ Ya 872226 ; yIX7OGx
+ (mQSZ) 426510 zcBER @ tsnr606449 ; cJS:z12J4QvOA
+ [Yjfk:FmxjrVAa278WBuv5:50eG7w] Xake56556.8
+ uM61VJYWHmmS2swhTs1624firiH7MjO5Z5 M0Y
+
+2187/02/23=1987/05/08 ! (R) Dg8:279q:90GKH7:pq3WN kXi:C:4qf76
+ ; NlnAoFwM C7d3KH8A:6:lHr o8kxh6N130jo:kJ
+ VI6YZ -597872tIjW
+ (e:81C4:oO swE5f 9l:d9) wUna-360797 ; mJ y8mI
+ (a2y 6:iZLCdeW t10vqB7n3YN:7) 272995 NcKRN
+ ; taqP:G6i 8ePbU7GD59:r6iS
+ E46j:t7f50rjzczn -420867In
+ (qfK1A qE9SAMwwY:RbfPDS) lAcJ136457 ; AJ7hu5lhG2Q
+ m9GnlWF:P 2e6300i0o:K 930528Om @ 127927Rq
+ ; YN86XuS17Ax6fBYw8:FZB
+ v89c7V90x898JVnHFV:i28l3mMf1xTU:Bo7iJ2d ; YHqY
+
+2187/02/24=1987/05/12 * (t87o) mjrM4MyN8oKdpS4s
+ ; dh4gFZgBk5z29g2F44zT5I9g58Xpo:AYe9u3
+ (uQI843zXpD9vK6l2dc93) VDZfau695674 @ 1.395381tJ
+ A:UT:2t 4BPcec8xuFg Qb2sRg79Wt -715837BWwCJu
+ i uRN1hvI8
+ ; Yh 2:snU9F5u0Q7:eT507nI3mN oSE8MkeZ0kPnF
+
+2187/03/02 (JaP) M:j9:DzCYyd:M V6 ; YCd5tl8gTY7r
+ KWu6Q vju54Wqg:Hajy3K5 699037PsAu @ 0.0432019 dmmsRU
+ gm:9:Uf:t:jZCaY8ZoPqd UB:qjC5OtR v2y -65372.2DRpj @ aPXbpG0.66845387
+ ; HOntIQNDiUq2h7O88
+ Lq6
+ ; hB1 YKJgE pnE0a
+
+2187/03/08=1987/05/16 (fVUT) GB0y15 ; gw8ge764c35f
+ [fgY:5j4fG6:uBT] 994884 IuSb
+ [Qv84kw0f1cxt8MBl] -314694 SKN @ uIGy0.488405
+ ; S x865af7EOVbh09z1C:atG
+ C:aOmIU 6quv27:L8:fO0X61IFuzPKm
+ ; cU95Tz:wN9F99u2C:7QW2R6QdK:y:5Lcdf
+
+2187/03/11=1987/05/18 * (Q2P) kOT0:08TY
+ r19iHviTP MQmbBklzoe5Zy:8T4 -701612X @ 1.347401 dhCZ
+ j8hn 8a:zJvx -950080X ; X6V4:Vrz7:A9
+ ChMhD kwW2Drf8oW4 m23b6 rg17K cc
+
+2187/03/16 * (tZ) U C44:BGa4J6Bzwwb4o
+ [YK49QGHo:U167ZuH:Sus] -653997 r @ 990828 WsMH
+ ; YY7Vl4m0y6h:97Eb:t:WZ38
+ [rkd6eQ QnFRO4Eap1:o24ze7 MJta:5Lmw8y1] 713018 dOybc @ 867696 pqosoj
+ L rGB fhmmAj41N:75MlS:HI02Dhtvv92:9
+
+2187/03/19=1987/05/20 * (C) x4MFm08t8:Rv484
+ ; pteIOL8G3L7K:PiF:ed01NaE:5
+ jLXaJ7:TPkx:hnc0a84T949V -337155Pt
+ [z Cimq6yQ5 6Hz:4:y j3pTBDWr:Xwi5xa4fT0] egJUlL927254 @ ETLYp 830216
+ ; i5s0qZi5:it:qv1g 35vrb4GY:4y5
+ (aGO8o973:8:DqP588) cReiuY -813613 @ ZVwoPf 932860
+ [iG0OQ:tWl:Y:F59 R09pKiI] 499724 y @ qwy 1.437329
+ ; bNO myx5K3DhtxI0gZGc4eWCtZ:Nsb:l7Yx 3v
+ [OT2sQn:78mn:6dC6k2BPLg8Xu:1NV8GHg6cq9d] yFpTo -290498 @ PdADxe 2.729485
+ W2C:PzB:a7iA:VHeXq7gYKI vM2611:bCoq aHgK 381854 @ 721958RKv
+ pPYXx2wrFvMC:94zr3C9T1TdhR6D4
+
+2187/03/25 * (R) Z6et66o5F44:HG74D5gdk9
+ ; I8k7lSGq5223eFvFeSX4B20O
+ EiOxe -49170.3ptFX @ WYtmyT 781596
+ [cn U0zGj:Ylm2R:4a Sy] pxLB-155187
+ x:yw8duO9Ic:4CqN:AUa xd
+
+2187/03/27 * (tg) VirzaV9213J8:jkhY7b
+ (Kk:22T6893q129HLfMQ9QZf K66Kc:1 Qu:51Ki) hEP288640 @ 1.734694 ES
+ [yP7cI1x:Ns7Ik:CF 8W:2r:tT8r:ND3K4naIL] vnCq-270931 @ 368422 Qum
+ [OkJ:WvF64D87M22b58xgnAS2yf85] -542640 eM
+ [P U] 602501 oUB
+ c32Ww93:Z:mj7BK
+
+2187/03/28=1987/05/21 ! (kIWM) lz L5
+ (HoA7y0U4v4R458U80K0wqj4V8L:3Hy hK:u2o) baf 692841
+ (tX) -864521 x @ Vy0.360209 ; Wxe043p9:061
+ (Lq:5g7L:wwv:HyHFPCZYy) MYpd631128 @ 508656Cz
+ (x5 79ER 2Z3I0U19 47VfLtNKOq:2X) -696823bSC ; a8:Ad6LdI
+
+2187/03/29=1987/05/24 * (O) E0Rfh34
+ t4FdLx3:l:Ck6m3VJ stAj-908528
+ ; vQ8h:OiGS:f7CG 5 Y7fBz d10
+ z 2936srygkDWhn1c4jyG:L:nHpTrnLB51 vG75178.3
+ ; UfL7dG5a87Mm430vKtQ4Z:M2sdMzh3:a
+ [bdA:M:AM9FCXQm6HMGIB7 Lz:YV4tm3:r:SN] 38780.8HvaL @ HamR 19.0182771
+ ; z4KY C:Q:3h2 X 7
+ (R:n9:lXU) YTx-417744 @ 1.230378 D
+ [DLs 9 mtV 1G:b:Bep yVU n93Hbd:8By2Mx:M] -48707.2 a
+ [R:Goz1E0Bj:blF5Hl] -298794rPVm @ 3.283965 bUoyr
+ ; KYa8M9:6482B650mXUE7b:Ka
+ w8NK7U54q7vHGU0l30t:Il:3P:U T:SfY0
+
+2187/04/01=1987/05/26 ! (Eq) M59Ml:3
+ K ASPeZQ:7LW:wzN2QaUr77OXr wS-848544 @ Exd 0.625834
+ (tX0 1Uhq2Q969Z5x07I4B:8:n2t4T4V:E:6K) rQem -637441 ; ErSB:G0z
+ RqXG2 IZT646tE30:n84Y fEt-767950 @ 0.594623 Hkqq
+ ; pw3k2j 4KT D3Uo:B27e:WKDiuOD43L
+ [C3] BheR-157970
+ (A8p:062sBPYe8e:6oK:n 8X:87Qn) 982770 uVsEo
+ ; oHE U:F7z1f1b8mTv:s 2KwNo7 WgI
+ [D20EU:1] Sppd 837068
+ Bdn0znI799:07:s60 M5099z7
+ ; dEGfxH:U8p6z8DBSZ:diiQ49H21 6
+
+2187/04/04 * (M80a) Z:k206
+ [x7:anc] 437316 Dqk @ eZCzj 0.285032
+ [hC705 NtL:iP:q3608P93d7P:GdE] 544670li
+ ; oy:4zAY3upE31j0VUKCo95b84:O9E5d365E:4
+ ysN:P:56bd43Z:v:krkEyf R y287D
+ ; eZ U0AU4:MSGD:jjK7O94rD3webUWW1:4r0:07
+
+2187/04/07 (vvA) wc05O32W:H34:Y990
+ jBCX1J524A63U1k1j98823 2PYK886n 391730qA
+ tf50 ptOxzto:d6O0ye:cg8MIIR9C3ZlTRQ1 nx920305 @ 1.019439 MyVL
+ (UaTIt08Xl47:98zv83X92k3Ap) nlfkS 154433
+ (j3M44pR2dc4J:rTP7oN 2kO5 GOp:2zo8I) PXO-126823
+ ; yM21414i0eV2HAu50r61:5WN:2ApRhE:sM:ic
+ Xs551d 14OR5:j:5:u
+ ; psjmtc 57 BvF44CTmU7BXYna9w:2e58Wk5dV
+
+2187/04/12 * (Rh2) xe:4694A4vNXtTO:BRdX4n:U:6gLXW4atziLPi
+ h:E31A:6lSbJKE181aNzg2l2s1q -158224QX @ dKAD 4.682267
+ (pfitL560:H:rL2O7L) 976398 wMZ @ t486509
+ Motxoa 3u1 D 58373.9 V @ 3.1142171 a
+ ; tnBH6o 757IL 3:bche8 5 V:6 F29
+ [U 76 7PO6E2M6fJ47pk3no K1KI6r] 690776 cL
+ ; Tz1W zMl:6r0Sr67wwiv6mHI1:UMowDl
+ (A:4s97lX2ZJg5XL:JASmf:H) wTEdPr 126890
+ ; E8N231t1v62wd53wZ3S7ZJ
+ [MkN6f0006:53MX36298W vL2Rr] 131653voz
+ i:Y82PTu8o0RsU5c393 ; m09zvwT873z02
+
+2187/04/16 * (O) g09nmf1f ; fiQ T4D2:E
+ pNwv4w va614303 @ BfEt776882
+ [z 8M3qc:0W6 323:7 HE46vE5goXpC4C] GZatQD349279 ; c79
+ N6Ny7D0NikU2Qzy:W:N4M:94F7D:C zua3Gr ol-473902 @ TiG1.58947
+ ; USUm8 I01T7:rL9xC4H6QaI1fI4zg4iw9D9D94NY
+ iK9PGH2Lr:5xL 11077X ; WiNki
+ QxGAnH:0i1:5:hpK1454yx
+
+2187/04/18=1987/05/27 (d6 76) WKycS8BB4k2pWB6U1 TqC799Q3Q:Q:uvDbP v
+ ; S1 H:53Fz:Z9RY7660oS2:iEzlx8zId1KHlAp13
+ WAIZL7YC8T6xsxb C lEBkQZcC6ZYX8oUdjs aBsqCO-859192 ; X
+ (dVeDho1zLvBM1:bc2907v rw6w:fIyj) rJO-348003 @ 0.5828lNOAP
+ [VYR86aOPCBtU:T43s31qNrcmX55] 148376Kaqj
+ ; Hb5Mz2T33FTGp:6mf92L1
+ g0P FOnuBD634440
+ Eu2590k:6 542890 YbTkPd
+ ; MG7:fX1QN7F:aBWcZ WK:miVS9IS4
+ TXhdCnW:q3 83Q0:A:TLa:P1 389207Qu @ ZxD 474844
+ ; ze Xr:DiUmY4O5a494 E 7C jd9f:y
+ ej:OV1p
+ ; UQIvt cFi75q3l7:33q9V4epmq1c3o:MQY9:4
+
+2187/04/19=1987/05/29 * (yIEGx) vtu yZ:FLNg8 bz81244o 1
+ [eQ0:vQFftKq312 5:qt4OJRF14:8iUS9] -514867H
+ [zS4kj:1U364n 77yT4heLR0WCtP:J:qpTVC] C 626719
+ ; k61HYqVgh4pw4PSJ:8Zd5c0LvKhEqop
+ hQu J5:iL7i:dlczgv5 aUXZ:SW20:d
+ ; B6CQoX:m7:T0DObHKf:c35E tVQg:6N7 XR0yk1F
+
+2187/04/25 (d5) mO3V65tcQ93f:bpH2O4KQc7i8A3ubo64L
+ [cY8j:X:D1g53O] 913440 AkznZ
+ ; qiWp0:1K:9:yoagiids8YU8GazTJnDZyJMuJjU
+ [h 6v3:GS9 8] w 909579.0 @ 0.439321 a
+ QpWryY4
+
+2187/04/26 * (Oa X8v) nTu ; Blnf
+ RcSi7T4:bA87Zk v1a6Bue2RiF3Q:1vHkrC Yd-594954 @ 379530 uXzDxT
+ (jlB2E47ka6U7mwZ3Tp:56wE:w Gr5pco7WO) MJ -210135
+ (HrjMwB92A2:4:bnLz1hf5EAIx8wo71 AnSM83) PzifkG-18466.2 @ 532525.0 a
+ a E913Z48r PEZ930954 @ TWN0.26211
+ K4:3w6:okgb7jHi3He7DMv:h6N:W15Go9 ; OLa p:p6ITR
+
+2187/04/28 ! (eq) I87a 1T 0f95
+ (wD) ZGCAmF-128472
+ ; h6161Ck29nuU0N764bi 4g:0W
+ (Ri:q26Y9xE ubzMbbB672:pM0:QA) 68525.6QBwZl @ 750604 joWS
+
+2187/04/30=1987/06/03 ! (B) tv2:A:T22kI16n5J6mbHh:qZj:E:86
+ (ktL4z m2B5cKS4m53CgD:MnTTq584P8:6) 655291 eOEUll @ 594372OkUXd ; upZep79v
+ (bYT3j0yJy8fIq:2O48R70YV5T34) -498403 RaGA @ 1.611509AfRXi
+ G:2XSwTH3:WI7Y8:H:Kk 134595OIIOE
+ ; p:38 jzL2NZhQjy5fIB0S:ixVU0
+ [BTLZ Y7Kd8W:mo6rR] -142806 ZUSM @ iV629749
+ ; VRFPZ40e:B8vMk8VYpyaxYT T1f1jY8qHg69LxTr
+ (QzQvt5:a) 863612 C
+ ; UW7ldN1R6:JV79EzO9I5nkQKqXNx
+ (K0jG1Nn6xW6qmGoK852Wm82P891w O) w -639451.0
+ ; b2ERs49v6Ri734X01
+ STI0MT
+ ; kpM7x:a6:n61Z1k7 uY4ABsJ93
+
+2187/05/06=1987/06/07 ! (DWS3) G Z r2r iYLKfxR7:vrl9vr707P6pb 94XSd
+ HC5FR0S vxXRGK -142673 @ xAV 605535
+ [Fy1:u15syG 8:8C 38vB:178ExtQk5eBMq7F:blU] -130770oz
+ QvJZ:t xw:N
+
+2187/05/09 * (nKVz6) a9:T7
+ ; eT75RyW4Xd8a f1a679UnNi SH065V KnOo9
+ l7liL:4Qnz07kFUQ 800903 emTA @ 976479MTcm
+ (a:d) -761443Ay @ 0.576885 TDa
+ i
+
+2187/05/12=1987/06/09 ! (G3CN) nm r5
+ (rsiBbx8T EXY5) 386928 flTii @ 2.000225 DE
+ HQg3:FK1EgO -941250jElq ; Yn6gjO
+ C42Y z:mOpuX:f:nnk:tx
+
+2187/05/13 * (r6e y0) V65by ; Z X8Whns
+ [h96nQFF32fyO8y] 968994 UBNDg
+ ; T72YEmnsvcmFX91DLNMg w2948464
+ [Qwk40WBS0:N2V WUW:BM7M4:09961G0] 130897QkeP @ 719156 E
+ l5C402g:8ym9I0T1id zl7:sa811F1q vbUv:8Q
+ ; t5AnGg88R45W:S161kRC0o
+>>>2
+=== 0
diff --git a/test/baseline/opt-sort-all.test b/test/baseline/opt-sort-all.test
new file mode 100644
index 00000000..b289f8e8
--- /dev/null
+++ b/test/baseline/opt-sort-all.test
@@ -0,0 +1,122 @@
+reg --monthly --sort=-amount
+<<<
+2008/01/11 LIAT
+ Expenses:Travel:Airfare $40.00
+ Liabilities:MasterCard
+
+2008/01/14 cheaptickets.com
+ Expenses:Travel:Airfare $182.19
+ Liabilities:MasterCard
+
+2008/02/05 CTX
+ Expenses:Travel:Auto $240.38
+ Liabilities:MasterCard
+
+2008/02/05 UNITED
+ Expenses:Travel:Airfare $238.80
+ Liabilities:MasterCard
+
+2008/02/05 UNITED
+ Expenses:Travel:Airfare $238.80
+ Liabilities:MasterCard
+
+2008/02/22 BUDGET RENT-A-CAR
+ Expenses:Travel:Auto $40.59
+ Liabilities:MasterCard
+
+2008/03/16 IBERIA
+ Expenses:Travel:Airfare $1,231.60
+ Liabilities:MasterCard
+
+2008/03/16 IBERIA
+ Expenses:Travel:Airfare $1,231.60
+ Liabilities:MasterCard
+
+2008/04/03 AMERICAN
+ Expenses:Travel:Airfare $155.86
+ Liabilities:MasterCard
+
+2008/04/03 AMERICAN
+ Expenses:Travel:Airfare $155.86
+ Liabilities:MasterCard
+
+2008/04/30 UNITED
+ Expenses:Travel:Airfare $437.21
+ Liabilities:MasterCard
+
+2008/04/30 UNITED
+ Expenses:Travel:Airfare $437.21
+ Liabilities:MasterCard
+
+2008/08/08 BCIS I-131 FILING FEE-
+ Expenses:Travel:Passport $170.00
+ Liabilities:MasterCard
+
+2008/09/06 AMERICAN
+ Expenses:Travel:Airfare $912.60
+ Liabilities:MasterCard
+
+2008/09/06 AMERICAN
+ Expenses:Travel:Airfare $912.60
+ Liabilities:MasterCard
+
+2008/09/22 AGNT FEE
+ Expenses:Travel:Airfare $70.00
+ Liabilities:MasterCard
+
+2008/09/22 DELTA
+ Expenses:Travel:Airfare $806.20
+ Liabilities:MasterCard
+
+2008/09/22 DELTA
+ Expenses:Travel:Airfare $806.20
+ Liabilities:MasterCard
+
+2008/09/22 LIAT 1974 LIMITED
+ Expenses:Travel:Airfare $418.34
+ Liabilities:MasterCard
+
+2008/12/26 U.S. Department of State
+ Expenses:Travel:Passport $127.00
+ Assets:Checking
+
+2008/12/26 U.S. Department of State
+ Expenses:Travel:Passport $127.00
+ Assets:Checking
+>>>1
+08-Jan-01 - 08-Jan-31 Ex:Travel:Airfare $222.19 $222.19
+ Liabilities:MasterCard $-222.19 0
+08-Feb-01 - 08-Feb-29 Ex:Travel:Airfare $477.60 $477.60
+ Expenses:Travel:Auto $280.97 $758.57
+ Liabilities:MasterCard $-758.57 0
+08-Mar-01 - 08-Mar-31 Ex:Travel:Airfare $2,463.20 $2,463.20
+ Liabilities:MasterCard $-2,463.20 0
+08-Apr-01 - 08-Apr-30 Ex:Travel:Airfare $1,186.14 $1,186.14
+ Liabilities:MasterCard $-1,186.14 0
+08-Aug-01 - 08-Aug-31 Ex:Travel:Passport $170.00 $170.00
+ Liabilities:MasterCard $-170.00 0
+08-Sep-01 - 08-Sep-30 Ex:Travel:Airfare $3,925.94 $3,925.94
+ Liabilities:MasterCard $-3,925.94 0
+08-Dec-01 - 08-Dec-31 Ex:Travel:Passport $254.00 $254.00
+ Assets:Checking $-254.00 0
+>>>2
+=== 0
+reg --monthly --sort-all=-amount
+>>>1
+08-Sep-01 - 08-Sep-30 Ex:Travel:Airfare $3,925.94 $3,925.94
+08-Mar-01 - 08-Mar-31 Ex:Travel:Airfare $2,463.20 $6,389.14
+08-Apr-01 - 08-Apr-30 Ex:Travel:Airfare $1,186.14 $7,575.28
+08-Feb-01 - 08-Feb-29 Ex:Travel:Airfare $477.60 $8,052.88
+ Expenses:Travel:Auto $280.97 $8,333.85
+08-Dec-01 - 08-Dec-31 Ex:Travel:Passport $254.00 $8,587.85
+08-Jan-01 - 08-Jan-31 Ex:Travel:Airfare $222.19 $8,810.04
+08-Aug-01 - 08-Aug-31 Ex:Travel:Passport $170.00 $8,980.04
+ Liabilities:MasterCard $-170.00 $8,810.04
+08-Jan-01 - 08-Jan-31 Liabilities:MasterCard $-222.19 $8,587.85
+08-Dec-01 - 08-Dec-31 Assets:Checking $-254.00 $8,333.85
+08-Feb-01 - 08-Feb-29 Liabilities:MasterCard $-758.57 $7,575.28
+08-Apr-01 - 08-Apr-30 Liabilities:MasterCard $-1,186.14 $6,389.14
+08-Mar-01 - 08-Mar-31 Liabilities:MasterCard $-2,463.20 $3,925.94
+08-Sep-01 - 08-Sep-30 Liabilities:MasterCard $-3,925.94 0
+>>>2
+=== 0
diff --git a/test/baseline/opt-sort-xacts.test b/test/baseline/opt-sort-xacts.test
new file mode 100644
index 00000000..4882e18f
--- /dev/null
+++ b/test/baseline/opt-sort-xacts.test
@@ -0,0 +1,176 @@
+reg --sort=account
+<<<
+2008/01/11 LIAT
+ Expenses:Travel:Airfare $40.00
+ Liabilities:MasterCard
+
+2008/01/14 cheaptickets.com
+ Expenses:Travel:Airfare $182.19
+ Liabilities:MasterCard
+
+2008/02/05 CTX
+ Expenses:Travel:Auto $240.38
+ Liabilities:MasterCard
+
+2008/02/05 UNITED
+ Expenses:Travel:Airfare $238.80
+ Liabilities:MasterCard
+
+2008/02/05 UNITED
+ Expenses:Travel:Airfare $238.80
+ Liabilities:MasterCard
+
+2008/02/22 BUDGET RENT-A-CAR
+ Expenses:Travel:Auto $40.59
+ Liabilities:MasterCard
+
+2008/03/16 IBERIA
+ Expenses:Travel:Airfare $1,231.60
+ Liabilities:MasterCard
+
+2008/03/16 IBERIA
+ Expenses:Travel:Airfare $1,231.60
+ Liabilities:MasterCard
+
+2008/04/03 AMERICAN
+ Expenses:Travel:Airfare $155.86
+ Liabilities:MasterCard
+
+2008/04/03 AMERICAN
+ Expenses:Travel:Airfare $155.86
+ Liabilities:MasterCard
+
+2008/04/30 UNITED
+ Expenses:Travel:Airfare $437.21
+ Liabilities:MasterCard
+
+2008/04/30 UNITED
+ Expenses:Travel:Airfare $437.21
+ Liabilities:MasterCard
+
+2008/08/08 BCIS I-131 FILING FEE-
+ Expenses:Travel:Passport $170.00
+ Liabilities:MasterCard
+
+2008/09/06 AMERICAN
+ Expenses:Travel:Airfare $912.60
+ Liabilities:MasterCard
+
+2008/09/06 AMERICAN
+ Expenses:Travel:Airfare $912.60
+ Liabilities:MasterCard
+
+2008/09/22 AGNT FEE
+ Expenses:Travel:Airfare $70.00
+ Liabilities:MasterCard
+
+2008/09/22 DELTA
+ Expenses:Travel:Airfare $806.20
+ Liabilities:MasterCard
+
+2008/09/22 DELTA
+ Expenses:Travel:Airfare $806.20
+ Liabilities:MasterCard
+
+2008/09/22 LIAT 1974 LIMITED
+ Expenses:Travel:Airfare $418.34
+ Liabilities:MasterCard
+
+2008/12/26 U.S. Department of State
+ Expenses:Travel:Passport $127.00
+ Assets:Checking
+
+2008/12/26 U.S. Department of State
+ Expenses:Travel:Passport $127.00
+ Assets:Checking
+>>>1
+08-Dec-26 U.S. Department of .. Assets:Checking $-127.00 $-127.00
+08-Dec-26 U.S. Department of .. Assets:Checking $-127.00 $-254.00
+08-Jan-11 LIAT Ex:Travel:Airfare $40.00 $-214.00
+08-Jan-14 cheaptickets.com Ex:Travel:Airfare $182.19 $-31.81
+08-Feb-05 UNITED Ex:Travel:Airfare $238.80 $206.99
+08-Feb-05 UNITED Ex:Travel:Airfare $238.80 $445.79
+08-Mar-16 IBERIA Ex:Travel:Airfare $1,231.60 $1,677.39
+08-Mar-16 IBERIA Ex:Travel:Airfare $1,231.60 $2,908.99
+08-Apr-03 AMERICAN Ex:Travel:Airfare $155.86 $3,064.85
+08-Apr-03 AMERICAN Ex:Travel:Airfare $155.86 $3,220.71
+08-Apr-30 UNITED Ex:Travel:Airfare $437.21 $3,657.92
+08-Apr-30 UNITED Ex:Travel:Airfare $437.21 $4,095.13
+08-Sep-06 AMERICAN Ex:Travel:Airfare $912.60 $5,007.73
+08-Sep-06 AMERICAN Ex:Travel:Airfare $912.60 $5,920.33
+08-Sep-22 AGNT FEE Ex:Travel:Airfare $70.00 $5,990.33
+08-Sep-22 DELTA Ex:Travel:Airfare $806.20 $6,796.53
+08-Sep-22 DELTA Ex:Travel:Airfare $806.20 $7,602.73
+08-Sep-22 LIAT 1974 LIMITED Ex:Travel:Airfare $418.34 $8,021.07
+08-Feb-05 CTX Expenses:Travel:Auto $240.38 $8,261.45
+08-Feb-22 BUDGET RENT-A-CAR Expenses:Travel:Auto $40.59 $8,302.04
+08-Aug-08 BCIS I-131 FILING F.. Ex:Travel:Passport $170.00 $8,472.04
+08-Dec-26 U.S. Department of .. Ex:Travel:Passport $127.00 $8,599.04
+08-Dec-26 U.S. Department of .. Ex:Travel:Passport $127.00 $8,726.04
+08-Jan-11 LIAT Liabilities:MasterCard $-40.00 $8,686.04
+08-Jan-14 cheaptickets.com Liabilities:MasterCard $-182.19 $8,503.85
+08-Feb-05 CTX Liabilities:MasterCard $-240.38 $8,263.47
+08-Feb-05 UNITED Liabilities:MasterCard $-238.80 $8,024.67
+08-Feb-05 UNITED Liabilities:MasterCard $-238.80 $7,785.87
+08-Feb-22 BUDGET RENT-A-CAR Liabilities:MasterCard $-40.59 $7,745.28
+08-Mar-16 IBERIA Liabilities:MasterCard $-1,231.60 $6,513.68
+08-Mar-16 IBERIA Liabilities:MasterCard $-1,231.60 $5,282.08
+08-Apr-03 AMERICAN Liabilities:MasterCard $-155.86 $5,126.22
+08-Apr-03 AMERICAN Liabilities:MasterCard $-155.86 $4,970.36
+08-Apr-30 UNITED Liabilities:MasterCard $-437.21 $4,533.15
+08-Apr-30 UNITED Liabilities:MasterCard $-437.21 $4,095.94
+08-Aug-08 BCIS I-131 FILING F.. Liabilities:MasterCard $-170.00 $3,925.94
+08-Sep-06 AMERICAN Liabilities:MasterCard $-912.60 $3,013.34
+08-Sep-06 AMERICAN Liabilities:MasterCard $-912.60 $2,100.74
+08-Sep-22 AGNT FEE Liabilities:MasterCard $-70.00 $2,030.74
+08-Sep-22 DELTA Liabilities:MasterCard $-806.20 $1,224.54
+08-Sep-22 DELTA Liabilities:MasterCard $-806.20 $418.34
+08-Sep-22 LIAT 1974 LIMITED Liabilities:MasterCard $-418.34 0
+>>>2
+=== 0
+reg --sort-xacts=account
+>>>1
+08-Jan-11 LIAT Ex:Travel:Airfare $40.00 $40.00
+ Liabilities:MasterCard $-40.00 0
+08-Jan-14 cheaptickets.com Ex:Travel:Airfare $182.19 $182.19
+ Liabilities:MasterCard $-182.19 0
+08-Feb-05 CTX Expenses:Travel:Auto $240.38 $240.38
+ Liabilities:MasterCard $-240.38 0
+08-Feb-05 UNITED Ex:Travel:Airfare $238.80 $238.80
+ Liabilities:MasterCard $-238.80 0
+08-Feb-05 UNITED Ex:Travel:Airfare $238.80 $238.80
+ Liabilities:MasterCard $-238.80 0
+08-Feb-22 BUDGET RENT-A-CAR Expenses:Travel:Auto $40.59 $40.59
+ Liabilities:MasterCard $-40.59 0
+08-Mar-16 IBERIA Ex:Travel:Airfare $1,231.60 $1,231.60
+ Liabilities:MasterCard $-1,231.60 0
+08-Mar-16 IBERIA Ex:Travel:Airfare $1,231.60 $1,231.60
+ Liabilities:MasterCard $-1,231.60 0
+08-Apr-03 AMERICAN Ex:Travel:Airfare $155.86 $155.86
+ Liabilities:MasterCard $-155.86 0
+08-Apr-03 AMERICAN Ex:Travel:Airfare $155.86 $155.86
+ Liabilities:MasterCard $-155.86 0
+08-Apr-30 UNITED Ex:Travel:Airfare $437.21 $437.21
+ Liabilities:MasterCard $-437.21 0
+08-Apr-30 UNITED Ex:Travel:Airfare $437.21 $437.21
+ Liabilities:MasterCard $-437.21 0
+08-Aug-08 BCIS I-131 FILING F.. Ex:Travel:Passport $170.00 $170.00
+ Liabilities:MasterCard $-170.00 0
+08-Sep-06 AMERICAN Ex:Travel:Airfare $912.60 $912.60
+ Liabilities:MasterCard $-912.60 0
+08-Sep-06 AMERICAN Ex:Travel:Airfare $912.60 $912.60
+ Liabilities:MasterCard $-912.60 0
+08-Sep-22 AGNT FEE Ex:Travel:Airfare $70.00 $70.00
+ Liabilities:MasterCard $-70.00 0
+08-Sep-22 DELTA Ex:Travel:Airfare $806.20 $806.20
+ Liabilities:MasterCard $-806.20 0
+08-Sep-22 DELTA Ex:Travel:Airfare $806.20 $806.20
+ Liabilities:MasterCard $-806.20 0
+08-Sep-22 LIAT 1974 LIMITED Ex:Travel:Airfare $418.34 $418.34
+ Liabilities:MasterCard $-418.34 0
+08-Dec-26 U.S. Department of .. Assets:Checking $-127.00 $-127.00
+ Ex:Travel:Passport $127.00 0
+08-Dec-26 U.S. Department of .. Assets:Checking $-127.00 $-127.00
+ Ex:Travel:Passport $127.00 0
+>>>2
+=== 0
diff --git a/test/baseline/opt-sort.test b/test/baseline/opt-sort.test
new file mode 100644
index 00000000..27efe31b
--- /dev/null
+++ b/test/baseline/opt-sort.test
@@ -0,0 +1,246 @@
+reg airfare --sort=date
+<<<
+2008/01/11 LIAT
+ Expenses:Travel:Airfare $40.00
+ Liabilities:MasterCard
+
+2008/01/14 cheaptickets.com
+ Expenses:Travel:Airfare $182.19
+ Liabilities:MasterCard
+
+2008/02/05 CTX
+ Expenses:Travel:Auto $240.38
+ Liabilities:MasterCard
+
+2008/02/05 UNITED
+ Expenses:Travel:Airfare $238.80
+ Liabilities:MasterCard
+
+2008/02/05 UNITED
+ Expenses:Travel:Airfare $238.80
+ Liabilities:MasterCard
+
+2008/02/22 BUDGET RENT-A-CAR
+ Expenses:Travel:Auto $40.59
+ Liabilities:MasterCard
+
+2008/03/16 IBERIA
+ Expenses:Travel:Airfare $1,231.60
+ Liabilities:MasterCard
+
+2008/03/16 IBERIA
+ Expenses:Travel:Airfare $1,231.60
+ Liabilities:MasterCard
+
+2008/04/03 AMERICAN
+ Expenses:Travel:Airfare $155.86
+ Liabilities:MasterCard
+
+2008/04/03 AMERICAN
+ Expenses:Travel:Airfare $155.86
+ Liabilities:MasterCard
+
+2008/04/30 UNITED
+ Expenses:Travel:Airfare $437.21
+ Liabilities:MasterCard
+
+2008/04/30 UNITED
+ Expenses:Travel:Airfare $437.21
+ Liabilities:MasterCard
+
+2008/08/08 BCIS I-131 FILING FEE-
+ Expenses:Travel:Passport $170.00
+ Liabilities:MasterCard
+
+2008/09/06 AMERICAN
+ Expenses:Travel:Airfare $912.60
+ Liabilities:MasterCard
+
+2008/09/06 AMERICAN
+ Expenses:Travel:Airfare $912.60
+ Liabilities:MasterCard
+
+2008/09/22 AGNT FEE
+ Expenses:Travel:Airfare $70.00
+ Liabilities:MasterCard
+
+2008/09/22 DELTA
+ Expenses:Travel:Airfare $806.20
+ Liabilities:MasterCard
+
+2008/09/22 DELTA
+ Expenses:Travel:Airfare $806.20
+ Liabilities:MasterCard
+
+2008/09/22 LIAT 1974 LIMITED
+ Expenses:Travel:Airfare $418.34
+ Liabilities:MasterCard
+
+2008/12/26 U.S. Department of State
+ Expenses:Travel:Passport $127.00
+ Assets:Checking
+
+2008/12/26 U.S. Department of State
+ Expenses:Travel:Passport $127.00
+ Assets:Checking
+>>>1
+08-Jan-11 LIAT Ex:Travel:Airfare $40.00 $40.00
+08-Jan-14 cheaptickets.com Ex:Travel:Airfare $182.19 $222.19
+08-Feb-05 UNITED Ex:Travel:Airfare $238.80 $460.99
+08-Feb-05 UNITED Ex:Travel:Airfare $238.80 $699.79
+08-Mar-16 IBERIA Ex:Travel:Airfare $1,231.60 $1,931.39
+08-Mar-16 IBERIA Ex:Travel:Airfare $1,231.60 $3,162.99
+08-Apr-03 AMERICAN Ex:Travel:Airfare $155.86 $3,318.85
+08-Apr-03 AMERICAN Ex:Travel:Airfare $155.86 $3,474.71
+08-Apr-30 UNITED Ex:Travel:Airfare $437.21 $3,911.92
+08-Apr-30 UNITED Ex:Travel:Airfare $437.21 $4,349.13
+08-Sep-06 AMERICAN Ex:Travel:Airfare $912.60 $5,261.73
+08-Sep-06 AMERICAN Ex:Travel:Airfare $912.60 $6,174.33
+08-Sep-22 AGNT FEE Ex:Travel:Airfare $70.00 $6,244.33
+08-Sep-22 DELTA Ex:Travel:Airfare $806.20 $7,050.53
+08-Sep-22 DELTA Ex:Travel:Airfare $806.20 $7,856.73
+08-Sep-22 LIAT 1974 LIMITED Ex:Travel:Airfare $418.34 $8,275.07
+>>>2
+=== 0
+reg airfare --sort=date,amount
+>>>1
+08-Jan-11 LIAT Ex:Travel:Airfare $40.00 $40.00
+08-Jan-14 cheaptickets.com Ex:Travel:Airfare $182.19 $222.19
+08-Feb-05 UNITED Ex:Travel:Airfare $238.80 $460.99
+08-Feb-05 UNITED Ex:Travel:Airfare $238.80 $699.79
+08-Mar-16 IBERIA Ex:Travel:Airfare $1,231.60 $1,931.39
+08-Mar-16 IBERIA Ex:Travel:Airfare $1,231.60 $3,162.99
+08-Apr-03 AMERICAN Ex:Travel:Airfare $155.86 $3,318.85
+08-Apr-03 AMERICAN Ex:Travel:Airfare $155.86 $3,474.71
+08-Apr-30 UNITED Ex:Travel:Airfare $437.21 $3,911.92
+08-Apr-30 UNITED Ex:Travel:Airfare $437.21 $4,349.13
+08-Sep-06 AMERICAN Ex:Travel:Airfare $912.60 $5,261.73
+08-Sep-06 AMERICAN Ex:Travel:Airfare $912.60 $6,174.33
+08-Sep-22 AGNT FEE Ex:Travel:Airfare $70.00 $6,244.33
+08-Sep-22 LIAT 1974 LIMITED Ex:Travel:Airfare $418.34 $6,662.67
+08-Sep-22 DELTA Ex:Travel:Airfare $806.20 $7,468.87
+08-Sep-22 DELTA Ex:Travel:Airfare $806.20 $8,275.07
+>>>2
+=== 0
+reg airfare --sort=date,-amount
+>>>1
+08-Jan-11 LIAT Ex:Travel:Airfare $40.00 $40.00
+08-Jan-14 cheaptickets.com Ex:Travel:Airfare $182.19 $222.19
+08-Feb-05 UNITED Ex:Travel:Airfare $238.80 $460.99
+08-Feb-05 UNITED Ex:Travel:Airfare $238.80 $699.79
+08-Mar-16 IBERIA Ex:Travel:Airfare $1,231.60 $1,931.39
+08-Mar-16 IBERIA Ex:Travel:Airfare $1,231.60 $3,162.99
+08-Apr-03 AMERICAN Ex:Travel:Airfare $155.86 $3,318.85
+08-Apr-03 AMERICAN Ex:Travel:Airfare $155.86 $3,474.71
+08-Apr-30 UNITED Ex:Travel:Airfare $437.21 $3,911.92
+08-Apr-30 UNITED Ex:Travel:Airfare $437.21 $4,349.13
+08-Sep-06 AMERICAN Ex:Travel:Airfare $912.60 $5,261.73
+08-Sep-06 AMERICAN Ex:Travel:Airfare $912.60 $6,174.33
+08-Sep-22 DELTA Ex:Travel:Airfare $806.20 $6,980.53
+08-Sep-22 DELTA Ex:Travel:Airfare $806.20 $7,786.73
+08-Sep-22 LIAT 1974 LIMITED Ex:Travel:Airfare $418.34 $8,205.07
+08-Sep-22 AGNT FEE Ex:Travel:Airfare $70.00 $8,275.07
+>>>2
+=== 0
+reg airfare --sort=-date,-amount
+>>>1
+08-Sep-22 DELTA Ex:Travel:Airfare $806.20 $806.20
+08-Sep-22 DELTA Ex:Travel:Airfare $806.20 $1,612.40
+08-Sep-22 LIAT 1974 LIMITED Ex:Travel:Airfare $418.34 $2,030.74
+08-Sep-22 AGNT FEE Ex:Travel:Airfare $70.00 $2,100.74
+08-Sep-06 AMERICAN Ex:Travel:Airfare $912.60 $3,013.34
+08-Sep-06 AMERICAN Ex:Travel:Airfare $912.60 $3,925.94
+08-Apr-30 UNITED Ex:Travel:Airfare $437.21 $4,363.15
+08-Apr-30 UNITED Ex:Travel:Airfare $437.21 $4,800.36
+08-Apr-03 AMERICAN Ex:Travel:Airfare $155.86 $4,956.22
+08-Apr-03 AMERICAN Ex:Travel:Airfare $155.86 $5,112.08
+08-Mar-16 IBERIA Ex:Travel:Airfare $1,231.60 $6,343.68
+08-Mar-16 IBERIA Ex:Travel:Airfare $1,231.60 $7,575.28
+08-Feb-05 UNITED Ex:Travel:Airfare $238.80 $7,814.08
+08-Feb-05 UNITED Ex:Travel:Airfare $238.80 $8,052.88
+08-Jan-14 cheaptickets.com Ex:Travel:Airfare $182.19 $8,235.07
+08-Jan-11 LIAT Ex:Travel:Airfare $40.00 $8,275.07
+>>>2
+=== 0
+bal --sort=total
+>>>1
+ $-8,726.04 Liabilities:MasterCard
+ $-254.00 Assets:Checking
+ $8,980.04 Expenses:Travel
+ $280.97 Auto
+ $424.00 Passport
+ $8,275.07 Airfare
+--------------------
+ 0
+>>>2
+=== 0
+bal --sort=-total
+>>>1
+ $8,980.04 Expenses:Travel
+ $8,275.07 Airfare
+ $424.00 Passport
+ $280.97 Auto
+ $-254.00 Assets:Checking
+ $-8,726.04 Liabilities:MasterCard
+--------------------
+ 0
+>>>2
+=== 0
+bal --sort=-account
+>>>1
+ $-8,726.04 Liabilities:MasterCard
+ $8,980.04 Expenses:Travel
+ $424.00 Passport
+ $280.97 Auto
+ $8,275.07 Airfare
+ $-254.00 Assets:Checking
+--------------------
+ 0
+>>>2
+=== 0
+reg --sort=account
+>>>1
+08-Dec-26 U.S. Department of .. Assets:Checking $-127.00 $-127.00
+08-Dec-26 U.S. Department of .. Assets:Checking $-127.00 $-254.00
+08-Jan-11 LIAT Ex:Travel:Airfare $40.00 $-214.00
+08-Jan-14 cheaptickets.com Ex:Travel:Airfare $182.19 $-31.81
+08-Feb-05 UNITED Ex:Travel:Airfare $238.80 $206.99
+08-Feb-05 UNITED Ex:Travel:Airfare $238.80 $445.79
+08-Mar-16 IBERIA Ex:Travel:Airfare $1,231.60 $1,677.39
+08-Mar-16 IBERIA Ex:Travel:Airfare $1,231.60 $2,908.99
+08-Apr-03 AMERICAN Ex:Travel:Airfare $155.86 $3,064.85
+08-Apr-03 AMERICAN Ex:Travel:Airfare $155.86 $3,220.71
+08-Apr-30 UNITED Ex:Travel:Airfare $437.21 $3,657.92
+08-Apr-30 UNITED Ex:Travel:Airfare $437.21 $4,095.13
+08-Sep-06 AMERICAN Ex:Travel:Airfare $912.60 $5,007.73
+08-Sep-06 AMERICAN Ex:Travel:Airfare $912.60 $5,920.33
+08-Sep-22 AGNT FEE Ex:Travel:Airfare $70.00 $5,990.33
+08-Sep-22 DELTA Ex:Travel:Airfare $806.20 $6,796.53
+08-Sep-22 DELTA Ex:Travel:Airfare $806.20 $7,602.73
+08-Sep-22 LIAT 1974 LIMITED Ex:Travel:Airfare $418.34 $8,021.07
+08-Feb-05 CTX Expenses:Travel:Auto $240.38 $8,261.45
+08-Feb-22 BUDGET RENT-A-CAR Expenses:Travel:Auto $40.59 $8,302.04
+08-Aug-08 BCIS I-131 FILING F.. Ex:Travel:Passport $170.00 $8,472.04
+08-Dec-26 U.S. Department of .. Ex:Travel:Passport $127.00 $8,599.04
+08-Dec-26 U.S. Department of .. Ex:Travel:Passport $127.00 $8,726.04
+08-Jan-11 LIAT Liabilities:MasterCard $-40.00 $8,686.04
+08-Jan-14 cheaptickets.com Liabilities:MasterCard $-182.19 $8,503.85
+08-Feb-05 CTX Liabilities:MasterCard $-240.38 $8,263.47
+08-Feb-05 UNITED Liabilities:MasterCard $-238.80 $8,024.67
+08-Feb-05 UNITED Liabilities:MasterCard $-238.80 $7,785.87
+08-Feb-22 BUDGET RENT-A-CAR Liabilities:MasterCard $-40.59 $7,745.28
+08-Mar-16 IBERIA Liabilities:MasterCard $-1,231.60 $6,513.68
+08-Mar-16 IBERIA Liabilities:MasterCard $-1,231.60 $5,282.08
+08-Apr-03 AMERICAN Liabilities:MasterCard $-155.86 $5,126.22
+08-Apr-03 AMERICAN Liabilities:MasterCard $-155.86 $4,970.36
+08-Apr-30 UNITED Liabilities:MasterCard $-437.21 $4,533.15
+08-Apr-30 UNITED Liabilities:MasterCard $-437.21 $4,095.94
+08-Aug-08 BCIS I-131 FILING F.. Liabilities:MasterCard $-170.00 $3,925.94
+08-Sep-06 AMERICAN Liabilities:MasterCard $-912.60 $3,013.34
+08-Sep-06 AMERICAN Liabilities:MasterCard $-912.60 $2,100.74
+08-Sep-22 AGNT FEE Liabilities:MasterCard $-70.00 $2,030.74
+08-Sep-22 DELTA Liabilities:MasterCard $-806.20 $1,224.54
+08-Sep-22 DELTA Liabilities:MasterCard $-806.20 $418.34
+08-Sep-22 LIAT 1974 LIMITED Liabilities:MasterCard $-418.34 0
+>>>2
+=== 0
diff --git a/test/baseline/opt-subtotal.test b/test/baseline/opt-subtotal.test
new file mode 100644
index 00000000..41defbc1
--- /dev/null
+++ b/test/baseline/opt-subtotal.test
@@ -0,0 +1,93 @@
+reg --subtotal
+<<<
+2008/01/11 LIAT
+ Expenses:Travel:Airfare $40.00
+ Liabilities:MasterCard
+
+2008/01/14 cheaptickets.com
+ Expenses:Travel:Airfare $182.19
+ Liabilities:MasterCard
+
+2008/02/05 CTX
+ Expenses:Travel:Auto $240.38
+ Liabilities:MasterCard
+
+2008/02/05 UNITED
+ Expenses:Travel:Airfare $238.80
+ Liabilities:MasterCard
+
+2008/02/05 UNITED
+ Expenses:Travel:Airfare $238.80
+ Liabilities:MasterCard
+
+2008/02/22 BUDGET RENT-A-CAR
+ Expenses:Travel:Auto $40.59
+ Liabilities:MasterCard
+
+2008/03/16 IBERIA
+ Expenses:Travel:Airfare $1,231.60
+ Liabilities:MasterCard
+
+2008/03/16 IBERIA
+ Expenses:Travel:Airfare $1,231.60
+ Liabilities:MasterCard
+
+2008/04/03 AMERICAN
+ Expenses:Travel:Airfare $155.86
+ Liabilities:MasterCard
+
+2008/04/03 AMERICAN
+ Expenses:Travel:Airfare $155.86
+ Liabilities:MasterCard
+
+2008/04/30 UNITED
+ Expenses:Travel:Airfare $437.21
+ Liabilities:MasterCard
+
+2008/04/30 UNITED
+ Expenses:Travel:Airfare $437.21
+ Liabilities:MasterCard
+
+2008/08/08 BCIS I-131 FILING FEE-
+ Expenses:Travel:Passport $170.00
+ Liabilities:MasterCard
+
+2008/09/06 AMERICAN
+ Expenses:Travel:Airfare $912.60
+ Liabilities:MasterCard
+
+2008/09/06 AMERICAN
+ Expenses:Travel:Airfare $912.60
+ Liabilities:MasterCard
+
+2008/09/22 AGNT FEE
+ Expenses:Travel:Airfare $70.00
+ Liabilities:MasterCard
+
+2008/09/22 DELTA
+ Expenses:Travel:Airfare $806.20
+ Liabilities:MasterCard
+
+2008/09/22 DELTA
+ Expenses:Travel:Airfare $806.20
+ Liabilities:MasterCard
+
+2008/09/22 LIAT 1974 LIMITED
+ Expenses:Travel:Airfare $418.34
+ Liabilities:MasterCard
+
+2008/12/26 U.S. Department of State
+ Expenses:Travel:Passport $127.00
+ Assets:Checking
+
+2008/12/26 U.S. Department of State
+ Expenses:Travel:Passport $127.00
+ Assets:Checking
+>>>1
+08-Jan-11 - 08-Dec-26 Assets:Checking $-254.00 $-254.00
+ Ex:Travel:Airfare $8,275.07 $8,021.07
+ Expenses:Travel:Auto $280.97 $8,302.04
+ Ex:Travel:Passport $424.00 $8,726.04
+ Liabilities:MasterCard $-8,726.04 0
+>>>2
+=== 0
diff --git a/test/baseline/opt-total-data.test b/test/baseline/opt-total-data.test
new file mode 100644
index 00000000..f1ab33c1
--- /dev/null
+++ b/test/baseline/opt-total-data.test
@@ -0,0 +1,10 @@
+reg --market --total-data
+<<<
+2007/02/02 RD VMMXX
+ Assets:Investments:Vanguard:VMMXX 0.350 VMMXX @ $1.00
+ Income:Dividends:Vanguard:VMMXX $-0.35
+>>>1
+2007-02-02 0.35
+2007-02-02 0
+>>>2
+=== 0
diff --git a/test/baseline/opt-total.test b/test/baseline/opt-total.test
new file mode 100644
index 00000000..8f4719d2
--- /dev/null
+++ b/test/baseline/opt-total.test
@@ -0,0 +1,10 @@
+reg --total=10
+<<<
+2007/02/02 RD VMMXX
+ Assets:Investments:Vanguard:VMMXX 0.350 VMMXX @ $1.00
+ Income:Dividends:Vanguard:VMMXX $-0.35
+>>>1
+07-Feb-02 RD VMMXX As:In:Vanguard:VMMXX 0.350 VMMXX 10
+ In:Di:Vanguard:VMMXX $-0.35 10
+>>>2
+=== 0
diff --git a/test/baseline/opt-truncate.test b/test/baseline/opt-truncate.test
new file mode 100644
index 00000000..0f9190c2
--- /dev/null
+++ b/test/baseline/opt-truncate.test
@@ -0,0 +1,144 @@
+reg airfare --truncate=leading
+<<<
+2008/01/11 LIAT
+ Expenses:Travel:Airfare $40.00
+ Liabilities:MasterCard
+
+2008/01/14 cheaptickets.com
+ Expenses:Travel:Airfare $182.19
+ Liabilities:MasterCard
+
+2008/02/05 CTX
+ Expenses:Travel:Auto $240.38
+ Liabilities:MasterCard
+
+2008/02/05 UNITED
+ Expenses:Travel:Airfare $238.80
+ Liabilities:MasterCard
+
+2008/02/05 UNITED
+ Expenses:Travel:Airfare $238.80
+ Liabilities:MasterCard
+
+2008/02/22 BUDGET RENT-A-CAR
+ Expenses:Travel:Auto $40.59
+ Liabilities:MasterCard
+
+2008/03/16 IBERIA
+ Expenses:Travel:Airfare $1,231.60
+ Liabilities:MasterCard
+
+2008/03/16 IBERIA
+ Expenses:Travel:Airfare $1,231.60
+ Liabilities:MasterCard
+
+2008/04/03 AMERICAN
+ Expenses:Travel:Airfare $155.86
+ Liabilities:MasterCard
+
+2008/04/03 AMERICAN
+ Expenses:Travel:Airfare $155.86
+ Liabilities:MasterCard
+
+2008/04/30 UNITED
+ Expenses:Travel:Airfare $437.21
+ Liabilities:MasterCard
+
+2008/04/30 UNITED
+ Expenses:Travel:Airfare $437.21
+ Liabilities:MasterCard
+
+2008/08/08 BCIS I-131 FILING FEE-
+ Expenses:Travel:Passport $170.00
+ Liabilities:MasterCard
+
+2008/09/06 AMERICAN
+ Expenses:Travel:Airfare $912.60
+ Liabilities:MasterCard
+
+2008/09/06 AMERICAN
+ Expenses:Travel:Airfare $912.60
+ Liabilities:MasterCard
+
+2008/09/22 AGNT FEE
+ Expenses:Travel:Airfare $70.00
+ Liabilities:MasterCard
+
+2008/09/22 DELTA
+ Expenses:Travel:Airfare $806.20
+ Liabilities:MasterCard
+
+2008/09/22 DELTA
+ Expenses:Travel:Airfare $806.20
+ Liabilities:MasterCard
+
+2008/09/22 LIAT 1974 LIMITED
+ Expenses:Travel:Airfare $418.34
+ Liabilities:MasterCard
+
+2008/12/26 U.S. Department of State
+ Expenses:Travel:Passport $127.00
+ Assets:Checking
+
+2008/12/26 U.S. Department of State
+ Expenses:Travel:Passport $127.00
+ Assets:Checking
+>>>1
+08-Jan-11 LIAT ..enses:Travel:Airfare $40.00 $40.00
+08-Jan-14 cheaptickets.com ..enses:Travel:Airfare $182.19 $222.19
+08-Feb-05 UNITED ..enses:Travel:Airfare $238.80 $460.99
+08-Feb-05 UNITED ..enses:Travel:Airfare $238.80 $699.79
+08-Mar-16 IBERIA ..enses:Travel:Airfare $1,231.60 $1,931.39
+08-Mar-16 IBERIA ..enses:Travel:Airfare $1,231.60 $3,162.99
+08-Apr-03 AMERICAN ..enses:Travel:Airfare $155.86 $3,318.85
+08-Apr-03 AMERICAN ..enses:Travel:Airfare $155.86 $3,474.71
+08-Apr-30 UNITED ..enses:Travel:Airfare $437.21 $3,911.92
+08-Apr-30 UNITED ..enses:Travel:Airfare $437.21 $4,349.13
+08-Sep-06 AMERICAN ..enses:Travel:Airfare $912.60 $5,261.73
+08-Sep-06 AMERICAN ..enses:Travel:Airfare $912.60 $6,174.33
+08-Sep-22 AGNT FEE ..enses:Travel:Airfare $70.00 $6,244.33
+08-Sep-22 DELTA ..enses:Travel:Airfare $806.20 $7,050.53
+08-Sep-22 DELTA ..enses:Travel:Airfare $806.20 $7,856.73
+08-Sep-22 LIAT 1974 LIMITED ..enses:Travel:Airfare $418.34 $8,275.07
+>>>2
+=== 0
+reg airfare --truncate=middle
+>>>1
+08-Jan-11 LIAT Expenses:T..el:Airfare $40.00 $40.00
+08-Jan-14 cheaptickets.com Expenses:T..el:Airfare $182.19 $222.19
+08-Feb-05 UNITED Expenses:T..el:Airfare $238.80 $460.99
+08-Feb-05 UNITED Expenses:T..el:Airfare $238.80 $699.79
+08-Mar-16 IBERIA Expenses:T..el:Airfare $1,231.60 $1,931.39
+08-Mar-16 IBERIA Expenses:T..el:Airfare $1,231.60 $3,162.99
+08-Apr-03 AMERICAN Expenses:T..el:Airfare $155.86 $3,318.85
+08-Apr-03 AMERICAN Expenses:T..el:Airfare $155.86 $3,474.71
+08-Apr-30 UNITED Expenses:T..el:Airfare $437.21 $3,911.92
+08-Apr-30 UNITED Expenses:T..el:Airfare $437.21 $4,349.13
+08-Sep-06 AMERICAN Expenses:T..el:Airfare $912.60 $5,261.73
+08-Sep-06 AMERICAN Expenses:T..el:Airfare $912.60 $6,174.33
+08-Sep-22 AGNT FEE Expenses:T..el:Airfare $70.00 $6,244.33
+08-Sep-22 DELTA Expenses:T..el:Airfare $806.20 $7,050.53
+08-Sep-22 DELTA Expenses:T..el:Airfare $806.20 $7,856.73
+08-Sep-22 LIAT 1974 LIMITED Expenses:T..el:Airfare $418.34 $8,275.07
+>>>2
+=== 0
+reg airfare --truncate=trailing
+>>>1
+08-Jan-11 LIAT Expenses:Travel:Airf.. $40.00 $40.00
+08-Jan-14 cheaptickets.com Expenses:Travel:Airf.. $182.19 $222.19
+08-Feb-05 UNITED Expenses:Travel:Airf.. $238.80 $460.99
+08-Feb-05 UNITED Expenses:Travel:Airf.. $238.80 $699.79
+08-Mar-16 IBERIA Expenses:Travel:Airf.. $1,231.60 $1,931.39
+08-Mar-16 IBERIA Expenses:Travel:Airf.. $1,231.60 $3,162.99
+08-Apr-03 AMERICAN Expenses:Travel:Airf.. $155.86 $3,318.85
+08-Apr-03 AMERICAN Expenses:Travel:Airf.. $155.86 $3,474.71
+08-Apr-30 UNITED Expenses:Travel:Airf.. $437.21 $3,911.92
+08-Apr-30 UNITED Expenses:Travel:Airf.. $437.21 $4,349.13
+08-Sep-06 AMERICAN Expenses:Travel:Airf.. $912.60 $5,261.73
+08-Sep-06 AMERICAN Expenses:Travel:Airf.. $912.60 $6,174.33
+08-Sep-22 AGNT FEE Expenses:Travel:Airf.. $70.00 $6,244.33
+08-Sep-22 DELTA Expenses:Travel:Airf.. $806.20 $7,050.53
+08-Sep-22 DELTA Expenses:Travel:Airf.. $806.20 $7,856.73
+08-Sep-22 LIAT 1974 LIMITED Expenses:Travel:Airf.. $418.34 $8,275.07
+>>>2
+=== 0
diff --git a/test/baseline/opt-unround.test b/test/baseline/opt-unround.test
new file mode 100644
index 00000000..70018f12
--- /dev/null
+++ b/test/baseline/opt-unround.test
@@ -0,0 +1,94 @@
+bal --unround --percent
+<<<
+2008/01/11 LIAT
+ Expenses:Travel:Airfare $40.00
+ Liabilities:MasterCard
+
+2008/01/14 cheaptickets.com
+ Expenses:Travel:Airfare $182.19
+ Liabilities:MasterCard
+
+2008/02/05 CTX
+ Expenses:Travel:Auto $240.38
+ Liabilities:MasterCard
+
+2008/02/05 UNITED
+ Expenses:Travel:Airfare $238.80
+ Liabilities:MasterCard
+
+2008/02/05 UNITED
+ Expenses:Travel:Airfare $238.80
+ Liabilities:MasterCard
+
+2008/02/22 BUDGET RENT-A-CAR
+ Expenses:Travel:Auto $40.59
+ Liabilities:MasterCard
+
+2008/03/16 IBERIA
+ Expenses:Travel:Airfare $1,231.60
+ Liabilities:MasterCard
+
+2008/03/16 IBERIA
+ Expenses:Travel:Airfare $1,231.60
+ Liabilities:MasterCard
+
+2008/04/03 AMERICAN
+ Expenses:Travel:Airfare $155.86
+ Liabilities:MasterCard
+
+2008/04/03 AMERICAN
+ Expenses:Travel:Airfare $155.86
+ Liabilities:MasterCard
+
+2008/04/30 UNITED
+ Expenses:Travel:Airfare $437.21
+ Liabilities:MasterCard
+
+2008/04/30 UNITED
+ Expenses:Travel:Airfare $437.21
+ Liabilities:MasterCard
+
+2008/08/08 BCIS I-131 FILING FEE-
+ Expenses:Travel:Passport $170.00
+ Liabilities:MasterCard
+
+2008/09/06 AMERICAN
+ Expenses:Travel:Airfare $912.60
+ Liabilities:MasterCard
+
+2008/09/06 AMERICAN
+ Expenses:Travel:Airfare $912.60
+ Liabilities:MasterCard
+
+2008/09/22 AGNT FEE
+ Expenses:Travel:Airfare $70.00
+ Liabilities:MasterCard
+
+2008/09/22 DELTA
+ Expenses:Travel:Airfare $806.20
+ Liabilities:MasterCard
+
+2008/09/22 DELTA
+ Expenses:Travel:Airfare $806.20
+ Liabilities:MasterCard
+
+2008/09/22 LIAT 1974 LIMITED
+ Expenses:Travel:Airfare $418.34
+ Liabilities:MasterCard
+
+2008/12/26 U.S. Department of State
+ Expenses:Travel:Passport $127.00
+ Assets:Checking
+
+2008/12/26 U.S. Department of State
+ Expenses:Travel:Passport $127.00
+ Assets:Checking
+>>>1
+ 100.00% Assets:Checking
+ 100.00% Expenses:Travel
+ 92.14958953% Airfare
+ 3.12882793% Auto
+ 4.72158253% Passport
+ 100.00% Liabilities:MasterCard
+>>>2
+=== 0
diff --git a/test/input/drewr.dat b/test/input/drewr.dat
index 937daa5c..13b88844 100644
--- a/test/input/drewr.dat
+++ b/test/input/drewr.dat
@@ -57,3 +57,7 @@
2004/01/27 Book Store
Expenses:Books $20.00
Liabilities:MasterCard
+
+2004/02/01 Sale
+ Assets:Checking:Business $ 30.00
+ Income:Sales
diff --git a/test/regress/04C5E1CA.test b/test/regress/04C5E1CA.test
new file mode 100644
index 00000000..729ae6bf
--- /dev/null
+++ b/test/regress/04C5E1CA.test
@@ -0,0 +1,18 @@
+reg
+<<<
+2009/04/04 CS Club Sign
+ Expenses:School:CS Club:Home Depot:4" Brush 2 @ $3.97
+ Liabilities:Mastercard
+
+2009/04/04 CS Club Sign
+ Expenses:School:CS Club:Home Depot:4" Brush (2 * $3.97)
+ Liabilities:Mastercard
+>>>1
+09-Apr-04 CS Club Sign Ex:Sc:CS:Ho:4" Brush 2 2
+ Liabilities:Mastercard $-7.94 2
+ $-7.94
+09-Apr-04 CS Club Sign Ex:Sc:CS:Ho:4" Brush $7.94 2
+ Liabilities:Mastercard $-7.94 2
+ $-7.94
+>>>2
+=== 0
diff --git a/test/regress/5A03CFC3.test b/test/regress/5A03CFC3.test
new file mode 100644
index 00000000..a5a12af3
--- /dev/null
+++ b/test/regress/5A03CFC3.test
@@ -0,0 +1,72 @@
+bal assets
+<<<
+= account =~ /^Income/
+ (Liabilities:Tithe) 0.12
+
+~ Monthly
+ Assets:Checking $500.00
+ Income:Salary
+
+2003/12/01 * Checking balance
+ Assets:Checking $1,000.00
+ Equity:Opening Balances
+
+2003/12/20 Organic Co-op
+ Expenses:Food:Groceries $ 37.50 ; [=2004/01/01]
+ Expenses:Food:Groceries $ 37.50 ; [=2004/02/01]
+ Expenses:Food:Groceries $ 37.50 ; [=2004/03/01]
+ Expenses:Food:Groceries $ 37.50 ; [=2004/04/01]
+ Expenses:Food:Groceries $ 37.50 ; [=2004/05/01]
+ Expenses:Food:Groceries $ 37.50 ; [=2004/06/01]
+ Assets:Checking $ -225.00
+
+2003/12/28=2004/01/01 Acme Mortgage
+ Liabilities:Mortgage:Principal $ 200.00
+ Expenses:Interest:Mortgage $ 500.00
+ Expenses:Escrow $ 300.00
+ Assets:Checking $ -1000.00
+
+2004/01/02 Grocery Store
+ Expenses:Food:Groceries $ 65.00
+ Assets:Checking
+
+2004/01/05 Employer
+ Assets:Checking $ 2000.00
+ Income:Salary
+
+2004/01/14 Bank
+ ; Regular monthly savings transfer
+ Assets:Savings $ 300.00
+ Assets:Checking
+
+2004/01/19 Grocery Store
+ Expenses:Food:Groceries $ 44.00
+ Assets:Checking
+
+2004/01/25 Bank
+ ; Transfer to cover car purchase
+ Assets:Checking $ 5,500.00
+ Assets:Savings
+ ; :nobudget:
+
+2004/01/25 Tom's Used Cars
+ Expenses:Auto $ 5,500.00
+ ; :nobudget:
+ Assets:Checking
+
+2004/01/27 Book Store
+ Expenses:Books $20.00
+ Liabilities:MasterCard
+
+2004/02/01 Sale
+ Assets:Checking:Business $ 30.00
+ Income:Sales
+>>>1
+ $ -3,804.00 Assets
+ $ 1,396.00 Checking
+ $ 30.00 Business
+ $ -5,200.00 Savings
+--------------------
+ $ -3,804.00
+>>>2
+=== 0
diff --git a/test/regress/5FBF2ED8.test b/test/regress/5FBF2ED8.test
new file mode 100644
index 00000000..78df5a6e
--- /dev/null
+++ b/test/regress/5FBF2ED8.test
@@ -0,0 +1,20 @@
+bal -B
+<<<
+2008/01/01 * Checking balance
+ Assets:Bank:Checking £0.00
+ Equity:Opening Balances
+
+2008/02/02 Salary
+ Income:Employer £-334.00
+ Assets:Bank:Checking $512.85 @@ £334.00
+
+2008/03/02 Salary
+ Income:Employer £-248.07
+ Assets:Bank:Checking $404.82 @@ £248.07
+>>>1
+ £582.07 Assets:Bank:Checking
+ £-582.07 Income:Employer
+--------------------
+ 0
+>>>2
+=== 0
diff --git a/test/regress/647D5DB9.test b/test/regress/647D5DB9.test
new file mode 100644
index 00000000..2d6b78e3
--- /dev/null
+++ b/test/regress/647D5DB9.test
@@ -0,0 +1,17 @@
+bal --end 2008/12/31 -JV Equities
+<<<
+2008/01/01 * Purchase Apple shares
+ Equities 1000 AAPL @ $2
+ Cash
+
+2008/06/30 * Sell some Apple shares
+ Equities -500 AAPL @ $2.5
+ Cash
+
+P 2008/10/01 02:18:02 AAPL $3
+P 2009/01/31 02:18:02 AAPL $4
+P 3000/01/01 02:18:02 APPL $100
+>>>1
+2008-12-31 1500
+>>>2
+=== 0
diff --git a/test/regress/6DAB9FE3.test b/test/regress/6DAB9FE3.test
new file mode 100644
index 00000000..50b944ae
--- /dev/null
+++ b/test/regress/6DAB9FE3.test
@@ -0,0 +1,10 @@
+reg -E
+<<<
+2009/01/01 Sample
+ assets 0 FOO @ $8.88
+ equity
+>>>1
+09-Jan-01 Sample assets 0 0
+ equity 0 0
+>>>2
+=== 0
diff --git a/test/regress/727B2DF8.test b/test/regress/727B2DF8.test
new file mode 100644
index 00000000..599090a1
--- /dev/null
+++ b/test/regress/727B2DF8.test
@@ -0,0 +1,81 @@
+reg --color
+<<<
+N $
+
+= account =~ /^Expenses:Books/
+ (Liabilities:Taxes) -0.10
+
+~ Monthly
+ Assets:Bank:Checking $500.00
+ Income:Salary
+
+2004/05/01 * Checking balance
+ Assets:Bank:Checking $1,000.00
+ Equity:Opening Balances
+
+2004/05/03=2004/05/01 * Investment balance
+ Assets:Brokerage 50 AAPL @ $30.00
+ Equity:Opening Balances
+
+2004/05/14 * Páy dày
+ Assets:Bank:Checking 500.00€
+ Income:Salary
+
+2004/05/14 * Another dày in which there is Páying
+ Asséts:Bánk:Chécking:Asséts:Bánk:Chécking $500.00
+ Income:Salary
+
+2004/05/14 * Another dày in which there is Páying
+ Русский язык:Русский язык:Русский язык:Русский язык $1000.00
+ Income:Salary
+
+2004/05/27 Book Store
+ Expenses:Books $20.00
+ Expenses:Cards $40.00
+ Expenses:Docs $30.00
+ Liabilities:MasterCard
+
+2004/05/27 (100) Credit card company
+ ; This is an xact note!
+ ; Sample: Value
+ Liabilities:MasterCard $20.00
+ ; This is a posting note!
+ ; Sample: Another Value
+ ; :MyTag:
+ Assets:Bank:Checking
+ ; :AnotherTag:
+>>>1
+04-May-01 Checking balance Assets:Bank:Checking  $1,000.00 $1,000.00
+ Eq:Opening Balances  $-1,000.00 0
+04-May-03 Investment balance Assets:Brokerage  50 AAPL 50 AAPL
+ Eq:Opening Balances  $-1,500.00 $-1,500.00
+ 50 AAPL
+04-May-14 Páy dày Assets:Bank:Checking  500.00€ $-1,500.00
+ 50 AAPL
+ 500.00€
+ Income:Salary  -500.00€ $-1,500.00
+ 50 AAPL
+04-May-14 Another dày in whic.. ..Bá:Ch:As:Bá:Chécking $500.00 $-1,000.00
+ 50 AAPL
+ Income:Salary  $-500.00 $-1,500.00
+ 50 AAPL
+04-May-14 Another dày in whic.. Ру:Ру:Ру:Русский язык  $1,000.00 $-500.00
+ 50 AAPL
+ Income:Salary  $-1,000.00 $-1,500.00
+ 50 AAPL
+04-May-27 Book Store  Expenses:Books  $20.00 $-1,480.00
+ 50 AAPL
+ Expenses:Cards  $40.00 $-1,440.00
+ 50 AAPL
+ Expenses:Docs  $30.00 $-1,410.00
+ 50 AAPL
+ Liabilities:MasterCard $-90.00 $-1,500.00
+ 50 AAPL
+ (Liabilities:Taxes)  $-2.00 $-1,502.00
+ 50 AAPL
+04-May-27 Credit card company  Liabilities:MasterCard $20.00 $-1,482.00
+ 50 AAPL
+ Assets:Bank:Checking  $-20.00 $-1,502.00
+ 50 AAPL
+>>>2
+=== 0
diff --git a/test/regress/793F6BF0.test b/test/regress/793F6BF0.test
new file mode 100644
index 00000000..a4e1234c
--- /dev/null
+++ b/test/regress/793F6BF0.test
@@ -0,0 +1,52 @@
+entry 2009/03/15 book 10
+<<<
+N $
+
+= account =~ /^Expenses:Books/
+ (Liabilities:Taxes) -0.10
+
+~ Monthly
+ Assets:Bank:Checking $500.00
+ Income:Salary
+
+2004/05/01 * Checking balance
+ Assets:Bank:Checking $1,000.00
+ Equity:Opening Balances
+
+2004/05/03=2004/05/01 * Investment balance
+ Assets:Brokerage 50 AAPL @ $30.00
+ Equity:Opening Balances
+
+2004/05/14 * Páy dày
+ Assets:Bank:Checking 500.00€
+ Income:Salary
+
+2004/05/14 * Another dày in which there is Páying
+ Asséts:Bánk:Chécking:Asséts:Bánk:Chécking $500.00
+ Income:Salary
+
+2004/05/14 * Another dày in which there is Páying
+ Русский язык:Русский язык:Русский язык:Русский язык $1000.00
+ Income:Salary
+
+2004/05/27 Book Store
+ Expenses:Books $20.00
+ Expenses:Cards $40.00
+ Expenses:Docs $30.00
+ Liabilities:MasterCard
+
+2004/05/27 (100) Credit card company
+ ; This is an xact note!
+ ; Sample: Value
+ Liabilities:MasterCard $20.00
+ ; This is a posting note!
+ ; Sample: Another Value
+ ; :MyTag:
+ Assets:Bank:Checking
+ ; :AnotherTag:
+>>>1
+2009/03/15 Book Store
+ Expenses:Books $10.00
+ Liabilities:MasterCard
+>>>2
+=== 0
diff --git a/test/regress/86D2BDC4.test b/test/regress/86D2BDC4.test
new file mode 100644
index 00000000..8cd51e42
--- /dev/null
+++ b/test/regress/86D2BDC4.test
@@ -0,0 +1,12 @@
+reg -B
+<<<
+2009/06/03 Westjet
+ Expenses:Transportation:Air C$429.80 @ 1.572865
+ Expenses:Bank:Fees 2.73
+ Liabilities:Mastercard
+>>>1
+09-Jun-03 Westjet Ex:Transportation:Air 676.017377 676.017377
+ Expenses:Bank:Fees 2.73 678.747377
+ Liabilities:Mastercard -678.747377 0
+>>>2
+=== 0
diff --git a/test/regress/B68FFB0D.test b/test/regress/B68FFB0D.test
new file mode 100644
index 00000000..3a43df4c
--- /dev/null
+++ b/test/regress/B68FFB0D.test
@@ -0,0 +1,15 @@
+print
+<<<
+D $1,000.00
+
+2009/01/01 Sample
+ assets 134.123 FOO @ $8.88
+ assets 100 BAR @ $8.88
+ equity
+>>>1
+2009/01/01 Sample
+ assets 134.123 FOO @ $8.88
+ assets 100 BAR @ $8.88
+ equity
+>>>2
+=== 0
diff --git a/test/regress/BBFA1759.test b/test/regress/BBFA1759.test
new file mode 100644
index 00000000..ca6842dc
--- /dev/null
+++ b/test/regress/BBFA1759.test
@@ -0,0 +1,10 @@
+period june 2008
+<<<
+>>>1
+global details =>
+
+ start: 2008-06-01
+ end: 2008-07-01
+ factor: 1
+>>>2
+=== 0
diff --git a/test/regress/C523E23F.test b/test/regress/C523E23F.test
new file mode 100644
index 00000000..b8b08e00
--- /dev/null
+++ b/test/regress/C523E23F.test
@@ -0,0 +1,21 @@
+bal
+<<<
+D 1.000,00€
+
+2009/03/16 * denn's
+ ; Kauf: Yogi-Tee
+ Aufwand:Einkauf:Lebensmittel 17,94€
+ Aktiva:Bank:Girokonto
+
+2009/03/24 Ansparen
+ Aktiva:Bank:Sparkonto 800,00€
+ Aktiva:Bank:Girokonto
+>>>1
+ -17,94€ Aktiva:Bank
+ -817,94€ Girokonto
+ 800,00€ Sparkonto
+ 17,94€ Aufwand:Einkauf:Lebensmittel
+--------------------
+ 0
+>>>2
+=== 0
diff --git a/test/regress/D943AE0F.test b/test/regress/D943AE0F.test
new file mode 100644
index 00000000..3e568db6
--- /dev/null
+++ b/test/regress/D943AE0F.test
@@ -0,0 +1,13 @@
+reg -V --end=2009/06/16
+<<<
+2008/04/15 * Paid expenses back from cie.
+ Expenses:Cie-Reimbursements 2000 CAD @ 1.10 EUR
+ Assets:Checking
+
+P 2008/04/20 00:00:00 CAD 1.20 EUR
+>>>1
+08-Apr-15 Paid expenses back .. Ex:Cie-Reimbursements 2200.00 EUR 2200.00 EUR
+ Assets:Checking -2200.00 EUR 0
+09-Jun-16 Commodities revalued <Revalued> 200.00 EUR 200.00 EUR
+>>>2
+=== 0
diff --git a/test/regress/F559EC12.test b/test/regress/F559EC12.test
new file mode 100644
index 00000000..7900b60d
--- /dev/null
+++ b/test/regress/F559EC12.test
@@ -0,0 +1,35 @@
+format "%-12(scrub(amount))"
+<<<
+>>>1
+--- Context is first posting of the following transaction ---
+2004/05/27 Book Store
+ Expenses:Books 20 BOOK @ $10
+ Liabilities:MasterCard $-200.00
+
+--- Input format string ---
+%-12(scrub(amount))
+
+--- Format elements ---
+Element: EXPR flags: 0x1 min: 12 max: 0 expr: scrub(amount)
+
+--- Formatted string ---
+"20 BOOK "
+>>>2
+=== 0
+format "%12(scrub(amount))"
+>>>1
+--- Context is first posting of the following transaction ---
+2004/05/27 Book Store
+ Expenses:Books 20 BOOK @ $10
+ Liabilities:MasterCard $-200.00
+
+--- Input format string ---
+%12(scrub(amount))
+
+--- Format elements ---
+Element: EXPR flags: 0x0 min: 12 max: 0 expr: scrub(amount)
+
+--- Formatted string ---
+" 20 BOOK"
+>>>2
+=== 0
diff --git a/test/unit/t_amount.cc b/test/unit/t_amount.cc
index 8aa67137..ba863ca7 100644
--- a/test/unit/t_amount.cc
+++ b/test/unit/t_amount.cc
@@ -866,7 +866,7 @@ void AmountTestCase::testIntegerDivision()
x1 /= amount_t(456L);
assertEqual(string("0.269737"), x1.to_string());
x1 /= 456L;
- assertEqual(string("0.000591528162511542"), x1.to_string());
+ assertEqual(string("0.000591528163"), x1.to_string());
amount_t x4("123456789123456789123456789");
amount_t y4("56");
@@ -889,32 +889,32 @@ void AmountTestCase::testFractionalDivision()
amount_t y1("456.456");
assertThrow(x1 / 0L, amount_error);
- assertEqual(string("0.00812195934"), (amount_t("1.0") / x1).to_string());
- assertEqual(string("0.00812195934"), (amount_t("1.0") / x1).to_string());
+ assertEqual(string("0.0081219593"), (amount_t("1.0") / x1).to_string());
+ assertEqual(string("0.0081219593"), (amount_t("1.0") / x1).to_string());
assertEqual(x1, x1 / amount_t("1.0"));
- assertEqual(string("0.00812195934"), (amount_t("1.0") / x1).to_string());
- assertEqual(string("0.00812195934"), (amount_t("1.0") / x1).to_string());
+ assertEqual(string("0.0081219593"), (amount_t("1.0") / x1).to_string());
+ assertEqual(string("0.0081219593"), (amount_t("1.0") / x1).to_string());
assertEqual(- x1, x1 / amount_t("-1.0"));
- assertEqual(string("-0.00812195934"), (amount_t("-1.0") / x1).to_string());
- assertEqual(string("-0.00812195934"), (amount_t("-1.0") / x1).to_string());
- assertEqual(string("0.269736842105263"), (x1 / y1).to_string());
- assertEqual(string("3.707317073170732"), (y1 / x1).to_string());
- assertEqual(string("0.269736842105263"), (x1 / amount_t("456.456")).to_string());
- assertEqual(string("3.707317073170732"), (amount_t("456.456") / x1).to_string());
- assertEqual(string("3.707317073170732"), (amount_t("456.456") / x1).to_string());
+ assertEqual(string("-0.0081219593"), (amount_t("-1.0") / x1).to_string());
+ assertEqual(string("-0.0081219593"), (amount_t("-1.0") / x1).to_string());
+ assertEqual(string("0.269736842105"), (x1 / y1).to_string());
+ assertEqual(string("3.707317073171"), (y1 / x1).to_string());
+ assertEqual(string("0.269736842105"), (x1 / amount_t("456.456")).to_string());
+ assertEqual(string("3.707317073171"), (amount_t("456.456") / x1).to_string());
+ assertEqual(string("3.707317073171"), (amount_t("456.456") / x1).to_string());
x1 /= amount_t("456.456");
- assertEqual(string("0.269736842105263"), x1.to_string());
+ assertEqual(string("0.269736842105"), x1.to_string());
x1 /= amount_t("456.456");
- assertEqual(string("0.000590937225286255757169884601508201951"), x1.to_string());
+ assertEqual(string("0.000590937225286255757"), x1.to_string());
x1 /= 456L;
- assertEqual(string("0.00000129591496773301701133746621429767819329289006668733529828959526392431755859036"), x1.to_string());
+ assertEqual(string("0.000001295914967733017011337"), x1.to_string());
amount_t x4("1234567891234567.89123456789");
amount_t y4("56.789");
assertEqual(amount_t("1.0"), x4 / x4);
- assertEqual(string("21739560323910.7554497273748437197344556164046"), (x4 / y4).to_string());
+ assertEqual(string("21739560323910.75544972737484371973"), (x4 / y4).to_string());
assertValid(x1);
assertValid(y1);
@@ -946,9 +946,9 @@ void AmountTestCase::testCommodityDivision()
// Internal amounts retain their precision, even when being
// converted to strings
assertEqual(string("$0.99727201"), (x1 / x2).to_fullstring());
- assertEqual(string("$1.00273545321637426901"), (x2 / x1).to_fullstring());
+ assertEqual(string("$1.00273545321637"), (x2 / x1).to_fullstring());
assertEqual(string("$1.00"), (x1 / x2).to_string());
- assertEqual(string("$1.00273545321637426901"), (x2 / x1).to_string());
+ assertEqual(string("$1.00273545321637"), (x2 / x1).to_string());
assertThrow(x1 / x0, amount_error);
assertThrow(x0 / x1, amount_error);
@@ -970,9 +970,9 @@ void AmountTestCase::testCommodityDivision()
amount_t x7(internalAmount("$123456789123456789.123456789123456789"));
assertEqual(amount_t("$1"), x7 / x7);
- assertEqual(string("$0.0019216115121765559608381226612019501046413574469262"),
+ assertEqual(string("$0.0019216115121765559608381226612019501"),
(x6 / x7).to_fullstring());
- assertEqual(string("$520.39654928343335571379527154924040947271699678158689736256"),
+ assertEqual(string("$520.39654928343335571379527154924040947272"),
(x7 / x6).to_fullstring());
assertValid(x1);