summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2008-08-05 13:17:04 -0400
committerJohn Wiegley <johnw@newartisans.com>2008-08-05 18:05:49 -0400
commitf6f4a46cf5b14f9a2170cd6475958efbf320caec (patch)
tree05bc1defcdebc201de3dd10477483d906a842821 /src
parentb7970b29855563e4c67f85af8b31233eda80c22a (diff)
downloadfork-ledger-f6f4a46cf5b14f9a2170cd6475958efbf320caec.tar.gz
fork-ledger-f6f4a46cf5b14f9a2170cd6475958efbf320caec.tar.bz2
fork-ledger-f6f4a46cf5b14f9a2170cd6475958efbf320caec.zip
Moved around most of the files so that source code is in src/, documentation
is in doc/, etc.
Diffstat (limited to 'src')
-rw-r--r--src/account.cc198
-rw-r--r--src/account.h155
-rw-r--r--src/amount.cc1433
-rw-r--r--src/amount.h748
-rw-r--r--src/balance.cc255
-rw-r--r--src/balance.h526
-rw-r--r--src/balpair.h357
-rw-r--r--src/binary.cc188
-rw-r--r--src/binary.h270
-rw-r--r--src/cache.cc871
-rw-r--r--src/cache.h142
-rw-r--r--src/commodity.cc667
-rw-r--r--src/commodity.h420
-rw-r--r--src/compare.cc85
-rw-r--r--src/compare.h77
-rw-r--r--src/csv.cc148
-rw-r--r--src/csv.h63
-rw-r--r--src/derive.cc228
-rw-r--r--src/derive.h45
-rw-r--r--src/emacs.cc110
-rw-r--r--src/emacs.h68
-rw-r--r--src/entry.cc482
-rw-r--r--src/entry.h239
-rw-r--r--src/error.h85
-rw-r--r--src/expr.cc203
-rw-r--r--src/expr.h125
-rw-r--r--src/fdstream.h215
-rw-r--r--src/filters.cc755
-rw-r--r--src/filters.h702
-rw-r--r--src/flags.h117
-rw-r--r--src/format.cc391
-rw-r--r--src/format.h144
-rw-r--r--src/gnucash.cc432
-rw-r--r--src/gnucash.h53
-rw-r--r--src/handler.h72
-rw-r--r--src/help.cc205
-rw-r--r--src/help.h49
-rw-r--r--src/hooks.h71
-rw-r--r--src/iterators.cc200
-rw-r--r--src/iterators.h226
-rw-r--r--src/journal.cc150
-rw-r--r--src/journal.h128
-rw-r--r--src/ledger.h81
-rw-r--r--src/main.cc593
-rw-r--r--src/mask.cc59
-rw-r--r--src/mask.h67
-rw-r--r--src/ofx.cc257
-rw-r--r--src/ofx.h53
-rw-r--r--src/op.cc1130
-rw-r--r--src/op.h330
-rw-r--r--src/option.cc212
-rw-r--r--src/option.h52
-rw-r--r--src/output.cc310
-rw-r--r--src/output.h140
-rw-r--r--src/parser.cc441
-rw-r--r--src/parser.h103
-rw-r--r--src/predicate.h69
-rw-r--r--src/pushvar.h80
-rw-r--r--src/qif.cc276
-rw-r--r--src/qif.h53
-rw-r--r--src/quotes.cc109
-rw-r--r--src/quotes.h70
-rw-r--r--src/reconcile.cc116
-rw-r--r--src/reconcile.h72
-rw-r--r--src/report.cc432
-rw-r--r--src/report.h757
-rw-r--r--src/scope.cc123
-rw-r--r--src/scope.h280
-rw-r--r--src/session.cc391
-rw-r--r--src/session.h255
-rw-r--r--src/system.hh167
-rw-r--r--src/textual.cc1131
-rw-r--r--src/textual.h81
-rw-r--r--src/times.cc350
-rw-r--r--src/times.h154
-rw-r--r--src/token.cc378
-rw-r--r--src/token.h116
-rw-r--r--src/utils.cc716
-rw-r--r--src/utils.h578
-rw-r--r--src/value.cc1773
-rw-r--r--src/value.h922
-rw-r--r--src/xact.cc278
-rw-r--r--src/xact.h228
-rw-r--r--src/xml.cc499
-rw-r--r--src/xml.h85
85 files changed, 26465 insertions, 0 deletions
diff --git a/src/account.cc b/src/account.cc
new file mode 100644
index 00000000..d3b19ad0
--- /dev/null
+++ b/src/account.cc
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 2003-2008, 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 "account.h"
+
+namespace ledger {
+
+account_t::~account_t()
+{
+ TRACE_DTOR(account_t);
+
+ foreach (accounts_map::value_type& pair, accounts)
+ checked_delete(pair.second);
+}
+
+account_t * account_t::find_account(const string& name,
+ const bool auto_create)
+{
+ accounts_map::const_iterator i = accounts.find(name);
+ if (i != accounts.end())
+ return (*i).second;
+
+ char buf[256];
+
+ string::size_type sep = name.find(':');
+ assert(sep < 256|| sep == string::npos);
+
+ const char * first, * rest;
+ if (sep == string::npos) {
+ first = name.c_str();
+ rest = NULL;
+ } else {
+ std::strncpy(buf, name.c_str(), sep);
+ buf[sep] = '\0';
+
+ first = buf;
+ rest = name.c_str() + sep + 1;
+ }
+
+ account_t * account;
+
+ i = accounts.find(first);
+ if (i == accounts.end()) {
+ if (! auto_create)
+ return NULL;
+
+ account = new account_t(this, first);
+ std::pair<accounts_map::iterator, bool> result
+ = accounts.insert(accounts_map::value_type(first, account));
+ assert(result.second);
+ } else {
+ account = (*i).second;
+ }
+
+ if (rest)
+ account = account->find_account(rest, auto_create);
+
+ return account;
+}
+
+string account_t::fullname() const
+{
+ if (! _fullname.empty()) {
+ return _fullname;
+ } else {
+ const account_t * first = this;
+ string fullname = name;
+
+ while (first->parent) {
+ first = first->parent;
+ if (! first->name.empty())
+ fullname = first->name + ":" + fullname;
+ }
+
+ _fullname = fullname;
+
+ return fullname;
+ }
+}
+
+std::ostream& operator<<(std::ostream& out, const account_t& account)
+{
+ out << account.fullname();
+ return out;
+}
+
+namespace {
+ value_t get_total(account_t& account) {
+ assert(account.xdata_);
+ return account.xdata_->total;
+ }
+
+ template <value_t (*Func)(account_t&)>
+ value_t get_wrapper(call_scope_t& scope) {
+ return (*Func)(find_scope<account_t>(scope));
+ }
+}
+
+expr_t::ptr_op_t account_t::lookup(const string& name)
+{
+ switch (name[0]) {
+ case 'f':
+ if (name.find("fmt_") == 0) {
+ switch (name[4]) {
+ case 'T':
+ return WRAP_FUNCTOR(get_wrapper<&get_total>);
+ }
+ }
+ break;
+
+ case 't':
+ if (name == "total")
+ return WRAP_FUNCTOR(get_wrapper<&get_total>);
+ break;
+ }
+ return expr_t::ptr_op_t();
+}
+
+bool account_t::valid() const
+{
+ if (depth > 256) {
+ DEBUG("ledger.validate", "account_t: depth > 256");
+ return false;
+ }
+
+ foreach (const accounts_map::value_type& pair, accounts) {
+ if (this == pair.second) {
+ DEBUG("ledger.validate", "account_t: parent refers to itself!");
+ return false;
+ }
+
+ if (! pair.second->valid()) {
+ DEBUG("ledger.validate", "account_t: child not valid");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void account_t::calculate_sums()
+{
+ xdata_t& xd(xdata());
+
+ foreach (accounts_map::value_type& pair, accounts) {
+ (*pair.second).calculate_sums();
+
+ if (xd.total.is_null())
+ xd.total = (*pair.second).xdata().total;
+ else
+ xd.total += (*pair.second).xdata().total;
+
+ xd.total_count += ((*pair.second).xdata().total_count +
+ (*pair.second).xdata().count);
+ }
+
+ value_t result;
+#if 0
+ compute_amount(result, details_t(account));
+#endif
+
+ if (xd.total.is_null())
+ xd.total = result;
+ else
+ xd.total += result;
+
+ xd.total_count += xd.count;
+}
+
+} // namespace ledger
diff --git a/src/account.h b/src/account.h
new file mode 100644
index 00000000..7d35593c
--- /dev/null
+++ b/src/account.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2003-2008, 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 _ACCOUNT_H
+#define _ACCOUNT_H
+
+#include "utils.h"
+#include "scope.h"
+
+namespace ledger {
+
+class account_t;
+
+typedef std::map<const string, account_t *> accounts_map;
+
+class account_t : public scope_t
+{
+ public:
+ typedef unsigned long ident_t;
+
+ account_t * parent;
+ string name;
+ optional<string> note;
+ unsigned short depth;
+ accounts_map accounts;
+
+ mutable void * data;
+ mutable ident_t ident;
+ mutable string _fullname;
+
+ account_t(account_t * _parent = NULL,
+ const string& _name = "",
+ const optional<string>& _note = none)
+ : scope_t(), parent(_parent), name(_name), note(_note),
+ depth(parent ? parent->depth + 1 : 0), data(NULL), ident(0) {
+ TRACE_CTOR(account_t, "account_t *, const string&, const string&");
+ }
+ account_t(const account_t& other)
+ : scope_t(),
+ parent(other.parent),
+ name(other.name),
+ note(other.note),
+ depth(other.depth),
+ accounts(other.accounts),
+ data(NULL),
+ ident(0) {
+ TRACE_CTOR(account_t, "copy");
+ assert(other.data == NULL);
+ assert(other.ident == 0);
+ }
+ ~account_t();
+
+ operator string() const {
+ return fullname();
+ }
+ string fullname() const;
+
+ void add_account(account_t * acct) {
+ accounts.insert(accounts_map::value_type(acct->name, acct));
+ }
+ bool remove_account(account_t * acct) {
+ accounts_map::size_type n = accounts.erase(acct->name);
+ return n > 0;
+ }
+
+ account_t * find_account(const string& name, bool auto_create = true);
+
+ virtual expr_t::ptr_op_t lookup(const string& name);
+
+ bool valid() const;
+
+ friend class journal_t;
+
+ struct xdata_t : public supports_flags<>
+ {
+#define ACCOUNT_EXT_TO_DISPLAY 0x01
+#define ACCOUNT_EXT_DISPLAYED 0x02
+#define ACCOUNT_EXT_SORT_CALC 0x04
+#define ACCOUNT_EXT_HAS_NON_VIRTUALS 0x08
+#define ACCOUNT_EXT_HAS_UNB_VIRTUALS 0x10
+
+ value_t value;
+ value_t total;
+ value_t sort_value;
+ unsigned int count; // xacts counted toward amount
+ unsigned int total_count; // xacts counted toward total
+ unsigned int virtuals;
+ unsigned short dflags;
+
+ xdata_t()
+ : supports_flags<>(), count(0), total_count(0),
+ virtuals(0), dflags(0)
+ {
+ TRACE_CTOR(xdata_t, "");
+ }
+
+ ~xdata_t() throw() {
+ TRACE_DTOR(xdata_t);
+ }
+ };
+
+ // This variable holds optional "extended data" which is usually produced
+ // only during reporting, and only for the transaction set being reported.
+ // It's a memory-saving measure to delay allocation until the last possible
+ // moment.
+ mutable optional<xdata_t> xdata_;
+
+ bool has_xdata() const {
+ return xdata_;
+ }
+ void clear_xdata() {
+ xdata_ = none;
+ }
+ xdata_t& xdata() {
+ if (! xdata_)
+ xdata_ = xdata_t();
+ return *xdata_;
+ }
+
+ void calculate_sums();
+};
+
+std::ostream& operator<<(std::ostream& out, const account_t& account);
+
+} // namespace ledger
+
+#endif // _ACCOUNT_H
diff --git a/src/amount.cc b/src/amount.cc
new file mode 100644
index 00000000..7f849bff
--- /dev/null
+++ b/src/amount.cc
@@ -0,0 +1,1433 @@
+/*
+ * Copyright (c) 2003-2008, 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 amount.cc
+ * @author John Wiegley
+ * @date Thu Apr 26 15:19:46 2007
+ *
+ * @brief Types for handling commoditized math.
+ *
+ * This file defines member functions for amount_t, and also defines a
+ * helper class, bigint_t, which is used as a refcounted wrapper
+ * around libgmp's mpz_t type.
+ */
+
+#include "amount.h"
+#include "binary.h"
+
+namespace ledger {
+
+commodity_pool_t * amount_t::current_pool = NULL;
+
+bool amount_t::keep_base = false;
+
+bool amount_t::keep_price = false;
+bool amount_t::keep_date = false;
+bool amount_t::keep_tag = false;
+
+bool amount_t::stream_fullstrings = false;
+
+#ifndef THREADSAFE
+/**
+ * These global temporaries are pre-initialized for the sake of
+ * efficiency, and reused over and over again.
+ */
+static mpz_t temp;
+static mpz_t divisor;
+#endif
+
+struct amount_t::bigint_t : public supports_flags<>
+{
+#define BIGINT_BULK_ALLOC 0x01
+#define BIGINT_KEEP_PREC 0x02
+
+ mpz_t val;
+ precision_t prec;
+ uint_least16_t ref;
+ uint_fast32_t index;
+
+#define MPZ(bigint) ((bigint)->val)
+
+ bigint_t() : prec(0), ref(1), index(0) {
+ TRACE_CTOR(bigint_t, "");
+ mpz_init(val);
+ }
+ bigint_t(mpz_t _val) : prec(0), ref(1), index(0) {
+ TRACE_CTOR(bigint_t, "mpz_t");
+ mpz_init_set(val, _val);
+ }
+ bigint_t(const bigint_t& other)
+ : supports_flags<>(other.flags() & ~BIGINT_BULK_ALLOC),
+ prec(other.prec), ref(1), index(0) {
+ TRACE_CTOR(bigint_t, "copy");
+ mpz_init_set(val, other.val);
+ }
+ ~bigint_t() {
+ TRACE_DTOR(bigint_t);
+ assert(ref == 0);
+ mpz_clear(val);
+ }
+
+ bool valid() const {
+ if (prec > 128) {
+ DEBUG("ledger.validate", "amount_t::bigint_t: prec > 128");
+ return false;
+ }
+ if (ref > 16535) {
+ DEBUG("ledger.validate", "amount_t::bigint_t: ref > 16535");
+ return false;
+ }
+#if 0
+ // jww (2008-07-24): How does one check the validity of an mpz_t?
+ if (val[0]._mp_size < 0 || val[0]._mp_size > 100) {
+ DEBUG("ledger.validate", "amount_t::bigint_t: val._mp_size is bad");
+ return false;
+ }
+#endif
+ return true;
+ }
+};
+
+uint_fast32_t amount_t::sizeof_bigint_t()
+{
+ return sizeof(bigint_t);
+}
+
+void amount_t::initialize()
+{
+ mpz_init(temp);
+ mpz_init(divisor);
+
+ // jww (2007-05-02): Be very careful here!
+ if (! current_pool)
+ current_pool = new commodity_pool_t;
+
+ // Add time commodity conversions, so that timelog's may be parsed
+ // in terms of seconds, but reported as minutes or hours.
+ if (commodity_t * commodity = current_pool->create("s")) {
+ commodity->add_flags(COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN);
+
+ parse_conversion("1.0m", "60s");
+ parse_conversion("1.0h", "60m");
+ } else {
+ assert(false);
+ }
+}
+
+void amount_t::shutdown()
+{
+ mpz_clear(temp);
+ mpz_clear(divisor);
+
+ // jww (2007-05-02): Be very careful here!
+ if (current_pool) {
+ checked_delete(current_pool);
+ current_pool = NULL;
+ }
+}
+
+void amount_t::_copy(const amount_t& amt)
+{
+ assert(amt.valid());
+
+ if (quantity != amt.quantity) {
+ if (quantity)
+ _release();
+
+ // Never maintain a pointer into a bulk allocation pool; such
+ // pointers are not guaranteed to remain.
+ if (amt.quantity->has_flags(BIGINT_BULK_ALLOC)) {
+ quantity = new bigint_t(*amt.quantity);
+ } else {
+ quantity = amt.quantity;
+ DEBUG("amounts.refs",
+ quantity << " ref++, now " << (quantity->ref + 1));
+ quantity->ref++;
+ }
+ }
+ commodity_ = amt.commodity_;
+
+ assert(valid());
+}
+
+void amount_t::_dup()
+{
+ assert(valid());
+
+ if (quantity->ref > 1) {
+ bigint_t * q = new bigint_t(*quantity);
+ _release();
+ quantity = q;
+ }
+
+ assert(valid());
+}
+
+void amount_t::_resize(precision_t prec)
+{
+ assert(prec < 256);
+
+ if (! quantity || prec == quantity->prec)
+ return;
+
+ _dup();
+
+ assert(prec > quantity->prec);
+ mpz_ui_pow_ui(divisor, 10, prec - quantity->prec);
+ mpz_mul(MPZ(quantity), MPZ(quantity), divisor);
+
+ quantity->prec = prec;
+
+ assert(valid());
+}
+
+void amount_t::_clear()
+{
+ if (quantity) {
+ _release();
+ quantity = NULL;
+ commodity_ = NULL;
+ } else {
+ assert(! commodity_);
+ }
+}
+
+void amount_t::_release()
+{
+ assert(valid());
+
+ DEBUG("amounts.refs", quantity << " ref--, now " << (quantity->ref - 1));
+
+ if (--quantity->ref == 0) {
+ if (quantity->has_flags(BIGINT_BULK_ALLOC))
+ quantity->~bigint_t();
+ else
+ checked_delete(quantity);
+ quantity = NULL;
+ commodity_ = NULL;
+ }
+
+ assert(valid());
+}
+
+
+#ifdef HAVE_GDTOA
+namespace {
+ amount_t::precision_t convert_double(mpz_t dest, double val)
+ {
+ int decpt, sign;
+ char * buf = dtoa(val, 0, 0, &decpt, &sign, NULL);
+ char * result;
+ int len = std::strlen(buf);
+
+ if (decpt <= len) {
+ decpt = len - decpt;
+ result = NULL;
+ } else {
+ // There were trailing zeros, which we have to put back on in
+ // order to convert this buffer into an integer.
+
+ int zeroes = decpt - len;
+ result = new char[len + zeroes + 1];
+
+ std::strcpy(result, buf);
+ int i;
+ for (i = 0; i < zeroes; i++)
+ result[len + i] = '0';
+ result[len + i] = '\0';
+
+ decpt = (len - decpt) + zeroes;
+ }
+
+ if (sign) {
+ char * newbuf = new char[std::strlen(result ? result : buf) + 2];
+ newbuf[0] = '-';
+ std::strcpy(&newbuf[1], result ? result : buf);
+ mpz_set_str(dest, newbuf, 10);
+ checked_array_delete(newbuf);
+ } else {
+ mpz_set_str(dest, result ? result : buf, 10);
+ }
+
+ if (result)
+ checked_array_delete(result);
+ freedtoa(buf);
+
+ return decpt;
+ }
+}
+
+amount_t::amount_t(const double val) : commodity_(NULL)
+{
+ TRACE_CTOR(amount_t, "const double");
+ quantity = new bigint_t;
+ quantity->prec = convert_double(MPZ(quantity), val);
+}
+#endif
+
+amount_t::amount_t(const unsigned long val) : commodity_(NULL)
+{
+ TRACE_CTOR(amount_t, "const unsigned long");
+ quantity = new bigint_t;
+ mpz_set_ui(MPZ(quantity), val);
+}
+
+amount_t::amount_t(const long val) : commodity_(NULL)
+{
+ TRACE_CTOR(amount_t, "const long");
+ quantity = new bigint_t;
+ mpz_set_si(MPZ(quantity), val);
+}
+
+
+amount_t& amount_t::operator=(const amount_t& amt)
+{
+ if (this != &amt) {
+ if (amt.quantity)
+ _copy(amt);
+ else if (quantity)
+ _clear();
+ }
+ return *this;
+}
+
+
+int amount_t::compare(const amount_t& amt) const
+{
+ assert(amt.valid());
+
+ if (! quantity || ! amt.quantity) {
+ if (quantity)
+ throw_(amount_error, "Cannot compare an amount to an uninitialized amount");
+ else if (amt.quantity)
+ throw_(amount_error, "Cannot compare an uninitialized amount to an amount");
+ else
+ throw_(amount_error, "Cannot compare two uninitialized amounts");
+ }
+
+ if (has_commodity() && amt.has_commodity() &&
+ commodity() != amt.commodity())
+ throw_(amount_error,
+ "Cannot compare amounts with different commodities: " <<
+ commodity().symbol() << " and " << amt.commodity().symbol());
+
+ if (quantity->prec == amt.quantity->prec) {
+ return mpz_cmp(MPZ(quantity), MPZ(amt.quantity));
+ }
+ else if (quantity->prec < amt.quantity->prec) {
+ amount_t t(*this);
+ t._resize(amt.quantity->prec);
+ return mpz_cmp(MPZ(t.quantity), MPZ(amt.quantity));
+ }
+ else {
+ amount_t t = amt;
+ t._resize(quantity->prec);
+ return mpz_cmp(MPZ(quantity), MPZ(t.quantity));
+ }
+}
+
+
+amount_t& amount_t::operator+=(const amount_t& amt)
+{
+ assert(amt.valid());
+
+ if (! quantity || ! amt.quantity) {
+ if (quantity)
+ throw_(amount_error, "Cannot add an amount to an uninitialized amount");
+ else if (amt.quantity)
+ throw_(amount_error, "Cannot add an uninitialized amount to an amount");
+ else
+ throw_(amount_error, "Cannot add two uninitialized amounts");
+ }
+
+ if (commodity() != amt.commodity())
+ throw_(amount_error,
+ "Adding amounts with different commodities: " <<
+ (has_commodity() ? commodity().symbol() : "NONE") <<
+ " != " <<
+ (amt.has_commodity() ? amt.commodity().symbol() : "NONE"));
+
+ _dup();
+
+ if (quantity->prec == amt.quantity->prec) {
+ mpz_add(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
+ }
+ else if (quantity->prec < amt.quantity->prec) {
+ _resize(amt.quantity->prec);
+ mpz_add(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
+ }
+ else {
+ amount_t t = amt;
+ t._resize(quantity->prec);
+ mpz_add(MPZ(quantity), MPZ(quantity), MPZ(t.quantity));
+ }
+
+ return *this;
+}
+
+amount_t& amount_t::operator-=(const amount_t& amt)
+{
+ assert(amt.valid());
+
+ if (! quantity || ! amt.quantity) {
+ if (quantity)
+ throw_(amount_error, "Cannot subtract an amount from an uninitialized amount");
+ else if (amt.quantity)
+ throw_(amount_error, "Cannot subtract an uninitialized amount from an amount");
+ else
+ throw_(amount_error, "Cannot subtract two uninitialized amounts");
+ }
+
+ if (commodity() != amt.commodity())
+ throw_(amount_error,
+ "Subtracting amounts with different commodities: " <<
+ (has_commodity() ? commodity().symbol() : "NONE") <<
+ " != " <<
+ (amt.has_commodity() ? amt.commodity().symbol() : "NONE"));
+
+ _dup();
+
+ if (quantity->prec == amt.quantity->prec) {
+ mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
+ }
+ else if (quantity->prec < amt.quantity->prec) {
+ _resize(amt.quantity->prec);
+ mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
+ }
+ else {
+ amount_t t = amt;
+ t._resize(quantity->prec);
+ mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(t.quantity));
+ }
+
+ return *this;
+}
+
+namespace {
+ void mpz_round(mpz_t out, mpz_t value, int value_prec, int round_prec)
+ {
+ // Round `value', with an encoding precision of `value_prec', to a
+ // rounded value with precision `round_prec'. Result is stored in
+ // `out'.
+
+ assert(value_prec > round_prec);
+
+ mpz_t quotient;
+ mpz_t remainder;
+
+ mpz_init(quotient);
+ mpz_init(remainder);
+
+ mpz_ui_pow_ui(divisor, 10, value_prec - round_prec);
+ mpz_tdiv_qr(quotient, remainder, value, divisor);
+ mpz_divexact_ui(divisor, divisor, 10);
+ mpz_mul_ui(divisor, divisor, 5);
+
+ if (mpz_sgn(remainder) < 0) {
+ mpz_neg(divisor, divisor);
+ if (mpz_cmp(remainder, divisor) < 0) {
+ mpz_ui_pow_ui(divisor, 10, value_prec - round_prec);
+ mpz_add(remainder, divisor, remainder);
+ mpz_ui_sub(remainder, 0, remainder);
+ mpz_add(out, value, remainder);
+ } else {
+ mpz_sub(out, value, remainder);
+ }
+ } else {
+ if (mpz_cmp(remainder, divisor) >= 0) {
+ mpz_ui_pow_ui(divisor, 10, value_prec - round_prec);
+ mpz_sub(remainder, divisor, remainder);
+ mpz_add(out, value, remainder);
+ } else {
+ mpz_sub(out, value, remainder);
+ }
+ }
+ mpz_clear(quotient);
+ mpz_clear(remainder);
+
+ // chop off the rounded bits
+ mpz_ui_pow_ui(divisor, 10, value_prec - round_prec);
+ mpz_tdiv_q(out, out, divisor);
+ }
+}
+
+amount_t& amount_t::operator*=(const amount_t& amt)
+{
+ assert(amt.valid());
+
+ if (! quantity || ! amt.quantity) {
+ if (quantity)
+ throw_(amount_error, "Cannot multiply an amount by an uninitialized amount");
+ else if (amt.quantity)
+ throw_(amount_error, "Cannot multiply an uninitialized amount by an amount");
+ else
+ throw_(amount_error, "Cannot multiply two uninitialized amounts");
+ }
+
+#if 0
+ if (has_commodity() && amt.has_commodity() &&
+ commodity() != amt.commodity())
+ throw_(amount_error,
+ "Multiplying amounts with different commodities: " <<
+ (has_commodity() ? commodity().symbol() : "NONE") <<
+ " != " <<
+ (amt.has_commodity() ? amt.commodity().symbol() : "NONE"));
+#endif
+
+ _dup();
+
+ mpz_mul(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
+ quantity->prec += amt.quantity->prec;
+
+ if (! has_commodity())
+ commodity_ = amt.commodity_;
+
+ if (has_commodity() && ! (quantity->has_flags(BIGINT_KEEP_PREC))) {
+ precision_t comm_prec = commodity().precision();
+ if (quantity->prec > comm_prec + 6U) {
+ mpz_round(MPZ(quantity), MPZ(quantity), quantity->prec, comm_prec + 6U);
+ quantity->prec = comm_prec + 6U;
+ }
+ }
+
+ return *this;
+}
+
+amount_t& amount_t::operator/=(const amount_t& amt)
+{
+ assert(amt.valid());
+
+ if (! quantity || ! amt.quantity) {
+ if (quantity)
+ throw_(amount_error, "Cannot divide an amount by an uninitialized amount");
+ else if (amt.quantity)
+ throw_(amount_error, "Cannot divide an uninitialized amount by an amount");
+ else
+ throw_(amount_error, "Cannot divide two uninitialized amounts");
+ }
+
+#if 0
+ if (has_commodity() && amt.has_commodity() &&
+ commodity() != amt.commodity())
+ throw_(amount_error,
+ "Dividing amounts with different commodities: " <<
+ (has_commodity() ? commodity().symbol() : "NONE") <<
+ " != " <<
+ (amt.has_commodity() ? amt.commodity().symbol() : "NONE"));
+#endif
+
+ if (! amt)
+ throw_(amount_error, "Divide by zero");
+
+ _dup();
+
+ // Increase the value's precision, to capture fractional parts after
+ // the divide. Round up in the last position.
+
+ mpz_ui_pow_ui(divisor, 10, (2 * amt.quantity->prec) + quantity->prec + 7U);
+ mpz_mul(MPZ(quantity), MPZ(quantity), divisor);
+ mpz_tdiv_q(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
+ quantity->prec += amt.quantity->prec + quantity->prec + 7U;
+
+ mpz_round(MPZ(quantity), MPZ(quantity), quantity->prec, quantity->prec - 1);
+ quantity->prec -= 1;
+
+ if (! has_commodity())
+ commodity_ = amt.commodity_;
+
+ // If this amount has a commodity, and we're not dealing with plain
+ // numbers, or internal numbers (which keep full precision at all
+ // times), then round the number to within the commodity's precision
+ // plus six places.
+
+ if (has_commodity() && ! (quantity->has_flags(BIGINT_KEEP_PREC))) {
+ precision_t comm_prec = commodity().precision();
+ if (quantity->prec > comm_prec + 6U) {
+ mpz_round(MPZ(quantity), MPZ(quantity), quantity->prec, comm_prec + 6U);
+ quantity->prec = comm_prec + 6U;
+ }
+ }
+
+ return *this;
+}
+
+
+amount_t::precision_t amount_t::precision() const
+{
+ if (! quantity)
+ throw_(amount_error, "Cannot determine precision of an uninitialized amount");
+
+ return quantity->prec;
+}
+
+amount_t& amount_t::in_place_negate()
+{
+ if (quantity) {
+ _dup();
+ mpz_neg(MPZ(quantity), MPZ(quantity));
+ } else {
+ throw_(amount_error, "Cannot negate an uninitialized amount");
+ }
+ return *this;
+}
+
+amount_t amount_t::round() const
+{
+ if (! quantity)
+ throw_(amount_error, "Cannot round an uninitialized amount");
+
+ if (! has_commodity())
+ return *this;
+
+ return round(commodity().precision());
+}
+
+amount_t amount_t::round(precision_t prec) const
+{
+ if (! quantity)
+ throw_(amount_error, "Cannot round an uninitialized amount");
+
+ amount_t t(*this);
+
+ if (quantity->prec <= prec) {
+ if (quantity && quantity->has_flags(BIGINT_KEEP_PREC)) {
+ t._dup();
+ t.quantity->drop_flags(BIGINT_KEEP_PREC);
+ }
+ return t;
+ }
+
+ t._dup();
+
+ mpz_round(MPZ(t.quantity), MPZ(t.quantity), t.quantity->prec, prec);
+
+ t.quantity->prec = prec;
+ t.quantity->drop_flags(BIGINT_KEEP_PREC);
+
+ return t;
+}
+
+amount_t amount_t::unround() const
+{
+ if (! quantity)
+ throw_(amount_error, "Cannot unround an uninitialized amount");
+ else if (quantity->has_flags(BIGINT_KEEP_PREC))
+ return *this;
+
+ amount_t t(*this);
+ t._dup();
+ t.quantity->add_flags(BIGINT_KEEP_PREC);
+
+ return t;
+}
+
+amount_t& amount_t::in_place_reduce()
+{
+ if (! quantity)
+ throw_(amount_error, "Cannot reduce an uninitialized amount");
+
+ while (commodity_ && commodity().smaller()) {
+ *this *= commodity().smaller()->number();
+ commodity_ = commodity().smaller()->commodity_;
+ }
+ return *this;
+}
+
+amount_t& amount_t::in_place_unreduce()
+{
+ if (! quantity)
+ throw_(amount_error, "Cannot unreduce an uninitialized amount");
+
+ while (commodity_ && commodity().larger()) {
+ *this /= commodity().larger()->number();
+ commodity_ = commodity().larger()->commodity_;
+ if (abs() < amount_t(1L))
+ break;
+ }
+ return *this;
+}
+
+optional<amount_t> amount_t::value(const optional<datetime_t>& moment) const
+{
+ if (quantity) {
+ optional<amount_t> amt(commodity().value(moment));
+ if (amt)
+ return (*amt * number()).round();
+ } else {
+ throw_(amount_error, "Cannot determine value of an uninitialized amount");
+ }
+ return none;
+}
+
+
+int amount_t::sign() const
+{
+ if (! quantity)
+ throw_(amount_error, "Cannot determine sign of an uninitialized amount");
+
+ return mpz_sgn(MPZ(quantity));
+}
+
+bool amount_t::is_zero() const
+{
+ if (! quantity)
+ throw_(amount_error, "Cannot determine if an uninitialized amount is zero");
+
+ if (has_commodity()) {
+ if (quantity->prec <= commodity().precision() ||
+ quantity->has_flags(BIGINT_KEEP_PREC))
+ return is_realzero();
+ else
+ return round(commodity().precision()).sign() == 0;
+ }
+ return is_realzero();
+}
+
+
+#ifdef HAVE_GDTOA
+double amount_t::to_double(bool no_check) const
+{
+ if (! quantity)
+ throw_(amount_error, "Cannot convert an uninitialized amount to a double");
+
+ mpz_t remainder;
+ mpz_init(remainder);
+
+ mpz_set(temp, MPZ(quantity));
+ mpz_ui_pow_ui(divisor, 10, quantity->prec);
+ mpz_tdiv_qr(temp, remainder, temp, divisor);
+
+ char * quotient_s = mpz_get_str(NULL, 10, temp);
+ char * remainder_s = mpz_get_str(NULL, 10, remainder);
+
+ std::ostringstream num;
+ num << quotient_s << '.' << remainder_s;
+
+ std::free(quotient_s);
+ std::free(remainder_s);
+
+ mpz_clear(remainder);
+
+ double value = lexical_cast<double>(num.str());
+
+ if (! no_check && *this != value)
+ throw_(amount_error, "Conversion of amount to_double loses precision");
+
+ return value;
+}
+#endif
+
+long amount_t::to_long(bool no_check) const
+{
+ if (! quantity)
+ throw_(amount_error, "Cannot convert an uninitialized amount to a long");
+
+ mpz_set(temp, MPZ(quantity));
+ mpz_ui_pow_ui(divisor, 10, quantity->prec);
+ mpz_tdiv_q(temp, temp, divisor);
+
+ long value = mpz_get_si(temp);
+
+ if (! no_check && *this != value)
+ throw_(amount_error, "Conversion of amount to_long loses precision");
+
+ return value;
+}
+
+#ifdef HAVE_GDTOA
+bool amount_t::fits_in_double() const
+{
+ double value = to_double(true);
+ return *this == amount_t(value);
+}
+#endif
+
+bool amount_t::fits_in_long() const
+{
+ long value = to_long(true);
+ return *this == amount_t(value);
+}
+
+
+void amount_t::annotate(const annotation_t& details)
+{
+ commodity_t * this_base;
+ annotated_commodity_t * this_ann = NULL;
+
+ 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");
+
+ if (commodity().annotated) {
+ this_ann = &as_annotated_commodity(commodity());
+ this_base = &this_ann->referent();
+ } else {
+ this_base = &commodity();
+ }
+ assert(this_base);
+
+ DEBUG("amounts.commodities", "Annotating commodity for amount "
+ << *this << std::endl << details);
+
+ if (commodity_t * ann_comm =
+ this_base->parent().find_or_create(*this_base, details))
+ set_commodity(*ann_comm);
+#ifdef ASSERTS_ON
+ else
+ assert(false);
+#endif
+
+ DEBUG("amounts.commodities", " Annotated amount is " << *this);
+}
+
+bool amount_t::annotated() const
+{
+ if (! quantity)
+ throw_(amount_error,
+ "Cannot determine if an uninitialized amount's commodity is annotated");
+
+ assert(! commodity().annotated || as_annotated_commodity(commodity()).details);
+ return commodity().annotated;
+}
+
+annotation_t& amount_t::annotation()
+{
+ if (! quantity)
+ throw_(amount_error,
+ "Cannot return commodity annotation details of an uninitialized amount");
+
+ if (! commodity().is_annotated())
+ throw_(amount_error,
+ "Request for annotation details from an unannotated amount");
+
+ annotated_commodity_t& ann_comm(as_annotated_commodity(commodity()));
+ return ann_comm.details;
+}
+
+amount_t amount_t::strip_annotations(const bool _keep_price,
+ const bool _keep_date,
+ const bool _keep_tag) const
+{
+ if (! quantity)
+ throw_(amount_error,
+ "Cannot strip commodity annotations from an uninitialized amount");
+
+ if (! commodity().annotated ||
+ (_keep_price && _keep_date && _keep_tag))
+ return *this;
+
+ amount_t t(*this);
+ t.set_commodity(as_annotated_commodity(commodity()).
+ strip_annotations(_keep_price, _keep_date, _keep_tag));
+ return t;
+}
+
+
+namespace {
+ void parse_quantity(std::istream& in, string& value)
+ {
+ char buf[256];
+ char c = peek_next_nonws(in);
+ READ_INTO(in, buf, 255, c,
+ std::isdigit(c) || c == '-' || c == '.' || c == ',');
+
+ int len = std::strlen(buf);
+ while (len > 0 && ! std::isdigit(buf[len - 1])) {
+ buf[--len] = '\0';
+ in.unget();
+ }
+
+ value = buf;
+ }
+}
+
+bool amount_t::parse(std::istream& in, flags_t flags)
+{
+ // The possible syntax for an amount is:
+ //
+ // [-]NUM[ ]SYM [@ AMOUNT]
+ // SYM[ ][-]NUM [@ AMOUNT]
+
+ string symbol;
+ string quant;
+ annotation_t details;
+ bool negative = false;
+
+ commodity_t::flags_t comm_flags = COMMODITY_STYLE_DEFAULTS;
+
+ char c = peek_next_nonws(in);
+ if (c == '-') {
+ negative = true;
+ in.get(c);
+ c = peek_next_nonws(in);
+ }
+
+ char n;
+ if (std::isdigit(c)) {
+ parse_quantity(in, quant);
+
+ if (! in.eof() && ((n = in.peek()) != '\n')) {
+ if (std::isspace(n))
+ comm_flags |= COMMODITY_STYLE_SEPARATED;
+
+ commodity_t::parse_symbol(in, symbol);
+
+ if (! symbol.empty())
+ comm_flags |= COMMODITY_STYLE_SUFFIXED;
+
+ if (! in.eof() && ((n = in.peek()) != '\n'))
+ details.parse(in);
+ }
+ } else {
+ commodity_t::parse_symbol(in, symbol);
+
+ if (! in.eof() && ((n = in.peek()) != '\n')) {
+ if (std::isspace(in.peek()))
+ comm_flags |= COMMODITY_STYLE_SEPARATED;
+
+ parse_quantity(in, quant);
+
+ if (! quant.empty() && ! in.eof() && ((n = in.peek()) != '\n'))
+ details.parse(in);
+ }
+ }
+
+ if (quant.empty()) {
+ if (flags & AMOUNT_PARSE_SOFT_FAIL)
+ return false;
+ else
+ throw_(amount_error, "No quantity specified for amount");
+ }
+
+ // Allocate memory for the amount's quantity value. We have to
+ // monitor the allocation in an auto_ptr because this function gets
+ // called sometimes from amount_t's constructor; and if there is an
+ // exeception thrown by any of the function calls after this point,
+ // the destructor will never be called and the memory never freed.
+
+ std::auto_ptr<bigint_t> safe_holder;
+
+ if (! quantity) {
+ quantity = new bigint_t;
+ safe_holder.reset(quantity);
+ }
+ else if (quantity->ref > 1) {
+ _release();
+ quantity = new bigint_t;
+ safe_holder.reset(quantity);
+ }
+
+ // Create the commodity if has not already been seen, and update the
+ // precision if something greater was used for the quantity.
+
+ bool newly_created = false;
+
+ if (symbol.empty()) {
+ commodity_ = NULL;
+ } else {
+ commodity_ = current_pool->find(symbol);
+ if (! commodity_) {
+ commodity_ = current_pool->create(symbol);
+ newly_created = true;
+ }
+ assert(commodity_);
+
+ if (details)
+ commodity_ = current_pool->find_or_create(*commodity_, details);
+ }
+
+ // Determine the precision of the amount, based on the usage of
+ // comma or period.
+
+ string::size_type last_comma = quant.rfind(',');
+ string::size_type last_period = quant.rfind('.');
+
+ if (last_comma != string::npos && last_period != string::npos) {
+ comm_flags |= COMMODITY_STYLE_THOUSANDS;
+ if (last_comma > last_period) {
+ comm_flags |= COMMODITY_STYLE_EUROPEAN;
+ quantity->prec = quant.length() - last_comma - 1;
+ } else {
+ quantity->prec = quant.length() - last_period - 1;
+ }
+ }
+ else if (last_comma != string::npos &&
+ commodity().has_flags(COMMODITY_STYLE_EUROPEAN)) {
+ quantity->prec = quant.length() - last_comma - 1;
+ }
+ else if (last_period != string::npos &&
+ ! (commodity().has_flags(COMMODITY_STYLE_EUROPEAN))) {
+ quantity->prec = quant.length() - last_period - 1;
+ }
+ else {
+ quantity->prec = 0;
+ }
+
+ // Set the commodity's flags and precision accordingly
+
+ if (commodity_ && ! (flags & AMOUNT_PARSE_NO_MIGRATE)) {
+ commodity().add_flags(comm_flags);
+
+ if (quantity->prec > commodity().precision())
+ commodity().set_precision(quantity->prec);
+ }
+
+ // Setup the amount's own flags
+
+ if (flags & AMOUNT_PARSE_NO_MIGRATE)
+ quantity->add_flags(BIGINT_KEEP_PREC);
+
+ // Now we have the final number. Remove commas and periods, if
+ // necessary.
+
+ if (last_comma != string::npos || last_period != string::npos) {
+ int len = quant.length();
+ char * buf = new char[len + 1];
+ const char * p = quant.c_str();
+ char * t = buf;
+
+ while (*p) {
+ if (*p == ',' || *p == '.')
+ p++;
+ *t++ = *p++;
+ }
+ *t = '\0';
+
+ mpz_set_str(MPZ(quantity), buf, 10);
+ checked_array_delete(buf);
+ } else {
+ mpz_set_str(MPZ(quantity), quant.c_str(), 10);
+ }
+
+ if (negative)
+ in_place_negate();
+
+ if (! (flags & AMOUNT_PARSE_NO_REDUCE))
+ in_place_reduce();
+
+ safe_holder.release(); // `this->quantity' owns the pointer
+
+ assert(valid());
+
+ return true;
+}
+
+void amount_t::parse_conversion(const string& larger_str,
+ const string& smaller_str)
+{
+ amount_t larger, smaller;
+
+ larger.parse(larger_str, AMOUNT_PARSE_NO_REDUCE);
+ smaller.parse(smaller_str, AMOUNT_PARSE_NO_REDUCE);
+
+ larger *= smaller.number();
+
+ if (larger.commodity()) {
+ larger.commodity().set_smaller(smaller);
+ larger.commodity().add_flags(smaller.commodity().flags() |
+ COMMODITY_STYLE_NOMARKET);
+ }
+ if (smaller.commodity())
+ smaller.commodity().set_larger(larger);
+}
+
+
+void amount_t::print(std::ostream& _out, bool omit_commodity,
+ bool full_precision) const
+{
+ assert(valid());
+
+ if (! quantity) {
+ _out << "<null>";
+ return;
+ }
+
+ amount_t base(*this);
+ if (! amount_t::keep_base)
+ base.in_place_unreduce();
+
+ std::ostringstream out;
+
+ mpz_t quotient;
+ mpz_t rquotient;
+ mpz_t remainder;
+
+ mpz_init(quotient);
+ mpz_init(rquotient);
+ mpz_init(remainder);
+
+ bool negative = false;
+
+ // Ensure the value is rounded to the commodity's precision before
+ // outputting it. NOTE: `rquotient' is used here as a temp variable!
+
+ commodity_t& comm(base.commodity());
+ precision_t precision = 0;
+
+ if (quantity) {
+ if (! comm || full_precision || base.quantity->has_flags(BIGINT_KEEP_PREC)) {
+ mpz_ui_pow_ui(divisor, 10, base.quantity->prec);
+ mpz_tdiv_qr(quotient, remainder, MPZ(base.quantity), divisor);
+ precision = base.quantity->prec;
+ }
+ else if (comm.precision() < base.quantity->prec) {
+ mpz_round(rquotient, MPZ(base.quantity), base.quantity->prec,
+ comm.precision());
+ mpz_ui_pow_ui(divisor, 10, comm.precision());
+ mpz_tdiv_qr(quotient, remainder, rquotient, divisor);
+ precision = comm.precision();
+ }
+ else if (comm.precision() > base.quantity->prec) {
+ mpz_ui_pow_ui(divisor, 10, comm.precision() - base.quantity->prec);
+ mpz_mul(rquotient, MPZ(base.quantity), divisor);
+ mpz_ui_pow_ui(divisor, 10, comm.precision());
+ mpz_tdiv_qr(quotient, remainder, rquotient, divisor);
+ precision = comm.precision();
+ }
+ else if (base.quantity->prec) {
+ mpz_ui_pow_ui(divisor, 10, base.quantity->prec);
+ mpz_tdiv_qr(quotient, remainder, MPZ(base.quantity), divisor);
+ precision = base.quantity->prec;
+ }
+ else {
+ mpz_set(quotient, MPZ(base.quantity));
+ mpz_set_ui(remainder, 0);
+ precision = 0;
+ }
+
+ if (mpz_sgn(quotient) < 0 || mpz_sgn(remainder) < 0) {
+ negative = true;
+
+ mpz_abs(quotient, quotient);
+ mpz_abs(remainder, remainder);
+ }
+ mpz_set(rquotient, remainder);
+ }
+
+ if (! omit_commodity && ! comm.has_flags(COMMODITY_STYLE_SUFFIXED)) {
+ comm.print(out);
+ if (comm.has_flags(COMMODITY_STYLE_SEPARATED))
+ out << " ";
+ }
+
+ if (negative)
+ out << "-";
+
+ if (! quantity || mpz_sgn(quotient) == 0) {
+ out << '0';
+ }
+ else if (omit_commodity || ! comm.has_flags(COMMODITY_STYLE_THOUSANDS)) {
+ char * p = mpz_get_str(NULL, 10, quotient);
+ out << p;
+ std::free(p);
+ }
+ else {
+ std::list<string> strs;
+ char buf[4];
+
+ for (int powers = 0; true; powers += 3) {
+ if (powers > 0) {
+ mpz_ui_pow_ui(divisor, 10, powers);
+ mpz_tdiv_q(temp, quotient, divisor);
+ if (mpz_sgn(temp) == 0)
+ break;
+ mpz_tdiv_r_ui(temp, temp, 1000);
+ } else {
+ mpz_tdiv_r_ui(temp, quotient, 1000);
+ }
+ mpz_get_str(buf, 10, temp);
+ strs.push_back(buf);
+ }
+
+ bool printed = false;
+
+ for (std::list<string>::reverse_iterator i = strs.rbegin();
+ i != strs.rend();
+ i++) {
+ if (printed) {
+ out << (comm.has_flags(COMMODITY_STYLE_EUROPEAN) ? '.' : ',');
+ out.width(3);
+ out.fill('0');
+ }
+ out << *i;
+
+ printed = true;
+ }
+ }
+
+ if (quantity && precision) {
+ std::ostringstream final;
+ final.width(precision);
+ final.fill('0');
+ char * p = mpz_get_str(NULL, 10, rquotient);
+ final << p;
+ std::free(p);
+
+ const string& str(final.str());
+ int i, len = str.length();
+ const char * q = str.c_str();
+ for (i = len; i > 0; i--)
+ if (q[i - 1] != '0')
+ break;
+
+ string ender;
+ if (i == len)
+ ender = str;
+ else if (i < comm.precision())
+ ender = string(str, 0, comm.precision());
+ else
+ ender = string(str, 0, i);
+
+ if (! ender.empty()) {
+ if (omit_commodity)
+ out << '.';
+ else
+ out << (comm.has_flags(COMMODITY_STYLE_EUROPEAN) ? ',' : '.');
+ out << ender;
+ }
+ }
+
+ if (! omit_commodity && comm.has_flags(COMMODITY_STYLE_SUFFIXED)) {
+ if (comm.has_flags(COMMODITY_STYLE_SEPARATED))
+ out << " ";
+ comm.print(out);
+ }
+
+ mpz_clear(quotient);
+ mpz_clear(rquotient);
+ mpz_clear(remainder);
+
+ // If there are any annotations associated with this commodity,
+ // output them now.
+
+ if (! omit_commodity && comm.annotated) {
+ annotated_commodity_t& ann(static_cast<annotated_commodity_t&>(comm));
+ assert(&*ann.details.price != this);
+ ann.write_annotations(out);
+ }
+
+ // Things are output to a string first, so that if anyone has
+ // specified a width or fill for _out, it will be applied to the
+ // entire amount string, and not just the first part.
+
+ _out << out.str();
+}
+
+void amount_t::read(std::istream& in)
+{
+ using namespace ledger::binary;
+
+ // Read in the commodity for this amount
+
+ commodity_t::ident_t ident;
+ read_long(in, ident);
+ if (ident == 0xffffffff)
+ commodity_ = NULL;
+ else if (ident == 0)
+ commodity_ = current_pool->null_commodity;
+ else {
+ commodity_ = current_pool->find(ident);
+ assert(commodity_);
+ }
+
+ // Read in the quantity
+
+ char byte;
+ in.read(&byte, sizeof(byte));
+
+ if (byte < 3) {
+ quantity = new bigint_t;
+
+ unsigned short len;
+ in.read(reinterpret_cast<char *>(&len), sizeof(len));
+ assert(len < 4096);
+ static char buf[4096];
+ in.read(buf, len);
+ mpz_import(MPZ(quantity), len / sizeof(short), 1, sizeof(short),
+ 0, 0, buf);
+
+ char negative;
+ in.read(&negative, sizeof(negative));
+ if (negative)
+ mpz_neg(MPZ(quantity), MPZ(quantity));
+
+ in.read(reinterpret_cast<char *>(&quantity->prec), sizeof(quantity->prec));
+
+ bigint_t::flags_t tflags;
+ in.read(reinterpret_cast<char *>(&tflags), sizeof(tflags));
+ quantity->set_flags(tflags);
+ }
+ else {
+ assert(false);
+ }
+}
+
+void amount_t::read(const char *& data)
+{
+ using namespace ledger::binary;
+
+ // Read in the commodity for this amount
+
+ commodity_t::ident_t ident;
+ read_long(data, ident);
+ if (ident == 0xffffffff)
+ commodity_ = NULL;
+ else if (ident == 0)
+ commodity_ = current_pool->null_commodity;
+ else {
+ commodity_ = current_pool->find(ident);
+ assert(commodity_);
+ }
+
+ // Read in the quantity
+
+ char byte = *data++;;
+
+ if (byte < 3) {
+ if (byte == 2) {
+#if 0
+ quantity = new(reinterpret_cast<bigint_t *>(bigints_next)) bigint_t;
+ bigints_next += sizeof(bigint_t);
+#endif
+ } else {
+ quantity = new bigint_t;
+ }
+
+ unsigned short len =
+ *reinterpret_cast<unsigned short *>(const_cast<char *>(data));
+ data += sizeof(unsigned short);
+ mpz_import(MPZ(quantity), len / sizeof(short), 1, sizeof(short),
+ 0, 0, data);
+ data += len;
+
+ char negative = *data++;
+ if (negative)
+ mpz_neg(MPZ(quantity), MPZ(quantity));
+
+ quantity->prec = *reinterpret_cast<precision_t *>(const_cast<char *>(data));
+ data += sizeof(precision_t);
+ quantity->set_flags(*reinterpret_cast<flags_t *>(const_cast<char *>(data)));
+ data += sizeof(flags_t);
+
+ if (byte == 2)
+ quantity->add_flags(BIGINT_BULK_ALLOC);
+ } else {
+#if 0
+ uint_fast32_t index = *reinterpret_cast<uint_fast32_t *>(const_cast<char *>(data));
+ data += sizeof(uint_fast32_t);
+
+ quantity = reinterpret_cast<bigint_t *>(bigints + (index - 1) * sizeof(bigint_t));
+#endif
+ DEBUG("amounts.refs",
+ quantity << " ref++, now " << (quantity->ref + 1));
+ quantity->ref++;
+ }
+}
+
+void amount_t::write(std::ostream& out, bool optimized) const
+{
+ using namespace ledger::binary;
+
+ // Write out the commodity for this amount
+
+ if (! quantity)
+ throw_(amount_error, "Cannot serialize an uninitialized amount");
+
+ if (commodity_)
+ write_long(out, commodity_->ident);
+ else
+ write_long<commodity_t::ident_t>(out, 0xffffffff);
+
+ // Write out the quantity
+
+ char byte;
+
+ if (! optimized || quantity->index == 0) {
+ if (optimized) {
+#if 0
+ quantity->index = ++bigints_index; // if !optimized, this is garbage
+ bigints_count++;
+ byte = 2;
+#endif
+ } else {
+ byte = 1;
+ }
+ out.write(&byte, sizeof(byte));
+
+ std::size_t size;
+ static char buf[4096];
+ mpz_export(buf, &size, 1, sizeof(short), 0, 0, MPZ(quantity));
+ unsigned short len = size * sizeof(short);
+ out.write(reinterpret_cast<char *>(&len), sizeof(len));
+ if (len) {
+ assert(len < 4096);
+ out.write(buf, len);
+ }
+
+ byte = mpz_sgn(MPZ(quantity)) < 0 ? 1 : 0;
+ out.write(&byte, sizeof(byte));
+
+ out.write(reinterpret_cast<char *>(&quantity->prec), sizeof(quantity->prec));
+ bigint_t::flags_t tflags = quantity->flags() & ~BIGINT_BULK_ALLOC;
+ assert(sizeof(tflags) == sizeof(bigint_t::flags_t));
+ out.write(reinterpret_cast<char *>(&tflags), sizeof(tflags));
+ } else {
+ assert(quantity->ref > 1);
+
+ // Since this value has already been written, we simply write
+ // out a reference to which one it was.
+ byte = 3;
+ out.write(&byte, sizeof(byte));
+ out.write(reinterpret_cast<char *>(&quantity->index), sizeof(quantity->index));
+ }
+}
+
+bool amount_t::valid() const
+{
+ if (quantity) {
+ if (! quantity->valid())
+ return false;
+
+ if (quantity->ref == 0) {
+ DEBUG("ledger.validate", "amount_t: quantity->ref == 0");
+ return false;
+ }
+ }
+ else if (commodity_) {
+ DEBUG("ledger.validate", "amount_t: commodity_ != NULL");
+ return false;
+ }
+ return true;
+}
+
+} // namespace ledger
diff --git a/src/amount.h b/src/amount.h
new file mode 100644
index 00000000..8fb5aa14
--- /dev/null
+++ b/src/amount.h
@@ -0,0 +1,748 @@
+/*
+ * Copyright (c) 2003-2008, 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 amount.h
+ * @author John Wiegley
+ * @date Wed Apr 18 22:05:53 2007
+ *
+ * @brief Basic type for handling commoditized math: amount_t.
+ *
+ * This file contains the most basic numerical type in Ledger:
+ * amount_t, which relies upon commodity.h (commodity_t) for handling
+ * commoditized amounts. This class allows Ledger to handle
+ * mathematical expressions involving differing commodities, as well
+ * as math using no commodities at all (such as increasing a dollar
+ * amount by a multiplier).
+ */
+#ifndef _AMOUNT_H
+#define _AMOUNT_H
+
+#include "utils.h"
+
+namespace ledger {
+
+class commodity_t;
+class annotation_t;
+class commodity_pool_t;
+
+DECLARE_EXCEPTION(amount_error, std::runtime_error);
+
+/**
+ * @class amount_t
+ *
+ * @brief Encapsulates infinite-precision commoditized amounts.
+ *
+ * The amount_t class can be used for commoditized infinite-precision
+ * math, and also for uncommoditized math. In the commoditized case,
+ * commodities keep track of how they are used, and will always
+ * display back to the user after the same fashion. For
+ * uncommoditized numbers, no display truncation is ever done. In
+ * both cases, internal precision is always kept to an excessive
+ * degree.
+ */
+class amount_t
+ : public ordered_field_operators<amount_t,
+#ifdef HAVE_GDTOA
+ ordered_field_operators<amount_t, double,
+#endif
+ ordered_field_operators<amount_t, unsigned long,
+ ordered_field_operators<amount_t, long> > >
+#ifdef HAVE_GDTOA
+ >
+#endif
+{
+ // jww (2007-05-03): Make this private, and then make
+ // ledger::initialize into a member function of session_t.
+public:
+ /**
+ * The initialize and shutdown methods ready the amount subsystem
+ * for use. Normally they are called by `ledger::initialize' and
+ * `ledger::shutdown'.
+ */
+ static void initialize();
+ static void shutdown();
+
+public:
+ typedef uint_least16_t precision_t;
+
+ /**
+ * The current_pool is a static variable indicating which commodity
+ * pool should be used.
+ */
+ static commodity_pool_t * current_pool;
+
+ /**
+ * The `keep_base' member determines whether scalable commodities
+ * are automatically converted to their most reduced form when
+ * printing. The default is true.
+ *
+ * For example, Ledger supports time values specified in seconds
+ * (10s), hours (5.2h) or minutes. Internally, such amounts are
+ * always kept as quantities of seconds. However, when streaming
+ * the amount Ledger will convert it to its "least representation",
+ * which is "5.2h" in the second case. If `keep_base' is true, this
+ * amount is displayed as "18720s".
+ */
+ static bool keep_base;
+
+ /**
+ * The following three members determine whether lot details are
+ * maintained when working with commoditized values. The default is
+ * false for all three.
+ *
+ * Let's say a user adds two values of the following form:
+ * 10 AAPL + 10 AAPL {$20}
+ *
+ * This expression adds ten shares of Apple stock with another ten
+ * shares that were purchased for $20 a share. If `keep_price' is
+ * false, the result of this expression will be an amount equal to
+ * 20 AAPL. If `keep_price' is true, the expression yields an
+ * exception for adding amounts with different commodities. In that
+ * case, a balance_t object must be used to store the combined sum.
+ */
+ static bool keep_price;
+ static bool keep_date;
+ static bool keep_tag;
+
+ /**
+ * The `stream_fullstrings' static member is currently only used by
+ * the unit testing code. It causes amounts written to streams to
+ * use the `to_fullstring' method rather than the `to_string'
+ * method, so that complete precision is always displayed, no matter
+ * what the precision of an individual commodity might be.
+ * @see to_string
+ * @see to_fullstring
+ */
+ static bool stream_fullstrings;
+
+ static uint_fast32_t sizeof_bigint_t();
+
+protected:
+ void _copy(const amount_t& amt);
+ void _dup();
+ void _resize(precision_t prec);
+ void _clear();
+ void _release();
+
+ struct bigint_t;
+
+public: // needed by binary.cc
+ bigint_t * quantity;
+ commodity_t * commodity_;
+
+public:
+ /**
+ * Constructors. amount_t supports several forms of construction:
+ *
+ * amount_t() creates a value for which `is_null' is true, and which
+ * has no value or commodity. If used in value situations it will
+ * be zero, and its commodity equals `commodity_t::null_commodity'.
+ *
+ * amount_t(double), amount_t(unsigned long), amount_t(long) all
+ * convert from the respective numerics type to an amount. No
+ * precision or sign is lost in any of these conversions. The
+ * resulting commodity is always `commodity_t::null_commodity'.
+ *
+ * amount_t(string), amount_t(const char *) both convert from a
+ * string representation of an amount, which may or may not include
+ * a commodity. This is the proper way to initialize an amount like
+ * '$100.00'.
+ */
+ amount_t() : quantity(NULL), commodity_(NULL) {
+ TRACE_CTOR(amount_t, "");
+ }
+#ifdef HAVE_GDTOA
+ amount_t(const double val);
+#endif
+ amount_t(const unsigned long val);
+ amount_t(const long val);
+
+ explicit amount_t(const string& val) : quantity(NULL) {
+ TRACE_CTOR(amount_t, "const string&");
+ parse(val);
+ }
+ explicit amount_t(const char * val) : quantity(NULL) {
+ TRACE_CTOR(amount_t, "const char *");
+ assert(val);
+ parse(val);
+ }
+
+ /**
+ * Static creator function. Calling amount_t::exact(string) will
+ * create an amount whose display precision is never truncated, even
+ * if the amount uses a commodity (which normally causes "round on
+ * streaming" to occur). This function is mostly used by the
+ * debugging code. It is the proper way to initialize '$100.005',
+ * where display of the extra precision is required. If a regular
+ * constructor is used, this amount will stream as '$100.01', even
+ * though its internal value always equals $100.005.
+ */
+ static amount_t exact(const string& value);
+
+ /**
+ * Destructor. Releases the reference count held for the underlying
+ * bigint_t object pointed to be `quantity'.
+ */
+ ~amount_t() {
+ TRACE_DTOR(amount_t);
+ if (quantity)
+ _release();
+ }
+
+ /**
+ * Assignment and copy operators. An amount may be assigned or
+ * copied. If a double, long or unsigned long is assigned to an
+ * amount, a temporary is constructed, and then the temporary is
+ * assigned to `this'. Both the value and the commodity are copied,
+ * causing the result to compare equal to the reference amount.
+ *
+ * Note: `quantity' must be initialized to NULL first, otherwise the
+ * `_copy' function will attempt to release the uninitialized pointer.
+ */
+ amount_t(const amount_t& amt) : quantity(NULL) {
+ TRACE_CTOR(amount_t, "copy");
+ if (amt.quantity)
+ _copy(amt);
+ else
+ commodity_ = NULL;
+ }
+ amount_t& operator=(const amount_t& amt);
+
+#ifdef HAVE_GDTOA
+ amount_t& operator=(const double val) {
+ return *this = amount_t(val);
+ }
+#endif
+ amount_t& operator=(const unsigned long val) {
+ return *this = amount_t(val);
+ }
+ amount_t& operator=(const long val) {
+ return *this = amount_t(val);
+ }
+
+ amount_t& operator=(const string& str) {
+ return *this = amount_t(str);
+ }
+ amount_t& operator=(const char * str) {
+ assert(str);
+ return *this = amount_t(str);
+ }
+
+ /**
+ * Comparison operators. The fundamental comparison operation for
+ * amounts is `compare', which returns a value less than, greater
+ * than or equal to zero. All the other comparison operators are
+ * defined in terms of this method. The only special detail is that
+ * `operator==' will fail immediately if amounts with different
+ * commodities are being compared. Otherwise, if the commodities
+ * are equivalent (@see keep_price, et al), then the amount
+ * quantities are compared numerically.
+ *
+ * Comparison between an amount and a double, long or unsigned long
+ * is allowed. In such cases the non-amount value is constructed as
+ * an amount temporary, which is then compared to `this'.
+ */
+ int compare(const amount_t& amt) const;
+
+ bool operator==(const amount_t& amt) const;
+
+ template <typename T>
+ bool operator==(const T& val) const {
+ return compare(val) == 0;
+ }
+ template <typename T>
+ bool operator<(const T& amt) const {
+ return compare(amt) < 0;
+ }
+ template <typename T>
+ bool operator>(const T& amt) const {
+ return compare(amt) > 0;
+ }
+
+ /**
+ * Binary arithmetic operators. Amounts support addition,
+ * subtraction, multiplication and division -- but not modulus,
+ * bitwise operations, or shifting. Arithmetic is also supported
+ * between amounts, double, long and unsigned long, in which case
+ * temporary amount are constructed for the life of the expression.
+ *
+ * Although only in-place operators are defined here, the remainder
+ * are provided by `boost::ordered_field_operators<>'.
+ */
+ amount_t& operator+=(const amount_t& amt);
+ amount_t& operator-=(const amount_t& amt);
+ amount_t& operator*=(const amount_t& amt);
+ amount_t& operator/=(const amount_t& amt);
+
+ /**
+ * Unary arithmetic operators. There are several unary methods
+ * support on amounts:
+ *
+ * precision() return an amount's current, internal precision. To
+ * find the precision it will be displayed at -- assuming it was not
+ * created using the static method `amount_t::exact' -- refer to
+ * commodity().precision.
+ *
+ * negate(), also unary minus (- x), returns the negated value of an
+ * amount.
+ *
+ * abs() returns the absolute value of an amount. It is equivalent
+ * to: `(x < 0) ? - x : x'.
+ *
+ * round(precision_t) and round() round an amount's internal value
+ * to the given precision, or to the commodity's current display
+ * precision if no precision value is given. This method changes
+ * the internal value of the amount, if it's internal precision was
+ * greater than the rounding precision.
+ *
+ * unround() yields an amount whose display precision is never
+ * truncated, even though its commodity normally displays only
+ * rounded values.
+ *
+ * reduce() reduces a value to its most basic commodity form, for
+ * amounts that utilize "scaling commodities". For example, an
+ * amount of 1h after reduction will be 3600s.
+ *
+ * unreduce(), if used with a "scaling commodity", yields the most
+ * compact form greater than 1.0. That is, 3599s will unreduce to
+ * 59.98m, while 3601 unreduces to 1h.
+ *
+ * value(optional<datetime_t>) returns the historical value for an
+ * amount -- the default moment returns the most recently known
+ * price -- based on the price history of its commodity. For
+ * example, if the amount were 10 AAPL, and on Apr 10, 2000 each
+ * share of AAPL was worth $10, then call value() for that moment in
+ * time would yield the amount $100.00.
+ *
+ * Further, for the sake of efficiency and avoiding temporary
+ * objects, the following methods support "in-place" variants that
+ * act on the amount itself and return a reference to the result
+ * (`*this'):
+ *
+ * in_place_negate()
+ * in_place_reduce()
+ * in_place_unreduce()
+ */
+ precision_t precision() const;
+
+ amount_t negate() const {
+ amount_t temp(*this);
+ temp.in_place_negate();
+ return temp;
+ }
+ amount_t& in_place_negate();
+
+ amount_t operator-() const {
+ return negate();
+ }
+
+ amount_t abs() const {
+ if (sign() < 0)
+ return negate();
+ return *this;
+ }
+
+ amount_t round() const;
+ amount_t round(precision_t prec) const;
+ amount_t unround() const;
+
+ amount_t reduce() const {
+ amount_t temp(*this);
+ temp.in_place_reduce();
+ return temp;
+ }
+ amount_t& in_place_reduce();
+
+ amount_t unreduce() const {
+ amount_t temp(*this);
+ temp.in_place_unreduce();
+ return temp;
+ }
+ amount_t& in_place_unreduce();
+
+ optional<amount_t> value(const optional<datetime_t>& moment = none) const;
+
+ /**
+ * Truth tests. An amount may be truth test in several ways:
+ *
+ * sign() returns an integer less than, greater than, or equal to
+ * zero depending on whether the amount is negative, zero, or
+ * greater than zero. Note that this function tests the actual
+ * value of the amount -- using its internal precision -- and not
+ * the display value. To test its display value, use:
+ * `round().sign()'.
+ *
+ * is_nonzero(), or operator bool, returns true if an amount's
+ * display value is not zero.
+ *
+ * is_zero() returns true if an amount's display value is zero.
+ * Thus, $0.0001 is considered zero if the current display precision
+ * for dollars is two decimal places.
+ *
+ * is_realzero() returns true if an amount's actual value is zero.
+ * Thus, $0.0001 is never considered realzero.
+ *
+ * is_null() returns true if an amount has no value and no
+ * commodity. This only occurs if an uninitialized amount has never
+ * been assigned a value.
+ */
+ int sign() const;
+
+ operator bool() const {
+ return is_nonzero();
+ }
+ bool is_nonzero() const {
+ return ! is_zero();
+ }
+
+ bool is_zero() const;
+ bool is_realzero() const {
+ return sign() == 0;
+ }
+
+ bool is_null() const {
+ if (! quantity) {
+ assert(! commodity_);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Conversion methods. An amount may be converted to the same types
+ * it can be constructed from -- with the exception of unsigned
+ * long. Implicit conversions are not allowed in C++ (though they
+ * are in Python), rather the following conversion methods must be
+ * called explicitly:
+ *
+ * to_double([bool]) returns an amount as a double. If the optional
+ * boolean argument is true (the default), an exception is thrown if
+ * the conversion would lose information.
+ *
+ * to_long([bool]) returns an amount as a long integer. If the
+ * optional boolean argument is true (the default), an exception is
+ * thrown if the conversion would lose information.
+ *
+ * fits_in_double() returns true if to_double() would not lose
+ * precision.
+ *
+ * fits_in_long() returns true if to_long() would not lose
+ * precision.
+ *
+ * to_string() returns an amount'ss "display value" as a string --
+ * after rounding the value according to the commodity's default
+ * precision. It is equivalent to: `round().to_fullstring()'.
+ *
+ * to_fullstring() returns an amount's "internal value" as a string,
+ * without any rounding.
+ *
+ * quantity_string() returns an amount's "display value", but
+ * without any commodity. Note that this is different from
+ * `number().to_string()', because in that case the commodity has
+ * been stripped and the full, internal precision of the amount
+ * would be displayed.
+ */
+#ifdef HAVE_GDTOA
+ double to_double(bool no_check = false) const;
+#endif
+ long to_long(bool no_check = false) const;
+ string to_string() const;
+ string to_fullstring() const;
+ string quantity_string() const;
+
+#ifdef HAVE_GDTOA
+ bool fits_in_double() const;
+#endif
+ bool fits_in_long() const;
+
+ /**
+ * Commodity-related methods. The following methods relate to an
+ * amount's commodity:
+ *
+ * commodity() returns an amount's commodity. If the amount has no
+ * commodity, the value returned is `current_pool->null_commodity'.
+ *
+ * has_commodity() returns true if the amount has a commodity.
+ *
+ * set_commodity(commodity_t) sets an amount's commodity to the
+ * given value. Note that this merely sets the current amount to
+ * that commodity, it does not "observe" the amount for possible
+ * changes in the maximum display precision of the commodity, the
+ * way that `parse' does.
+ *
+ * clear_commodity() sets an amount's commodity to null, such that
+ * has_commodity() afterwards returns false.
+ *
+ * number() returns a commodity-less version of an amount. This is
+ * useful for accessing just the numeric portion of an amount.
+ */
+ commodity_t& commodity() const;
+
+ bool has_commodity() const;
+ void set_commodity(commodity_t& comm) {
+ if (! quantity)
+ *this = 0L;
+ commodity_ = &comm;
+ }
+ void clear_commodity() {
+ commodity_ = NULL;
+ }
+
+ amount_t number() const {
+ if (! has_commodity())
+ return *this;
+
+ amount_t temp(*this);
+ temp.clear_commodity();
+ return temp;
+ }
+
+ /**
+ * Annotated commodity methods. An amount's commodity may be
+ * annotated with special details, such as the price it was
+ * purchased for, when it was acquired, or an arbitrary note,
+ * identifying perhaps the lot number of an item.
+ *
+ * annotate_commodity(amount_t price, [datetime_t date, string tag])
+ * sets the annotations for the current amount's commodity. Only
+ * the price argument is required, although it can be passed as
+ * `none' if no price is desired.
+ *
+ * commodity_annotated() returns true if an amount's commodity has
+ * any annotation details associated with it.
+ *
+ * annotation_details() returns all of the details of an annotated
+ * commodity's annotations. The structure returns will evaluate as
+ * boolean false if there are no details.
+ *
+ * strip_annotations([keep_price, keep_date, keep_tag]) returns an
+ * amount whose commodity's annotations have been stripped. The
+ * three `keep_' arguments determine which annotation detailed are
+ * kept, meaning that the default is to follow whatever
+ * amount_t::keep_price, amount_t::keep_date and amount_t::keep_tag
+ * have been set to (which all default to false).
+ */
+ void annotate(const annotation_t& details);
+ bool annotated() const;
+
+ annotation_t& annotation();
+ const annotation_t& annotation() const {
+ return const_cast<amount_t&>(*this).annotation();
+ }
+
+ amount_t strip_annotations(const bool _keep_price = keep_price,
+ const bool _keep_date = keep_date,
+ const bool _keep_tag = keep_tag) const;
+
+ /**
+ * Parsing methods. The method `parse' is used to parse an amount
+ * from an input stream or a string. A global operator>> is also
+ * defined which simply calls parse on the input stream. The
+ * `parse' method has two forms:
+ *
+ * parse(istream, flags_t) parses an amount from the given input
+ * stream.
+ *
+ * parse(string, flags_t) parses an amount from the given string.
+ *
+ * parse(string, flags_t) also parses an amount from a string.
+ *
+ * The `flags' argument of both parsing may be one or more of the
+ * following:
+ *
+ * AMOUNT_PARSE_NO_MIGRATE means to not pay attention to the way an
+ * amount is used. Ordinarily, if an amount were $100.001, for
+ * example, it would cause the default display precision for $ to be
+ * "widened" to three decimal places. If AMOUNT_PARSE_NO_MIGRATE is
+ * used, the commodity's default display precision is not changed.
+ *
+ * AMOUNT_PARSE_NO_REDUCE means not to call in_place_reduce() on the
+ * resulting amount after it is parsed.
+ *
+ * These parsing methods observe the amounts they parse (unless
+ * AMOUNT_PARSE_NO_MIGRATE is true), and set the display details of
+ * the corresponding commodity accordingly. This way, amounts do
+ * not require commodities to be pre-defined in any way, but merely
+ * displays them back to the user in the same fashion as it saw them
+ * used.
+ *
+ * There is also a static convenience method called
+ * `parse_conversion' which can be used to define a relationship
+ * between scaling commodity values. For example, Ledger uses it to
+ * define the relationships among various time values:
+ *
+ * amount_t::parse_conversion("1.0m", "60s"); // a minute is 60 seconds
+ * amount_t::parse_conversion("1.0h", "60m"); // an hour is 60 minutes
+ */
+#define AMOUNT_PARSE_NO_MIGRATE 0x01
+#define AMOUNT_PARSE_NO_REDUCE 0x02
+#define AMOUNT_PARSE_SOFT_FAIL 0x04
+
+ typedef uint_least8_t flags_t;
+
+ bool parse(std::istream& in, flags_t flags = 0);
+ bool parse(const string& str, flags_t flags = 0) {
+ std::istringstream stream(str);
+ bool result = parse(stream, flags);
+ assert(stream.eof());
+ return result;
+ }
+
+ static void parse_conversion(const string& larger_str,
+ const string& smaller_str);
+
+ /**
+ * Printing methods. An amount may be output to a stream using the
+ * `print' method. There is also a global operator<< defined which
+ * simply calls print for an amount on the given stream. There is
+ * one form of the print method, which takes one required argument
+ * and two arguments with default values:
+ *
+ * print(ostream, bool omit_commodity = false, bool full_precision =
+ * false) prints an amounts to the given output stream, using its
+ * commodity's default display characteristics. If `omit_commodity'
+ * is true, the commodity will not be displayed, only the amount
+ * (although the commodity's display precision is still used). If
+ * `full_precision' is true, the full internal precision of the
+ * amount is displayed, regardless of its commodity's display
+ * precision.
+ */
+ void print(std::ostream& out, bool omit_commodity = false,
+ bool full_precision = false) const;
+
+ /**
+ * Serialization methods. An amount may be deserialized from an
+ * input stream or a character pointer, and it may be serialized to
+ * an output stream. The methods used are:
+ *
+ * read(istream) reads an amount from the given input stream. It
+ * must have been put there using `write(ostream)'. The required
+ * flow of logic is:
+ * amount_t::current_pool->write(out)
+ * amount.write(out) // write out all amounts
+ * amount_t::current_pool->read(in)
+ * amount.read(in)
+ *
+ * read(char *&) reads an amount from data which has been read from
+ * an input stream into a buffer. It advances the pointer passed in
+ * to the end of the deserialized amount.
+ *
+ * write(ostream, [bool]) writes an amount to an output stream in a
+ * compact binary format. If the second parameter is true,
+ * quantities with multiple reference counts will be written in an
+ * optimized fashion. NOTE: This form of usage is valid only for
+ * the binary journal writer, it should not be used otherwise, as it
+ * has strict requirements for reading that only the binary reader
+ * knows about.
+ */
+ void read(std::istream& in);
+ void read(const char *& data);
+ void write(std::ostream& out, bool optimize = false) const;
+
+ /**
+ * Debugging methods. There are two methods defined to help with
+ * debugging:
+ *
+ * dump(ostream) dumps an amount to an output stream. There is
+ * little different from print(), it simply surrounds the display
+ * value with a marker, for example "AMOUNT($1.00)". This code is
+ * used by other dumping code elsewhere in Ledger.
+ *
+ * valid() returns true if an amount is valid. This ensures that if
+ * an amount has a commodity, it has a valid value pointer, for
+ * example, even if that pointer simply points to a zero value.
+ */
+ void dump(std::ostream& out) const {
+ out << "AMOUNT(";
+ print(out);
+ out << ")";
+ }
+
+ bool valid() const;
+};
+
+inline amount_t amount_t::exact(const string& value) {
+ amount_t temp;
+ temp.parse(value, AMOUNT_PARSE_NO_MIGRATE);
+ return temp;
+}
+
+inline string amount_t::to_string() const {
+ std::ostringstream bufstream;
+ print(bufstream);
+ return bufstream.str();
+}
+
+inline string amount_t::to_fullstring() const {
+ std::ostringstream bufstream;
+ print(bufstream, false, true);
+ return bufstream.str();
+}
+
+inline string amount_t::quantity_string() const {
+ std::ostringstream bufstream;
+ print(bufstream, true);
+ return bufstream.str();
+}
+
+inline std::ostream& operator<<(std::ostream& out, const amount_t& amt) {
+ amt.print(out, false, amount_t::stream_fullstrings);
+ return out;
+}
+inline std::istream& operator>>(std::istream& in, amount_t& amt) {
+ amt.parse(in);
+ return in;
+}
+
+} // namespace ledger
+
+#include "commodity.h"
+
+namespace ledger {
+
+inline bool amount_t::operator==(const amount_t& amt) const {
+ if (commodity() != amt.commodity())
+ return false;
+ return compare(amt) == 0;
+}
+
+inline commodity_t& amount_t::commodity() const {
+ return has_commodity() ? *commodity_ : *current_pool->null_commodity;
+}
+
+inline bool amount_t::has_commodity() const {
+ return commodity_ && commodity_ != commodity_->parent().null_commodity;
+}
+
+} // namespace ledger
+
+#endif // _AMOUNT_H
diff --git a/src/balance.cc b/src/balance.cc
new file mode 100644
index 00000000..bac8d40c
--- /dev/null
+++ b/src/balance.cc
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2003-2008, 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 "balance.h"
+
+namespace ledger {
+
+balance_t& balance_t::operator+=(const balance_t& bal)
+{
+ foreach (const amounts_map::value_type& pair, bal.amounts)
+ *this += pair.second;
+ return *this;
+}
+
+balance_t& balance_t::operator+=(const amount_t& amt)
+{
+ if (amt.is_null())
+ throw_(balance_error,
+ "Cannot add an uninitialized amount to a balance");
+
+ if (amt.is_realzero())
+ return *this;
+
+ amounts_map::iterator i = amounts.find(&amt.commodity());
+ if (i != amounts.end())
+ i->second += amt;
+ else
+ amounts.insert(amounts_map::value_type(&amt.commodity(), amt));
+
+ return *this;
+}
+
+balance_t& balance_t::operator-=(const balance_t& bal)
+{
+ foreach (const amounts_map::value_type& pair, bal.amounts)
+ *this -= pair.second;
+ return *this;
+}
+
+balance_t& balance_t::operator-=(const amount_t& amt)
+{
+ if (amt.is_null())
+ throw_(balance_error,
+ "Cannot subtract an uninitialized amount from a balance");
+
+ if (amt.is_realzero())
+ return *this;
+
+ amounts_map::iterator i = amounts.find(&amt.commodity());
+ if (i != amounts.end()) {
+ i->second -= amt;
+ if (i->second.is_realzero())
+ amounts.erase(i);
+ } else {
+ amounts.insert(amounts_map::value_type(&amt.commodity(), amt.negate()));
+ }
+ return *this;
+}
+
+balance_t& balance_t::operator*=(const amount_t& amt)
+{
+ if (amt.is_null())
+ throw_(balance_error,
+ "Cannot multiply a balance by an uninitialized amount");
+
+ if (is_realzero()) {
+ ;
+ }
+ else if (amt.is_realzero()) {
+ *this = amt;
+ }
+ else if (! amt.commodity()) {
+ // Multiplying by an amount with no commodity causes all the
+ // component amounts to be increased by the same factor.
+ foreach (amounts_map::value_type& pair, amounts)
+ pair.second *= amt;
+ }
+ else if (amounts.size() == 1) {
+ // Multiplying by a commoditized amount is only valid if the sole
+ // commodity in the balance is of the same kind as the amount's
+ // commodity.
+ if (*amounts.begin()->first == amt.commodity())
+ amounts.begin()->second *= amt;
+ else
+ throw_(balance_error,
+ "Cannot multiply a balance with annotated commodities by a commoditized amount");
+ }
+ else {
+ assert(amounts.size() > 1);
+ throw_(balance_error,
+ "Cannot multiply a multi-commodity balance by a commoditized amount");
+ }
+ return *this;
+}
+
+balance_t& balance_t::operator/=(const amount_t& amt)
+{
+ if (amt.is_null())
+ throw_(balance_error,
+ "Cannot divide a balance by an uninitialized amount");
+
+ if (is_realzero()) {
+ ;
+ }
+ else if (amt.is_realzero()) {
+ throw_(balance_error, "Divide by zero");
+ }
+ else if (! amt.commodity()) {
+ // Dividing by an amount with no commodity causes all the
+ // component amounts to be divided by the same factor.
+ foreach (amounts_map::value_type& pair, amounts)
+ pair.second /= amt;
+ }
+ else if (amounts.size() == 1) {
+ // Dividing by a commoditized amount is only valid if the sole
+ // commodity in the balance is of the same kind as the amount's
+ // commodity.
+ if (*amounts.begin()->first == amt.commodity())
+ amounts.begin()->second /= amt;
+ else
+ throw_(balance_error,
+ "Cannot divide a balance with annotated commodities by a commoditized amount");
+ }
+ else {
+ assert(amounts.size() > 1);
+ throw_(balance_error,
+ "Cannot divide a multi-commodity balance by a commoditized amount");
+ }
+ return *this;
+}
+
+optional<balance_t>
+balance_t::value(const optional<datetime_t>& moment) const
+{
+ optional<balance_t> temp;
+
+ foreach (const amounts_map::value_type& pair, amounts)
+ if (optional<amount_t> val = pair.second.value(moment)) {
+ if (! temp)
+ temp = balance_t();
+ *temp += *val;
+ }
+
+ return temp;
+}
+
+optional<amount_t>
+balance_t::commodity_amount(const optional<const commodity_t&>& commodity) const
+{
+ // jww (2007-05-20): Needs work
+ if (! commodity) {
+ if (amounts.size() == 1) {
+ amounts_map::const_iterator i = amounts.begin();
+ return i->second;
+ }
+ else if (amounts.size() > 1) {
+ // Try stripping annotations before giving an error.
+ balance_t temp(strip_annotations());
+ if (temp.amounts.size() == 1)
+ return temp.commodity_amount(commodity);
+
+ throw_(amount_error,
+ "Requested amount of a balance with multiple commodities: " << temp);
+ }
+ }
+ else if (amounts.size() > 0) {
+ amounts_map::const_iterator i = amounts.find(&*commodity);
+ if (i != amounts.end())
+ return i->second;
+ }
+ return none;
+}
+
+balance_t balance_t::strip_annotations(const bool keep_price,
+ const bool keep_date,
+ const bool keep_tag) const
+{
+ balance_t temp;
+
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.strip_annotations(keep_price, keep_date, keep_tag);
+
+ return temp;
+}
+
+void balance_t::print(std::ostream& out,
+ const int first_width,
+ const int latter_width) const
+{
+ bool first = true;
+ int lwidth = latter_width;
+
+ if (lwidth == -1)
+ lwidth = first_width;
+
+ typedef std::vector<const amount_t *> amounts_array;
+ amounts_array sorted;
+
+ foreach (const amounts_map::value_type& pair, amounts)
+ if (pair.second)
+ sorted.push_back(&pair.second);
+
+ std::stable_sort(sorted.begin(), sorted.end(), compare_amount_commodities());
+
+ foreach (const amount_t * amount, sorted) {
+ int width;
+ if (! first) {
+ out << std::endl;
+ width = lwidth;
+ } else {
+ first = false;
+ width = first_width;
+ }
+
+ out.width(width);
+ out.fill(' ');
+ out << std::right << *amount;
+ }
+
+ if (first) {
+ out.width(first_width);
+ out.fill(' ');
+ out << std::right << "0";
+ }
+}
+
+} // namespace ledger
diff --git a/src/balance.h b/src/balance.h
new file mode 100644
index 00000000..5146937c
--- /dev/null
+++ b/src/balance.h
@@ -0,0 +1,526 @@
+/*
+ * Copyright (c) 2003-2008, 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 balance.h
+ * @author John Wiegley
+ * @date Sun May 20 15:28:44 2007
+ *
+ * @brief Basic type for adding multiple commodities together.
+ *
+ * Unlike the amount_t class, which throws an exception if amounts of
+ * differing commodities are added or subtracted, the balance_t class
+ * is designed to allow this, tracking the amounts of each component
+ * commodity separately.
+ */
+#ifndef _BALANCE_H
+#define _BALANCE_H
+
+#include "amount.h"
+
+namespace ledger {
+
+DECLARE_EXCEPTION(balance_error, std::runtime_error);
+
+/**
+ * @class balance_t
+ *
+ * @brief A wrapper around amount_t allowing addition of multiple commodities.
+ *
+ * The balance_t class is appopriate for keeping a running balance
+ * where amounts of multiple commodities may be involved.
+ */
+class balance_t
+ : public equality_comparable<balance_t,
+ equality_comparable<balance_t, amount_t,
+ equality_comparable<balance_t, double,
+ equality_comparable<balance_t, unsigned long,
+ equality_comparable<balance_t, long,
+ additive<balance_t,
+ additive<balance_t, amount_t,
+ additive<balance_t, double,
+ additive<balance_t, unsigned long,
+ additive<balance_t, long,
+ multiplicative<balance_t, amount_t,
+ multiplicative<balance_t, double,
+ multiplicative<balance_t, unsigned long,
+ multiplicative<balance_t, long> > > > > > > > > > > > > >
+{
+public:
+ typedef std::map<const commodity_t *, amount_t> amounts_map;
+
+ amounts_map amounts;
+
+ // jww (2007-05-20): Remove these two by adding access methods
+ friend class value_t;
+ friend class entry_base_t;
+
+ /**
+ * Constructors. balance_t supports similar forms of construction
+ * to amount_t.
+ *
+ * balance_t() creates an empty balance to which amounts or other
+ * balances may be added or subtracted.
+ *
+ * balance_t(amount_t) constructs a balance whose starting value is
+ * equal to the given amount.
+ *
+ * balance_t(double), balance_t(unsigned long) and balance_t(long)
+ * will construct an amount from their arguments and then construct
+ * a balance whose starting value is equal to that amount. This
+ * initial balance will have no commodity.
+ *
+ * balance_t(string) and balance_t(const char *) both convert from a
+ * string representation of an amount to a balance whose initial
+ * value is that amount. This is the proper way to initialize a
+ * balance like '$100.00'.
+ */
+ balance_t() {
+ TRACE_CTOR(balance_t, "");
+ }
+ balance_t(const amount_t& amt) {
+ TRACE_CTOR(balance_t, "const amount_t&");
+ if (amt.is_null())
+ throw_(balance_error,
+ "Cannot initialize a balance from an uninitialized amount");
+ if (! amt.is_realzero())
+ amounts.insert(amounts_map::value_type(&amt.commodity(), amt));
+ }
+#ifdef HAVE_GDTOA
+ balance_t(const double val) {
+ TRACE_CTOR(balance_t, "const double");
+ amounts.insert
+ (amounts_map::value_type(amount_t::current_pool->null_commodity, val));
+ }
+#endif
+ balance_t(const unsigned long val) {
+ TRACE_CTOR(balance_t, "const unsigned long");
+ amounts.insert
+ (amounts_map::value_type(amount_t::current_pool->null_commodity, val));
+ }
+ balance_t(const long val) {
+ TRACE_CTOR(balance_t, "const long");
+ amounts.insert
+ (amounts_map::value_type(amount_t::current_pool->null_commodity, val));
+ }
+
+ explicit balance_t(const string& val) {
+ TRACE_CTOR(balance_t, "const string&");
+ amount_t temp(val);
+ amounts.insert(amounts_map::value_type(&temp.commodity(), temp));
+ }
+ explicit balance_t(const char * val) {
+ TRACE_CTOR(balance_t, "const char *");
+ amount_t temp(val);
+ amounts.insert(amounts_map::value_type(&temp.commodity(), temp));
+ }
+
+ /**
+ * Destructor. Destroys all of the accumulated amounts in the
+ * balance.
+ */
+ virtual ~balance_t() {
+ TRACE_DTOR(balance_t);
+ }
+
+ /**
+ * Assignment and copy operators. An balance may be assigned or copied.
+ */
+ balance_t(const balance_t& bal) : amounts(bal.amounts) {
+ TRACE_CTOR(balance_t, "copy");
+ }
+
+ balance_t& operator=(const balance_t& bal) {
+ if (this != &bal)
+ amounts = bal.amounts;
+ return *this;
+ }
+ balance_t& operator=(const amount_t& amt) {
+ if (amt.is_null())
+ throw_(balance_error,
+ "Cannot assign an uninitialized amount to a balance");
+
+ amounts.clear();
+ if (! amt.is_realzero())
+ amounts.insert(amounts_map::value_type(&amt.commodity(), amt));
+
+ return *this;
+ }
+
+ balance_t& operator=(const string& str) {
+ return *this = balance_t(str);
+ }
+ balance_t& operator=(const char * str) {
+ return *this = balance_t(str);
+ }
+
+ /**
+ * Comparison operators. Balances are fairly restrictive in terms
+ * of how they may be compared. They may be compared for equality
+ * or inequality, but this is all, since the concept of "less than"
+ * or "greater than" makes no sense when amounts of multiple
+ * commodities are involved.
+ *
+ * Balances may also be compared to amounts, in which case the sum
+ * of the balance must equal the amount exactly.
+ *
+ * If a comparison between balances is desired, the balances must
+ * first be rendered to value equivalent amounts using the `value'
+ * method, to determine a market valuation at some specific moment
+ * in time.
+ */
+ bool operator==(const balance_t& bal) const {
+ amounts_map::const_iterator i, j;
+ for (i = amounts.begin(), j = bal.amounts.begin();
+ i != amounts.end() && j != bal.amounts.end();
+ i++, j++) {
+ if (! (i->first == j->first && i->second == j->second))
+ return false;
+ }
+ return i == amounts.end() && j == bal.amounts.end();
+ }
+ bool operator==(const amount_t& amt) const {
+ if (amt.is_null())
+ throw_(balance_error,
+ "Cannot compare a balance to an uninitialized amount");
+
+ if (amt.is_realzero())
+ return amounts.empty();
+ else
+ return amounts.size() == 1 && amounts.begin()->second == amt;
+ }
+
+ template <typename T>
+ bool operator==(const T& val) const {
+ return *this == balance_t(val);
+ }
+
+ /**
+ * Binary arithmetic operators. Balances support addition and
+ * subtraction of other balances or amounts, but multiplication and
+ * division are restricted to uncommoditized amounts only.
+ */
+ balance_t& operator+=(const balance_t& bal);
+ balance_t& operator+=(const amount_t& amt);
+ balance_t& operator-=(const balance_t& bal);
+ balance_t& operator-=(const amount_t& amt);
+
+ virtual balance_t& operator*=(const amount_t& amt);
+
+#ifdef HAVE_GDTOA
+ balance_t& operator*=(const double val) {
+ return *this *= amount_t(val);
+ }
+#endif
+ balance_t& operator*=(const unsigned long val) {
+ return *this *= amount_t(val);
+ }
+ balance_t& operator*=(const long val) {
+ return *this *= amount_t(val);
+ }
+
+ virtual balance_t& operator/=(const amount_t& amt);
+
+#ifdef HAVE_GDTOA
+ balance_t& operator/=(const double val) {
+ return *this /= amount_t(val);
+ }
+#endif
+ balance_t& operator/=(const unsigned long val) {
+ return *this /= amount_t(val);
+ }
+ balance_t& operator/=(const long val) {
+ return *this /= amount_t(val);
+ }
+
+ /**
+ * Unary arithmetic operators. There are only a few unary methods
+ * support on balance:
+ *
+ * negate(), also unary minus (- x), returns a balance all of whose
+ * component amounts have been negated. In order words, it inverts
+ * the sign of all member amounts.
+ *
+ * abs() returns a balance where no component amount is negative.
+ *
+ * reduce() reduces the values in a balance to their most basic
+ * commodity forms, for amounts that utilize "scaling commodities".
+ * For example, a balance of 1h and 1m after reduction will be
+ * 3660s.
+ *
+ * unreduce(), if used with amounts that use "scaling commodities",
+ * yields the most compact form greater than 1.0 for each component
+ * amount. That is, a balance of 10m and 1799s will unreduce to
+ * 39.98m.
+ *
+ * value(optional<datetime_t>) returns the total historical value for
+ * a balance -- the default moment returns a value based on the most
+ * recently known price -- based on the price history of its
+ * component commodities. See amount_t::value for an example.
+ *
+ * Further, for the sake of efficiency and avoiding temporary
+ * objects, the following methods support "in-place" variants act on
+ * the balance itself and return a reference to the result
+ * (`*this'):
+ *
+ * in_place_negate()
+ * in_place_reduce()
+ * in_place_unreduce()
+ */
+ balance_t negate() const {
+ balance_t temp(*this);
+ temp.in_place_negate();
+ return temp;
+ }
+ virtual balance_t& in_place_negate() {
+ foreach (amounts_map::value_type& pair, amounts)
+ pair.second.in_place_negate();
+ return *this;
+ }
+ balance_t operator-() const {
+ return negate();
+ }
+
+ balance_t abs() const {
+ balance_t temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.abs();
+ return temp;
+ }
+
+ balance_t round() const {
+ balance_t temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.round();
+ return temp;
+ }
+ balance_t round(amount_t::precision_t prec) const {
+ balance_t temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.round(prec);
+ return temp;
+ }
+ balance_t unround() const {
+ balance_t temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.unround();
+ return temp;
+ }
+
+ balance_t reduce() const {
+ balance_t temp(*this);
+ temp.in_place_reduce();
+ return temp;
+ }
+ virtual balance_t& in_place_reduce() {
+ // A temporary must be used here because reduction may cause
+ // multiple component amounts to collapse to the same commodity.
+ balance_t temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.reduce();
+ return *this = temp;
+ }
+
+ balance_t unreduce() const {
+ balance_t temp(*this);
+ temp.in_place_unreduce();
+ return temp;
+ }
+ virtual balance_t& in_place_unreduce() {
+ // A temporary must be used here because unreduction may cause
+ // multiple component amounts to collapse to the same commodity.
+ balance_t temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.unreduce();
+ return *this = temp;
+ }
+
+ optional<balance_t> value(const optional<datetime_t>& moment = none) const;
+
+ /**
+ * Truth tests. An balance may be truth test in two ways:
+ *
+ * is_nonzero(), or operator bool, returns true if a balance's
+ * display value is not zero.
+ *
+ * is_zero() returns true if an balance's display value is zero.
+ * Thus, a balance containing $0.0001 is considered zero if the
+ * current display precision for dollars is two decimal places.
+ *
+ * is_realzero() returns true if an balance's actual value is zero.
+ * Thus, a balance containing $0.0001 is never considered realzero.
+ *
+ * is_empty() returns true if a balance has no amounts within it.
+ * This can occur after a balance has been default initialized, or
+ * if the exact amount it contains is subsequently subtracted from
+ * it.
+ */
+ operator bool() const {
+ foreach (const amounts_map::value_type& pair, amounts)
+ if (pair.second.is_nonzero())
+ return true;
+ return false;
+ }
+
+ bool is_zero() const {
+ if (is_empty())
+ return true;
+
+ foreach (const amounts_map::value_type& pair, amounts)
+ if (! pair.second.is_zero())
+ return false;
+ return true;
+ }
+
+ bool is_realzero() const {
+ if (is_empty())
+ return true;
+
+ foreach (const amounts_map::value_type& pair, amounts)
+ if (! pair.second.is_realzero())
+ return false;
+ return true;
+ }
+
+ bool is_empty() const {
+ return amounts.size() == 0;
+ }
+
+ /**
+ * Conversion methods. A balance can be converted to an amount, but
+ * only if contains a single component amount.
+ */
+ amount_t to_amount() const {
+ if (is_empty())
+ throw_(balance_error, "Cannot convert an empty balance to an amount");
+ else if (amounts.size() == 1)
+ return amounts.begin()->second;
+ else
+ throw_(balance_error,
+ "Cannot convert a balance with multiple commodities to an amount");
+ }
+
+ /**
+ * Commodity-related methods. Balances support two
+ * commodity-related methods:
+ *
+ * commodity_count() returns the number of different commodities
+ * stored in the balance.
+ *
+ * commodity_amount(optional<commodity_t>) returns an (optional)
+ * amount for the given commodity within the balance; if no
+ * commodity is specified, it returns the (optional) uncommoditized
+ * component of the balance. If no matching element can be found,
+ * boost::none is returned.
+ */
+ std::size_t commodity_count() const {
+ return amounts.size();
+ }
+
+ optional<amount_t>
+ commodity_amount(const optional<const commodity_t&>& commodity = none) const;
+
+ /**
+ * Annotated commodity methods. The amounts contained by a balance
+ * may use annotated commodities. The `strip_annotations' method
+ * will return a balance all of whose component amount have had
+ * their commodity annotations likewise stripped. See
+ * amount_t::strip_annotations for more details.
+ */
+ balance_t
+ strip_annotations(const bool keep_price = amount_t::keep_price,
+ const bool keep_date = amount_t::keep_date,
+ const bool keep_tag = amount_t::keep_tag) const;
+
+ /**
+ * Printing methods. A balance may be output to a stream using the
+ * `print' method. There is also a global operator<< defined which
+ * simply calls print for a balance on the given stream. There is
+ * one form of the print method, which takes two required arguments
+ * and one arguments with a default value:
+ *
+ * print(ostream, int first_width, int latter_width) prints a
+ * balance to the given output stream, using each commodity's
+ * default display characteristics. The first_width parameter
+ * specifies the width that should be used for printing amounts
+ * (since they are likely to vary in width). The latter_width, if
+ * specified, gives the width to be used for each line after the
+ * first. This is useful when printing in a column which falls at
+ * the right-hand side of the screen.
+ *
+ * In addition to the width constraints, balances will also print
+ * with commodities in alphabetized order, regardless of the
+ * relative amounts of those commodities. There is no option to
+ * change this behavior.
+ */
+ void print(std::ostream& out, const int first_width,
+ const int latter_width = -1) const;
+
+ /**
+ * Debugging methods. There are two methods defined to help with
+ * debugging:
+ *
+ * dump(ostream) dumps a balance to an output stream. There is
+ * little different from print(), it simply surrounds the display
+ * value with a marker, for example "BALANCE($1.00, DM 12.00)".
+ * This code is used by other dumping code elsewhere in Ledger.
+ *
+ * valid() returns true if the amounts within the balance are valid.
+ */
+ void dump(std::ostream& out) const {
+ out << "BALANCE(";
+ bool first = true;
+ foreach (const amounts_map::value_type& pair, amounts) {
+ if (first)
+ first = false;
+ else
+ out << ", ";
+ pair.second.print(out);
+ }
+ out << ")";
+ }
+
+ virtual bool valid() const {
+ foreach (const amounts_map::value_type& pair, amounts)
+ if (! pair.second.valid())
+ return false;
+ return true;
+ }
+};
+
+inline std::ostream& operator<<(std::ostream& out, const balance_t& bal) {
+ bal.print(out, 12);
+ return out;
+}
+
+} // namespace ledger
+
+#endif // _BALANCE_H
diff --git a/src/balpair.h b/src/balpair.h
new file mode 100644
index 00000000..62da0810
--- /dev/null
+++ b/src/balpair.h
@@ -0,0 +1,357 @@
+/*
+ * Copyright (c) 2003-2008, 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 balpair.h
+ * @author John Wiegley
+ * @date Sun May 20 19:11:58 2007
+ *
+ * @brief Provides an abstraction around balance_t for tracking costs.
+ *
+ * When a transaction's amount is added to a balance, only the "value"
+ * of the amount is added -- not the associated cost of the
+ * transaction. To provide for this, the balance_pair_t type allows
+ * for adding amounts and costs simultaneously to a single balance.
+ * Both are tracked, and any time either the total amount balance or
+ * the total cost balance may be extracted.
+ *
+ * Note: By default, all balance-like operations operate on the amount
+ * balance, and not the cost. Also, the cost is entirely optional, in
+ * which case a balance_pair_t may be used as if it were a balance_t,
+ * from which is it derived.
+ */
+#ifndef _BALPAIR_H
+#define _BARPAIR_H
+
+#include "balance.h"
+
+namespace ledger {
+
+class balance_pair_t
+ : public balance_t,
+ public equality_comparable<balance_pair_t,
+ equality_comparable<balance_pair_t, balance_t,
+ equality_comparable<balance_pair_t, amount_t,
+ equality_comparable<balance_pair_t, double,
+ equality_comparable<balance_pair_t, unsigned long,
+ equality_comparable<balance_pair_t, long,
+ additive<balance_pair_t,
+ additive<balance_pair_t, balance_t,
+ additive<balance_pair_t, amount_t,
+ additive<balance_pair_t, double,
+ additive<balance_pair_t, unsigned long,
+ additive<balance_pair_t, long,
+ multiplicative<balance_pair_t, amount_t,
+ multiplicative<balance_pair_t, balance_t,
+ multiplicative<balance_pair_t, double,
+ multiplicative<balance_pair_t, unsigned long,
+ multiplicative<balance_pair_t, long> > > > > > > > > > > > > > > > >
+{
+ /**
+ * The `cost' member of a balance pair tracks the cost associated
+ * with each transaction amount that is added. This member is
+ * optional, and if not cost-bearing transactions are added, it will
+ * remain uninitialized.
+ */
+ optional<balance_t> cost;
+
+ friend class value_t;
+ friend class entry_base_t;
+
+public:
+ /**
+ * Constructors. balance_pair_t supports identical forms of construction
+ * to balance_t. See balance_t for more information.
+ */
+ balance_pair_t() {
+ TRACE_CTOR(balance_pair_t, "");
+ }
+ balance_pair_t(const balance_t& bal) : balance_t(bal) {
+ TRACE_CTOR(balance_pair_t, "const balance_t&");
+ }
+ balance_pair_t(const balance_t& bal,
+ const balance_t& cost_bal)
+ : balance_t(bal), cost(cost_bal) {
+ TRACE_CTOR(balance_pair_t, "const balance_t&, const balance_t&");
+ }
+ balance_pair_t(const amount_t& amt) : balance_t(amt) {
+ TRACE_CTOR(balance_pair_t, "const amount_t&");
+ }
+ balance_pair_t(const amount_t& amt, const amount_t& cost_amt)
+ : balance_t(amt), cost(cost_amt) {
+ TRACE_CTOR(balance_pair_t, "const amount_t&, const amount_t&");
+ }
+#ifdef HAVE_GDTOA
+ balance_pair_t(const double val) : balance_t(val) {
+ TRACE_CTOR(balance_pair_t, "const double");
+ }
+#endif
+ balance_pair_t(const unsigned long val) : balance_t(val) {
+ TRACE_CTOR(balance_pair_t, "const unsigned long");
+ }
+ balance_pair_t(const long val) : balance_t(val) {
+ TRACE_CTOR(balance_pair_t, "const long");
+ }
+
+ explicit balance_pair_t(const string& val) : balance_t(val) {
+ TRACE_CTOR(balance_pair_t, "const string&");
+ }
+ explicit balance_pair_t(const char * val) : balance_t(val) {
+ TRACE_CTOR(balance_pair_t, "const char *");
+ }
+
+ /**
+ * Destructor.
+ */
+ virtual ~balance_pair_t() {
+ TRACE_DTOR(balance_pair_t);
+ }
+
+ /**
+ * Assignment and copy operators. A balance pair may be assigned or
+ * copied, and assigned or copied from a balance.
+ */
+ balance_pair_t(const balance_pair_t& bal_pair)
+ : balance_t(bal_pair), cost(bal_pair.cost) {
+ TRACE_CTOR(balance_pair_t, "copy");
+ }
+
+ balance_pair_t& operator=(const balance_pair_t& bal_pair) {
+ if (this != &bal_pair) {
+ balance_t::operator=(bal_pair.quantity());
+ cost = bal_pair.cost;
+ }
+ return *this;
+ }
+ balance_pair_t& operator=(const balance_t& bal) {
+ balance_t::operator=(bal);
+ return *this;
+ }
+ balance_pair_t& operator=(const amount_t& amt) {
+ balance_t::operator=(amt);
+ return *this;
+ }
+
+ balance_t& operator=(const string& str) {
+ return *this = balance_t(str);
+ }
+ balance_t& operator=(const char * str) {
+ return *this = balance_t(str);
+ }
+
+ /**
+ * Binary arithmetic operators. Balances support addition and
+ * subtraction of other balance pairs, balances or amounts, but
+ * multiplication and division are restricted to uncommoditized
+ * amounts only.
+ *
+ * There is also an additional additive method called `add' which
+ * allows for adding an amount and an associated cost
+ * simultaneously. The signature is:
+ * add(amount_t amount, optional<amount_t> cost)
+ */
+ balance_pair_t& operator+=(const balance_pair_t& bal_pair) {
+ balance_t::operator+=(bal_pair);
+ if (bal_pair.cost) {
+ if (! cost)
+ cost = quantity();
+ *cost += *bal_pair.cost;
+ }
+ return *this;
+ }
+ balance_pair_t& operator-=(const balance_pair_t& bal_pair) {
+ balance_t::operator+=(bal_pair);
+ if (bal_pair.cost) {
+ if (! cost)
+ cost = quantity();
+ *cost += *bal_pair.cost;
+ }
+ return *this;
+ }
+
+ virtual balance_pair_t& operator*=(const amount_t& amt) {
+ balance_t::operator*=(amt);
+ if (cost)
+ *cost *= amt;
+ return *this;
+ }
+
+ virtual balance_pair_t& operator/=(const amount_t& amt) {
+ balance_t::operator/=(amt);
+ if (cost)
+ *cost /= amt;
+ return *this;
+ }
+
+ balance_pair_t& add(const amount_t& amt,
+ const optional<amount_t>& a_cost = none) {
+ if (a_cost && ! cost)
+ cost = quantity();
+
+ *this += amt;
+
+ if (cost)
+ *cost += a_cost ? *a_cost : amt;
+
+ return *this;
+ }
+
+ /**
+ * Unary arithmetic operators. There are only a few unary methods
+ * supported for balance pairs (otherwise, the operators inherited
+ * from balance_t are used):
+ *
+ * abs() returns the absolute value of both the quantity and the
+ * cost of a balance pair.
+ *
+ * in_place_negate() negates all the amounts in both the quantity
+ * and the cost.
+ *
+ * in_place_reduce() reduces all the amounts in both the quantity
+ * and the cost.
+ *
+ * in_place_unreduce() unreduces all the amounts in both the
+ * quantity and the cost.
+ *
+ * quantity() returns the balance part of a balance. It is the same
+ * as doing a downcast<balance_t>(balance_pair).
+ */
+ balance_pair_t abs() const {
+ balance_t temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.abs();
+
+ if (cost) {
+ balance_t cost_temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ cost_temp += pair.second.abs();
+ return balance_pair_t(temp, cost_temp);
+ }
+ return temp;
+ }
+
+ virtual balance_t& in_place_negate() {
+ balance_t::in_place_negate();
+ if (cost)
+ cost->in_place_negate();
+ return *this;
+ }
+
+ virtual balance_t& in_place_reduce() {
+ // A temporary must be used here because reduction may cause
+ // multiple component amounts to collapse to the same commodity.
+ balance_t temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.reduce();
+
+ if (cost) {
+ balance_t cost_temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ cost_temp += pair.second.reduce();
+ return *this = balance_pair_t(temp, cost_temp);
+ }
+ return *this = temp;
+ }
+
+ virtual balance_t& in_place_unreduce() {
+ // A temporary must be used here because unreduction may cause
+ // multiple component amounts to collapse to the same commodity.
+ balance_t temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.unreduce();
+
+ if (cost) {
+ balance_t cost_temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ cost_temp += pair.second.unreduce();
+ return *this = balance_pair_t(temp, cost_temp);
+ }
+ return *this = temp;
+ }
+
+ balance_t& quantity() {
+ return *this;
+ }
+ const balance_t& quantity() const {
+ return *this;
+ }
+
+ /**
+ * Truth tests. An balance pair may be truth tested by comparison
+ * to another balance pair, or by using one of the inherited
+ * operators from balance_t.
+ */
+ bool operator==(const balance_pair_t& bal_pair) const {
+ if (quantity() != bal_pair.quantity())
+ return false;
+
+ if ((cost && ! bal_pair.cost) ||
+ (! cost && bal_pair.cost))
+ return false;
+
+ if (*cost != *bal_pair.cost)
+ return false;
+
+ return true;
+ }
+
+ bool operator==(const balance_t& bal) const {
+ return balance_t::operator==(bal);
+ }
+ bool operator==(const amount_t& amt) const {
+ return balance_t::operator==(amt);
+ }
+ template <typename T>
+ bool operator==(const T& val) const {
+ return balance_t::operator==(val);
+ }
+
+ /**
+ * Debugging methods. There is only one method specifically for
+ * balance pairs to help with debugging:
+ *
+ * valid() returns true if the balances within the balance pair are
+ * valid.
+ */
+ bool valid() const {
+ if (! balance_t::valid())
+ return false;
+
+ if (cost && ! cost->valid())
+ return false;
+
+ return true;
+ }
+};
+
+} // namespace ledger
+
+#endif // _BALPAIR_H
diff --git a/src/binary.cc b/src/binary.cc
new file mode 100644
index 00000000..1744bb5d
--- /dev/null
+++ b/src/binary.cc
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2003-2008, 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 "binary.h"
+
+namespace ledger {
+namespace binary {
+
+void read_bool(std::istream& in, bool& num)
+{
+ read_guard(in, 0x2005);
+ unsigned char val;
+ in.read(reinterpret_cast<char *>(&val), sizeof(val));
+ num = val == 1;
+ read_guard(in, 0x2006);
+}
+
+void read_bool(const char *& data, bool& num)
+{
+ read_guard(data, 0x2005);
+ const unsigned char val = *reinterpret_cast<const unsigned char *>(data);
+ data += sizeof(unsigned char);
+ num = val == 1;
+ read_guard(data, 0x2006);
+}
+
+void read_string(std::istream& in, string& str)
+{
+ read_guard(in, 0x3001);
+
+ unsigned char len;
+ read_number_nocheck(in, len);
+ if (len == 0xff) {
+ unsigned short slen;
+ read_number_nocheck(in, slen);
+ char * buf = new char[slen + 1];
+ in.read(buf, slen);
+ buf[slen] = '\0';
+ str = buf;
+ checked_array_delete(buf);
+ }
+ else if (len) {
+ char buf[256];
+ in.read(buf, len);
+ buf[len] = '\0';
+ str = buf;
+ } else {
+ str = "";
+ }
+
+ read_guard(in, 0x3002);
+}
+
+void read_string(const char *& data, string& str)
+{
+ read_guard(data, 0x3001);
+
+ unsigned char len;
+ read_number_nocheck(data, len);
+ if (len == 0xff) {
+ unsigned short slen;
+ read_number_nocheck(data, slen);
+ str = string(data, slen);
+ data += slen;
+ }
+ else if (len) {
+ str = string(data, len);
+ data += len;
+ }
+ else {
+ str = "";
+ }
+
+ read_guard(data, 0x3002);
+}
+
+void read_string(const char *& data, string * str)
+{
+ read_guard(data, 0x3001);
+
+ unsigned char len;
+ read_number_nocheck(data, len);
+ if (len == 0xff) {
+ unsigned short slen;
+ read_number_nocheck(data, slen);
+ new(str) string(data, slen);
+ data += slen;
+ }
+ else if (len) {
+ new(str) string(data, len);
+ data += len;
+ }
+ else {
+ new(str) string("");
+ }
+
+ read_guard(data, 0x3002);
+}
+
+void read_string(std::istream& in, optional<string>& str)
+{
+ if (read_bool(in)) {
+ string temp;
+ read_string(in, temp);
+ str = temp;
+ } else {
+ str = none;
+ }
+}
+
+void read_string(const char *& data, optional<string>& str)
+{
+ if (read_bool(data)) {
+ string temp;
+ read_string(data, temp);
+ str = temp;
+ } else {
+ str = none;
+ }
+}
+
+void write_bool(std::ostream& out, bool num)
+{
+ write_guard(out, 0x2005);
+ unsigned char val = num ? 1 : 0;
+ out.write(reinterpret_cast<char *>(&val), sizeof(val));
+ write_guard(out, 0x2006);
+}
+
+void write_string(std::ostream& out, const string& str)
+{
+ write_guard(out, 0x3001);
+
+ unsigned long len = str.length();
+ if (len > 255) {
+ assert(len < 65536);
+ write_number_nocheck<unsigned char>(out, 0xff);
+ write_number_nocheck<unsigned short>(out, len);
+ } else {
+ write_number_nocheck<unsigned char>(out, len);
+ }
+
+ if (len)
+ out.write(str.c_str(), len);
+
+ write_guard(out, 0x3002);
+}
+
+void write_string(std::ostream& out, const optional<string>& str)
+{
+ if (str) {
+ write_bool(out, true);
+ write_string(out, *str);
+ } else {
+ write_bool(out, false);
+ }
+}
+
+} // namespace binary
+} // namespace ledger
diff --git a/src/binary.h b/src/binary.h
new file mode 100644
index 00000000..8a2ef681
--- /dev/null
+++ b/src/binary.h
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) 2003-2008, 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 BINARY_H
+#define BINARY_H
+
+#include "utils.h"
+
+namespace ledger {
+namespace binary {
+
+template <typename T>
+inline void read_number_nocheck(std::istream& in, T& num) {
+ in.read(reinterpret_cast<char *>(&num), sizeof(num));
+}
+
+template <typename T>
+inline void read_number_nocheck(const char *& data, T& num) {
+ num = *reinterpret_cast<const T *>(data);
+ data += sizeof(T);
+}
+
+template <typename T>
+inline T read_number_nocheck(std::istream& in) {
+ T num;
+ read_number_nocheck(in, num);
+ return num;
+}
+
+template <typename T>
+inline T read_number_nocheck(const char *& data) {
+ T num;
+ read_number_nocheck(data, num);
+ return num;
+}
+
+#if DEBUG_LEVEL >= ALPHA
+#define read_guard(in, id) \
+ if (read_number_nocheck<unsigned short>(in) != id) \
+ assert(false);
+#else
+#define read_guard(in, id)
+#endif
+
+template <typename T>
+inline void read_number(std::istream& in, T& num) {
+ read_guard(in, 0x2003);
+ in.read(reinterpret_cast<char *>(&num), sizeof(num));
+ read_guard(in, 0x2004);
+}
+
+template <typename T>
+inline void read_number(const char *& data, T& num) {
+ read_guard(data, 0x2003);
+ num = *reinterpret_cast<const T *>(data);
+ data += sizeof(T);
+ read_guard(data, 0x2004);
+}
+
+template <typename T>
+inline T read_number(std::istream& in) {
+ T num;
+ read_number(in, num);
+ return num;
+}
+
+template <typename T>
+inline T read_number(const char *& data) {
+ T num;
+ read_number(data, num);
+ return num;
+}
+
+void read_bool(std::istream& in, bool& num);
+void read_bool(const char *& data, bool& num);
+
+inline bool read_bool(std::istream& in) {
+ bool num;
+ read_bool(in, num);
+ return num;
+}
+
+inline bool read_bool(const char *& data) {
+ bool num;
+ read_bool(data, num);
+ return num;
+}
+
+template <typename T>
+void read_long(std::istream& in, T& num)
+{
+ read_guard(in, 0x2001);
+
+ unsigned char len;
+ read_number_nocheck(in, len);
+
+ num = 0;
+ unsigned char temp;
+ if (len > 3) {
+ read_number_nocheck(in, temp);
+ num |= static_cast<unsigned long>(temp) << 24;
+ }
+ if (len > 2) {
+ read_number_nocheck(in, temp);
+ num |= static_cast<unsigned long>(temp) << 16;
+ }
+ if (len > 1) {
+ read_number_nocheck(in, temp);
+ num |= static_cast<unsigned long>(temp) << 8;
+ }
+
+ read_number_nocheck(in, temp);
+ num |= static_cast<unsigned long>(temp);
+
+ read_guard(in, 0x2002);
+}
+
+template <typename T>
+void read_long(const char *& data, T& num)
+{
+ read_guard(data, 0x2001);
+
+ unsigned char len;
+ read_number_nocheck(data, len);
+
+ num = 0;
+ unsigned char temp;
+ if (len > 3) {
+ read_number_nocheck(data, temp);
+ num |= static_cast<unsigned long>(temp) << 24;
+ }
+ if (len > 2) {
+ read_number_nocheck(data, temp);
+ num |= static_cast<unsigned long>(temp) << 16;
+ }
+ if (len > 1) {
+ read_number_nocheck(data, temp);
+ num |= static_cast<unsigned long>(temp) << 8;
+ }
+
+ read_number_nocheck(data, temp);
+ num |= static_cast<unsigned long>(temp);
+
+ read_guard(data, 0x2002);
+}
+
+template <typename T>
+inline T read_long(std::istream& in) {
+ T num;
+ read_long(in, num);
+ return num;
+}
+
+template <typename T>
+inline T read_long(const char *& data) {
+ T num;
+ read_long(data, num);
+ return num;
+}
+
+void read_string(std::istream& in, string& str);
+void read_string(const char *& data, string& str);
+void read_string(const char *& data, string * str);
+
+inline string read_string(std::istream& in) {
+ string temp;
+ read_string(in, temp);
+ return temp;
+}
+
+inline string read_string(const char *& data) {
+ string temp;
+ read_string(data, temp);
+ return temp;
+}
+
+void read_string(std::istream& in, optional<string>& str);
+void read_string(const char *& data, optional<string>& str);
+
+
+template <typename T>
+inline void write_number_nocheck(std::ostream& out, T num) {
+ out.write(reinterpret_cast<char *>(&num), sizeof(num));
+}
+
+#if DEBUG_LEVEL >= ALPHA
+#define write_guard(out, id) \
+ write_number_nocheck<unsigned short>(out, id)
+#else
+#define write_guard(in, id)
+#endif
+
+template <typename T>
+inline void write_number(std::ostream& out, T num) {
+ write_guard(out, 0x2003);
+ out.write(reinterpret_cast<char *>(&num), sizeof(num));
+ write_guard(out, 0x2004);
+}
+
+void write_bool(std::ostream& out, bool num);
+
+template <typename T>
+void write_long(std::ostream& out, T num)
+{
+ write_guard(out, 0x2001);
+
+ unsigned char len = 4;
+ if (static_cast<unsigned long>(num) < 0x00000100UL)
+ len = 1;
+ else if (static_cast<unsigned long>(num) < 0x00010000UL)
+ len = 2;
+ else if (static_cast<unsigned long>(num) < 0x01000000UL)
+ len = 3;
+ write_number_nocheck<unsigned char>(out, len);
+
+ unsigned char temp;
+ if (len > 3) {
+ temp = (static_cast<unsigned long>(num) & 0xFF000000UL) >> 24;
+ write_number_nocheck(out, temp);
+ }
+ if (len > 2) {
+ temp = (static_cast<unsigned long>(num) & 0x00FF0000UL) >> 16;
+ write_number_nocheck(out, temp);
+ }
+ if (len > 1) {
+ temp = (static_cast<unsigned long>(num) & 0x0000FF00UL) >> 8;
+ write_number_nocheck(out, temp);
+ }
+
+ temp = (static_cast<unsigned long>(num) & 0x000000FFUL);
+ write_number_nocheck(out, temp);
+
+ write_guard(out, 0x2002);
+}
+
+void write_string(std::ostream& out, const string& str);
+void write_string(std::ostream& out, const optional<string>& str);
+
+} // namespace binary
+} // namespace ledger
+
+#endif // BINARY_H
diff --git a/src/cache.cc b/src/cache.cc
new file mode 100644
index 00000000..d9ee7549
--- /dev/null
+++ b/src/cache.cc
@@ -0,0 +1,871 @@
+/*
+ * Copyright (c) 2003-2008, 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 "cache.h"
+#include "binary.h"
+
+namespace ledger {
+
+using namespace binary;
+
+#if 0
+void read_xact(const char *& data, xact_t * xact)
+{
+ read_number(data, xact->_date);
+ read_number(data, xact->_date_eff);
+ xact->account = accounts[read_long<account_t::ident_t>(data) - 1];
+
+ unsigned char flag = read_number<unsigned char>(data);
+ if (flag == 0) {
+ xact->amount.read(data);
+ }
+ else if (flag == 1) {
+ xact->amount.read(data);
+ xact->amount_expr = expr_t();
+ xact->amount_expr->set_text(read_string(data));
+ }
+ else {
+ xact->amount_expr = expr_t();
+ xact->amount_expr->read(data);
+ }
+
+ if (read_bool(data)) {
+ xact->cost = amount_t();
+ xact->cost->read(data);
+
+ xact->cost_expr = expr_t();
+ xact->cost_expr->read(data);
+ } else {
+ xact->cost = none;
+ }
+
+ read_number(data, xact->state);
+ xact->set_flags(read_number<xact_t::flags_t>(data));
+ xact->add_flags(XACT_BULK_ALLOC);
+ read_string(data, xact->note);
+
+ xact->beg_pos = read_long<unsigned long>(data);
+ read_long(data, xact->beg_line);
+ xact->end_pos = read_long<unsigned long>(data);
+ read_long(data, xact->end_line);
+
+ xact->data = NULL;
+
+ if (xact->amount_expr)
+ expr_t::compute_amount(xact->amount_expr.get(), xact->amount, xact);
+}
+
+void write_xact(std::ostream& out, xact_t * xact,
+ bool ignore_calculated)
+{
+ write_number(out, xact->_date);
+ write_number(out, xact->_date_eff);
+ write_long(out, xact->account->ident);
+
+ if (ignore_calculated && xact->has_flags(XACT_CALCULATED)) {
+ write_number<unsigned char>(out, 0);
+ amount_t().write(out);
+ }
+ else if (xact->amount_expr) {
+ write_number<unsigned char>(out, 2);
+ // jww (2008-07-30): Um, is this right?
+ xact->amount_expr->write(out);
+ }
+ else if (! xact->amount_expr->text().empty()) {
+ write_number<unsigned char>(out, 1);
+ xact->amount.write(out);
+ write_string(out, xact->amount_expr->text());
+ }
+ else {
+ write_number<unsigned char>(out, 0);
+ xact->amount.write(out);
+ }
+
+ if (xact->cost &&
+ (! (ignore_calculated && xact->has_flags(XACT_CALCULATED)))) {
+ write_bool(out, true);
+ xact->cost->write(out);
+ // jww (2008-07-30): What if there is no cost expression?
+ xact->cost_expr->write(out);
+ } else {
+ write_bool(out, false);
+ }
+
+ write_number(out, xact->state);
+ write_number(out, xact->flags());
+ write_string(out, xact->note);
+
+ write_long(out, xact->beg_pos);
+ write_long(out, xact->beg_line);
+ write_long(out, xact->end_pos);
+ write_long(out, xact->end_line);
+}
+
+void read_entry_base(const char *& data, entry_base_t * entry,
+ xact_t *& xact_pool, bool& finalize)
+{
+ read_long(data, entry->src_idx);
+ // jww (2008-07-31): Use istream_pos_type
+ entry->beg_pos = read_long<unsigned long>(data);
+ read_long(data, entry->beg_line);
+ entry->end_pos = read_long<unsigned long>(data);
+ read_long(data, entry->end_line);
+
+ bool ignore_calculated = read_bool(data);
+
+ for (std::size_t i = 0, count = read_long<std::size_t>(data);
+ i < count;
+ i++) {
+ new(xact_pool) xact_t;
+ read_xact(data, xact_pool);
+ if (ignore_calculated && xact_pool->has_flags(XACT_CALCULATED))
+ finalize = true;
+ entry->add_xact(xact_pool++);
+ }
+}
+
+void write_entry_base(std::ostream& out, entry_base_t * entry)
+{
+ write_long(out, entry->src_idx);
+ write_long(out, entry->beg_pos);
+ write_long(out, entry->beg_line);
+ write_long(out, entry->end_pos);
+ write_long(out, entry->end_line);
+
+ bool ignore_calculated = false;
+ foreach (transaction_t * xact, entry->xacts)
+ if (xact->amount_expr) {
+ ignore_calculated = true;
+ break;
+ }
+
+ write_bool(out, ignore_calculated);
+
+ write_long(out, entry->xacts.size());
+ foreach (transaction_t * xact, entry->xacts)
+ write_xact(out, xact, ignore_calculated);
+}
+
+void read_entry(const char *& data, entry_t * entry,
+ xact_t *& xact_pool, bool& finalize)
+{
+ read_entry_base(data, entry, xact_pool, finalize);
+ read_number(data, entry->_date);
+ read_number(data, entry->_date_eff);
+ read_string(data, entry->code);
+ read_string(data, entry->payee);
+}
+
+void write_entry(std::ostream& out, entry_t * entry)
+{
+ write_entry_base(out, entry);
+ write_number(out, entry->_date);
+ write_number(out, entry->_date_eff);
+ write_string(out, entry->code);
+ write_string(out, entry->payee);
+}
+
+void read_auto_entry(const char *& data, auto_entry_t * entry,
+ xact_t *& xact_pool)
+{
+ bool ignore;
+ read_entry_base(data, entry, xact_pool, ignore);
+
+ expr_t expr;
+ expr.read(data);
+ entry->predicate = item_predicate<xact_t>(expr);
+}
+
+void write_auto_entry(std::ostream& out, auto_entry_t * entry)
+{
+ write_entry_base(out, entry);
+ entry->predicate.predicate.write(out);
+}
+
+void read_period_entry(const char *& data, period_entry_t * entry,
+ xact_t *& xact_pool, bool& finalize)
+{
+ read_entry_base(data, entry, xact_pool, finalize);
+ read_string(data, &entry->period_string);
+ std::istringstream stream(entry->period_string);
+ entry->period.parse(stream);
+}
+
+void write_period_entry(std::ostream& out, period_entry_t * entry)
+{
+ write_entry_base(out, entry);
+ write_string(out, entry->period_string);
+}
+
+commodity_t::base_t * read_commodity_base(const char *& data)
+{
+ string str;
+
+ read_string(data, str);
+
+ std::auto_ptr<commodity_t::base_t> commodity(new commodity_t::base_t(str));
+
+ read_string(data, str);
+ if (! str.empty())
+ commodity->name = str;
+
+ read_string(data, str);
+ if (! str.empty())
+ commodity->note = str;
+
+ read_number(data, commodity->precision);
+ unsigned long flags;
+ read_number(data, flags);
+ commodity->set_flags(flags);
+
+ return commodity.release();
+}
+
+void write_commodity_base(std::ostream& out, commodity_t::base_t * commodity)
+{
+ // jww (2008-04-22): Not using this anymore?
+ //commodity->ident = ++base_commodity_index;
+
+ write_string(out, commodity->symbol);
+ // jww (2008-04-22): What to do with optional members?
+ write_string(out, *commodity->name);
+ write_string(out, *commodity->note);
+ write_number(out, commodity->precision);
+ write_number(out, commodity->flags());
+}
+
+void read_commodity_base_extra(const char *& data,
+ commodity_t::ident_t ident)
+{
+ commodity_t::base_t * commodity = base_commodities[ident];
+
+ bool read_history = false;
+ // jww (2008-07-31): create a function read_size which does
+ // read_long<std::size_t>. Don't use read_number<std::size_t>, but it
+ // wastes too much space.
+ for (std::size_t i = 0, count = read_long<std::size_t>(data);
+ i < count;
+ i++) {
+ datetime_t when;
+ read_number(data, when);
+ amount_t amt;
+ amt.read(data);
+
+ // Upon insertion, amt will be copied, which will cause the amount to be
+ // duplicated (and thus not lost when the journal's item_pool is deleted).
+ if (! commodity->history)
+ commodity->history = commodity_t::history_t();
+ commodity->history->prices.insert(commodity_t::base_t::history_pair(when, amt));
+
+ read_history = true;
+ }
+ if (read_history)
+ read_number(data, commodity->history->last_lookup);
+
+ if (read_bool(data)) {
+ amount_t amt;
+ amt.read(data);
+ commodity->smaller = amount_t(amt);
+ }
+
+ if (read_bool(data)) {
+ amount_t amt;
+ amt.read(data);
+ commodity->larger = amount_t(amt);
+ }
+}
+
+void write_commodity_base_extra(std::ostream& out,
+ commodity_t::base_t * commodity)
+{
+#if 0
+ // jww (2008-04-22): What did bogus_time used to do?
+ if (commodity->history && commodity->history->bogus_time)
+ commodity->remove_price(commodity->history->bogus_time);
+#endif
+
+ if (! commodity->history) {
+ write_long<unsigned long>(out, 0);
+ } else {
+ write_long<unsigned long>(out, commodity->history->prices.size());
+ foreach (commodity_t::history_map::value_type& pair,
+ commodity->history->prices) {
+ write_number(out, pair.first);
+ pair.second.write(out);
+ }
+ write_number(out, commodity->history->last_lookup);
+ }
+
+ if (commodity->smaller) {
+ write_bool(out, true);
+ commodity->smaller->write(out);
+ } else {
+ write_bool(out, false);
+ }
+
+ if (commodity->larger) {
+ write_bool(out, true);
+ commodity->larger->write(out);
+ } else {
+ write_bool(out, false);
+ }
+}
+
+commodity_t * read_commodity(const char *& data)
+{
+ commodity_t::base_t * base =
+ base_commodities[read_long<commodity_t::ident_t>(data) - 1];
+
+ commodity_t * commodity =
+ new commodity_t(amount_t::current_pool,
+ shared_ptr<commodity_t::base_t>(base));
+
+ *commodities_next++ = commodity;
+
+ string str;
+ read_string(data, str);
+ if (! str.empty())
+ commodity->qualified_symbol = str;
+ commodity->annotated = false;
+
+ return commodity;
+}
+
+void write_commodity(std::ostream& out, commodity_t * commodity)
+{
+ commodity->ident = ++commodity_index;
+
+ // jww (2008-04-22): Is this used anymore?
+ //write_long(out, commodity->base->ident);
+ // jww (2008-04-22): Optional!
+ write_string(out, *commodity->qualified_symbol);
+}
+
+commodity_t * read_commodity_annotated(const char *& data)
+{
+ commodity_t * commodity =
+ commodities[read_long<commodity_t::ident_t>(data) - 1];
+
+ annotation_t details;
+
+ string str;
+ read_string(data, str);
+
+ // This read-and-then-assign causes a new amount to be allocated which does
+ // not live within the bulk allocation pool, since that pool will be deleted
+ // *before* the commodities are destroyed.
+ amount_t amt;
+ amt.read(data);
+ details.price = amt;
+
+#if 0
+ // jww (2008-04-22): These are optional members!
+ read_number(data, details.date);
+ read_string(data, details.tag);
+#endif
+
+ annotated_commodity_t * ann_comm =
+ new annotated_commodity_t(commodity, details);
+ *commodities_next++ = ann_comm;
+
+ if (! str.empty())
+ ann_comm->qualified_symbol = str;
+
+ return ann_comm;
+}
+
+void write_commodity_annotated(std::ostream& out,
+ commodity_t * commodity)
+{
+ commodity->ident = ++commodity_index;
+
+ // jww (2008-04-22): No longer needed?
+ //write_long(out, commodity->base->ident);
+ // jww (2008-04-22): Optional!
+ write_string(out, *commodity->qualified_symbol);
+
+ annotated_commodity_t * ann_comm =
+ static_cast<annotated_commodity_t *>(commodity);
+
+ // jww (2008-04-22): No longer needed?
+ //write_long(out, ann_comm->base->ident);
+ // jww (2008-04-22): Make a write_annotation_details function; and optional!
+ ann_comm->details.price->write(out);
+ ann_comm->details.date->write(out);
+ ann_comm->details.tag->write(out);
+}
+
+inline
+account_t * read_account(const char *& data, account_t * master = NULL)
+{
+ account_t * acct = new account_t(NULL);
+
+ accounts[account_ident++] = acct;
+
+ account_t::ident_t id;
+ read_long(data, id); // parent id
+ if (id == 0xffffffff)
+ acct->parent = NULL;
+ else
+ acct->parent = accounts[id - 1];
+
+ read_string(data, acct->name);
+ read_string(data, acct->note);
+ read_number(data, acct->depth);
+
+ // If all of the subaccounts will be added to a different master
+ // account, throw away what we've learned about the recorded
+ // journal's own master account.
+
+ if (master && acct != master) {
+ checked_delete(acct);
+ acct = master;
+ }
+
+ for (std::size_t i = 0, count = read_long<std::size_t>(data);
+ i < count;
+ i++) {
+ account_t * child = read_account(data);
+ child->parent = acct;
+ assert(acct != child);
+ acct->add_account(child);
+ }
+
+ return acct;
+}
+
+namespace {
+ inline account_t::ident_t count_accounts(account_t * account)
+ {
+ account_t::ident_t count = 1;
+
+ foreach (accounts_map::value_type& pair, account->accounts)
+ count += count_accounts(pair.second);
+
+ return count;
+ }
+}
+
+void write_account(std::ostream& out, account_t * account)
+{
+ account->ident = ++account_ident;
+
+ if (account->parent)
+ write_long(out, account->parent->ident);
+ else
+ write_long<account_t::ident_t>(out, 0xffffffff);
+
+ write_string(out, account->name);
+ write_string(out, account->note);
+ write_number(out, account->depth);
+
+ write_number<std::size_t>(out, account->accounts.size());
+
+ foreach (accounts_map::value_type& pair, account->accounts)
+ write_account(out, pair.second);
+}
+
+unsigned int read_journal(std::istream& in,
+ const path& file,
+ journal_t& journal,
+ account_t * master)
+{
+ using namespace binary;
+
+ // Read in the files that participated in this journal, so that they
+ // can be checked for changes on reading.
+
+ if (! file.empty()) {
+ for (unsigned short i = 0, count = read_number<unsigned short>(in);
+ i < count;
+ i++) {
+ path pathname = read_string(in);
+ std::time_t old_mtime;
+ read_number(in, old_mtime);
+ struct stat info;
+ // jww (2008-04-22): can this be done differently now?
+ stat(pathname.string().c_str(), &info);
+ if (std::difftime(info.st_mtime, old_mtime) > 0)
+ return 0;
+
+ sources.push_back(pathname);
+ }
+
+ // Make sure that the cache uses the same price database,
+ // otherwise it means that LEDGER_PRICE_DB has been changed, and
+ // we should ignore this cache file.
+ if (read_bool(in)) {
+ string pathname;
+ read_string(in, pathname);
+ if (! price_db ||
+ price_db->string() != std::string(pathname))
+ return 0;
+ }
+ }
+
+ // jww (2008-07-31): bind master to session.master
+
+ if (read_bool(data))
+ basket = accounts[read_long<account_t::ident_t>(data) - 1];
+
+ // Read in the entries and xacts
+
+ for (std::size_t i = 0; i < count; i++) {
+ new(entry_pool) entry_t;
+ bool finalize = false;
+ read_entry(data, entry_pool, xact_pool, finalize);
+ entry_pool->journal = &journal;
+ if (finalize && ! entry_pool->finalize())
+ continue;
+ entries.push_back(entry_pool++);
+ }
+
+ for (std::size_t i = 0; i < auto_count; i++) {
+ auto_entry_t * auto_entry = new auto_entry_t;
+ read_auto_entry(data, auto_entry, xact_pool);
+ auto_entry->journal = &journal;
+ auto_entries.push_back(auto_entry);
+ }
+
+ for (std::size_t i = 0; i < period_count; i++) {
+ period_entry_t * period_entry = new period_entry_t;
+ bool finalize = false;
+ read_period_entry(data, period_entry, xact_pool, finalize);
+ period_entry->journal = &journal;
+ if (finalize && ! period_entry->finalize())
+ continue;
+ period_entries.push_back(period_entry);
+ }
+
+ VERIFY(journal.valid());
+
+ return count;
+}
+
+std::pair<std::size_t, std::size_t>
+write_journal(std::ostream& out, const journal_t& journal)
+{
+ using namespace binary;
+
+ // Write out the files that participated in this journal, so that
+ // they can be checked for changes on reading.
+
+ if (sources.empty()) {
+ write_number<unsigned short>(out, 0);
+ } else {
+ write_number<unsigned short>(out, sources.size());
+ foreach (const path& path, sources) {
+ write_string(out, path.string());
+ struct stat info;
+ stat(path.string().c_str(), &info);
+ write_number(out, std::time_t(info.st_mtime));
+ }
+
+ // Write out the price database that relates to this data file, so
+ // that if it ever changes the cache can be invalidated.
+ if (price_db) {
+ write_bool(out, true);
+ write_string(out, price_db->string());
+ } else {
+ write_bool(out, false);
+ }
+ }
+
+ // Write out the basket accounts
+
+ if (basket) {
+ write_bool(out, true);
+ write_long(out, basket->ident);
+ } else {
+ write_bool(out, false);
+ }
+
+ // Write out the entries and xacts
+
+ std::size_t this_entry_count = 0;
+ std::size_t this_xact_count = 0;
+
+ foreach (entry_t * entry, entries) {
+ write_entry(out, entry);
+
+ this_entry_count++;
+ this_xact_count += entry->xacts.size();
+ }
+
+ foreach (auto_entry_t * entry, auto_entries) {
+ write_auto_entry(out, entry);
+
+ this_entry_count++;
+ this_xact_count += entry->xacts.size();
+ }
+
+ foreach (period_entry_t * entry, period_entries) {
+ write_period_entry(out, entry);
+
+ this_entry_count++;
+ this_xact_count += entry->xacts.size();
+ }
+
+ return std::pair<std::size_t, std::size_t>(this_entry_count,
+ this_xact_count);
+}
+
+std::size_t read_session(std::istream& in,
+ const path& file,
+ session_t& session)
+{
+ using namespace binary;
+
+ // Read all of the data in at once, so that we're just dealing with
+ // a big data buffer.
+
+ std::size_t data_size = read_number<std::size_t>(in);
+
+ scoped_array<char> data_pool(new char[data_size]);
+
+ in.read(data_pool, data_size);
+
+ const char * data = data_pool.get();
+
+ // Read in the accounts
+
+ accounts.resize(read_number<std::size_t>(data));
+ account_ident = 0;
+
+ if (session.master)
+ checked_delete(session.master);
+ session.master = read_account(data);
+
+ // Allocate the memory needed for the entries, xacts and bigints in one
+ // large block, which is then chopped up and custom constructed as
+ // necessary.
+
+ entry_count = read_number<std::size_t>(data);
+ auto_entry_count = read_number<std::size_t>(data);
+ period_entry_count = read_number<std::size_t>(data);
+ xact_count = read_number<std::size_t>(data);
+ bigints_count = read_number<std::size_t>(data);
+
+#define ENTRIES_SIZE (sizeof(entry_t) * entry_count)
+#define XACTS_SIZE (sizeof(xact_t) * xact_count)
+#define BIGINTS_SIZE (amount_t::sizeof_bigint_t() * bigints_count)
+
+#define ENTRIES_OFFSET 0
+#define XACTS_OFFSET ENTRIES_SIZE
+#define BIGINTS_OFFSET (ENTRIES_SIZE + XACTS_SIZE)
+
+ item_pool.reset(new char[ENTRIES_SIZE + XACTS_SIZE + BIGINTS_SIZE]);
+
+ entry_pool = reinterpret_cast<entry_t *>(item_pool.get() + ENTRIES_OFFSET);
+ xact_pool = reinterpret_cast<xact_t *>(item_pool.get() + XACTS_OFFSET);
+ bigints = item_pool.get() + BIGINTS_OFFSET;
+ bigints_next = bigints;
+ bigint_ident = 0;
+
+#if 0
+ // Read in the base commodities and the derived commodities
+
+ base_commodity_count = read_number<std::size_t>(data);
+ base_commodities.resize(base_commodity_count);
+
+ for (std::size_t i = 0; i < base_commodity_count; i++) {
+ commodity_t::base_t * base = read_commodity_base(data);
+ session.commodity_pool->commodities.push_back(base);
+
+ std::pair<base_commodities_map::iterator, bool> result =
+ commodity_base_t::commodities.insert
+ (base_commodities_pair(commodity->symbol, commodity));
+ if (! result.second) {
+ base_commodities_map::iterator c =
+ commodity_t::base_t::commodities.find(commodity->symbol);
+
+ // It's possible the user might have used a commodity in a value
+ // expression passed to an option, we'll just override the flags, but
+ // keep the commodity pointer intact.
+ if (c == commodity_t::base_t::commodities.end())
+ throw_(cache_error, "Failed to read base commodity from cache: "
+ << commodity->symbol);
+
+ (*c).second->name = commodity->name;
+ (*c).second->note = commodity->note;
+ (*c).second->precision = commodity->precision;
+ (*c).second->flags = commodity->flags;
+
+ if ((*c).second->smaller)
+ checked_delete((*c).second->smaller);
+ (*c).second->smaller = commodity->smaller;
+ if ((*c).second->larger)
+ checked_delete((*c).second->larger);
+ (*c).second->larger = commodity->larger;
+
+ *(base_commodities_next - 1) = (*c).second;
+
+ checked_delete(commodity);
+ }
+ }
+
+ commodity_count = read_number<std::size_t>(data);
+ commodities.resize(commodity_count);
+
+ for (std::size_t i = 0; i < commodity_count; i++) {
+ commodity_t * commodity;
+ string mapping_key;
+
+ if (! read_bool(data)) {
+ commodity = read_commodity(data);
+ mapping_key = commodity->base->symbol;
+ } else {
+ read_string(data, mapping_key);
+ commodity = read_commodity_annotated(data);
+ }
+
+ session.commodity_pool->commodities.push_back(commodity);
+
+ if (! result.second) {
+ commodities_map::iterator c =
+ commodity_t::commodities.find(mapping_key);
+ if (c == commodity_t::commodities.end())
+ throw_(cache_error, "Failed to read commodity from cache: "
+ << commodity->symbol());
+
+ *(commodities_next - 1) = (*c).second;
+ checked_delete(commodity);
+ }
+ }
+
+ for (std::size_t i = 0; i < base_commodity_count; i++)
+ read_commodity_base_extra(data, i);
+
+ commodity_t::ident_t ident = read_number<commodity_t::ident_t>(data);
+ if (ident == 0xffffffff || ident == 0)
+ session.commodity_pool->default_commodity = NULL;
+ else
+ session.commodity_pool->default_commodity = commodities[ident - 1];
+#endif
+
+ // Clean up and return the number of entries read
+
+ accounts.clear();
+ commodities.clear();
+
+ VERIFY(session.valid());
+
+ return count;
+}
+
+void write_session(std::ostream& out, session_t& session)
+{
+ using namespace binary;
+
+ write_number_nocheck(out, binary_magic_number);
+ write_number_nocheck(out, format_version);
+
+ // This number gets patched at the end of the function
+ ostream_pos_type data_val = out.tellp();
+ write_number<std::size_t>(out, 0);
+
+ // Write out the accounts
+
+ write_number<std::size_t>(out, count_accounts(session.master));
+ write_account(out, session.master);
+
+ // Write out the number of entries, xacts, and amounts
+
+ write_number<std::size_t>(out, entries.size());
+ write_number<std::size_t>(out, auto_entries.size());
+ write_number<std::size_t>(out, period_entries.size());
+
+ // These two numbers get patched at the end of the function
+ ostream_pos_type xacts_val = out.tellp();
+ write_number<std::size_t>(out, 0);
+ ostream_pos_type bigints_val = out.tellp();
+ write_number<std::size_t>(out, 0);
+
+ bigint_ident = 0;
+
+#if 0
+ // Write out the commodities
+ // jww (2008-04-22): This whole section needs to be reworked
+
+ write_number<std::size_t>(out, session.commodity_pool->commodities.size());
+ write_number<std::size_t>(out, session.commodity_pool->commodities.size());
+
+ for (base_commodities_map::value_type pair, commodity_t::base_t::commodities)
+ write_commodity_base(out, pair.second);
+
+ write_number<commodity_t::ident_t>
+ (out, commodity_t::commodities.size());
+
+ for (commodities_map::value_type pair, commodity_t::commodities) {
+ if (! pair.second->annotated) {
+ write_bool(out, false);
+ write_commodity(out, pair.second);
+ }
+ }
+
+ for (commodities_map::value_type pair, commodity_t::commodities) {
+ if (pair.second->annotated) {
+ write_bool(out, true);
+ write_string(out, pair.first); // the mapping key
+ write_commodity_annotated(out, pair.second);
+ }
+ }
+
+ // Write out the history and smaller/larger convertible links after
+ // both the base and the main commodities have been written, since
+ // the amounts in both will refer to the mains.
+
+ for (base_commodities_map::const_iterator i =
+ commodity_t::base_t::commodities.begin();
+ i != commodity_t::base_t::commodities.end();
+ i++)
+ write_commodity_base_extra(out, (*i).second);
+
+ if (commodity_t::default_commodity)
+ write_number(out, commodity_t::default_commodity->ident);
+ else
+ write_number<commodity_t::ident_t>(out, 0xffffffff);
+#endif
+
+ // Back-patch several counts which were not known beforehand
+
+ out.seekp(data_val);
+ write_number<std::size_t>(out, (static_cast<std::size_t>(out.tellp()) -
+ static_cast<std::size_t>(data_val) -
+ sizeof(std::size_t)));
+ out.seekp(xacts_val);
+ write_number<std::size_t>(out, xact_count);
+ out.seekp(bigints_val);
+ write_number<std::size_t>(out, bigints_count);
+}
+#endif
+
+} // namespace ledger
diff --git a/src/cache.h b/src/cache.h
new file mode 100644
index 00000000..43123f9f
--- /dev/null
+++ b/src/cache.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2003-2008, 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 CACHE_H
+#define CACHE_H
+
+#include "utils.h"
+#include "session.h"
+#include "journal.h"
+#include "account.h"
+
+namespace ledger {
+
+DECLARE_EXCEPTION(cache_error, std::runtime_error);
+
+class binary_cache_t
+{
+ static const unsigned long binary_magic_number = 0xFFEED765;
+#if defined(DEBUG_ON)
+ static const unsigned long format_version = 0x0002060d;
+#else
+ static const unsigned long format_version = 0x0002060c;
+#endif
+
+ scoped_array<char> item_pool;
+
+ std::vector<account_t *> accounts;
+ account_t::ident_t account_ident;
+
+ entry_t * entry_pool; // points into item_pool
+ std::size_t entry_count;
+ std::size_t auto_entry_count;
+ std::size_t period_entry_count;
+
+ xact_t * xact_pool; // points into item_pool
+ std::size_t xact_count;
+
+#if 0
+ commodity_base_t ** base_commodities; // allocated
+ commodity_base_t ** base_commodities_next;
+ uint_fast32_t base_commodity_index;
+ std::size_t base_commodity_count;
+#endif
+
+ commodity_t ** commodities; // allocated
+ commodity_t ** commodities_next;
+ uint_fast32_t commodity_ident;
+ std::size_t commodity_count;
+
+ char * bigints; // points into item_pool
+ char * bigints_next;
+ uint_fast32_t bigints_index;
+ std::size_t bigints_count;
+
+ void read_xact(const char *& data, xact_t * xact);
+ void write_xact(std::ostream& out, xact_t * xact,
+ bool ignore_calculated);
+
+ void read_entry_base(const char *& data, entry_base_t * entry,
+ xact_t *& xact_pool, bool& finalize);
+ void write_entry_base(std::ostream& out, entry_base_t * entry);
+ void read_entry(const char *& data, entry_t * entry,
+ xact_t *& xact_pool, bool& finalize);
+ void write_entry(std::ostream& out, entry_t * entry);
+ void read_auto_entry(const char *& data, auto_entry_t * entry,
+ xact_t *& xact_pool);
+ void write_auto_entry(std::ostream& out, auto_entry_t * entry);
+ void read_period_entry(const char *& data, period_entry_t * entry,
+ xact_t *& xact_pool, bool& finalize);
+ void write_period_entry(std::ostream& out, period_entry_t * entry);
+
+#if 0
+ commodity_t::base_t * read_commodity_base(const char *& data);
+ void write_commodity_base(std::ostream& out, commodity_t::base_t * commodity);
+ void read_commodity_base_extra(const char *& data,
+ commodity_t::ident_t ident);
+ void write_commodity_base_extra(std::ostream& out,
+ commodity_t::base_t * commodity);
+#endif
+
+ commodity_t * read_commodity(const char *& data);
+ void write_commodity(std::ostream& out, commodity_t * commodity);
+ commodity_t * read_commodity_annotated(const char *& data);
+ void write_commodity_annotated(std::ostream& out,
+ commodity_t * commodity);
+
+ account_t * read_account(const char *& data, account_t * master = NULL);
+ void write_account(std::ostream& out);
+
+ std::size_t read_journal(std::istream& in,
+ const path& file,
+ journal_t& journal,
+ account_t * master);
+ void write_journal(std::ostream& out,
+ const journal_t& journal);
+
+public:
+ binary_cache_t()
+ : account_ident(0),
+#if 0
+ base_commodity_ident(0),
+#endif
+ commodity_ident(0)
+ {
+ }
+
+ std::size_t read_session(std::istream& in, const path& file);
+ void write_session(std::ostream& out, session_t& session);
+
+};
+
+} // namespace ledger
+
+#endif // CACHE_H
diff --git a/src/commodity.cc b/src/commodity.cc
new file mode 100644
index 00000000..a1c1e2c9
--- /dev/null
+++ b/src/commodity.cc
@@ -0,0 +1,667 @@
+/*
+ * Copyright (c) 2003-2008, 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 commodity.cc
+ * @author John Wiegley
+ * @date Thu Apr 26 15:19:46 2007
+ *
+ * @brief Types for dealing with commodities.
+ *
+ * This file defines member functions for flavors of commodity_t.
+ */
+
+#include "amount.h"
+
+namespace ledger {
+
+void commodity_t::add_price(const datetime_t& date,
+ const amount_t& price)
+{
+ if (! base->history)
+ base->history = history_t();
+
+ history_map::iterator i = base->history->prices.find(date);
+ if (i != base->history->prices.end()) {
+ (*i).second = price;
+ } else {
+ std::pair<history_map::iterator, bool> result
+ = base->history->prices.insert(history_map::value_type(date, price));
+ assert(result.second);
+ }
+}
+
+bool commodity_t::remove_price(const datetime_t& date)
+{
+ if (base->history) {
+ history_map::size_type n = base->history->prices.erase(date);
+ if (n > 0) {
+ if (base->history->prices.empty())
+ base->history.reset();
+ return true;
+ }
+ }
+ return false;
+}
+
+optional<amount_t> commodity_t::value(const optional<datetime_t>& moment)
+{
+ optional<datetime_t> age;
+ optional<amount_t> price;
+
+ if (base->history) {
+ assert(base->history->prices.size() > 0);
+
+ if (! moment) {
+ history_map::reverse_iterator r = base->history->prices.rbegin();
+ age = (*r).first;
+ price = (*r).second;
+ } else {
+ history_map::iterator i = base->history->prices.lower_bound(*moment);
+ if (i == base->history->prices.end()) {
+ history_map::reverse_iterator r = base->history->prices.rbegin();
+ age = (*r).first;
+ price = (*r).second;
+ } else {
+ age = (*i).first;
+ if (*moment != *age) {
+ if (i != base->history->prices.begin()) {
+ --i;
+ age = (*i).first;
+ price = (*i).second;
+ } else {
+ age = none;
+ }
+ } else {
+ price = (*i).second;
+ }
+ }
+ }
+ }
+
+ if (! has_flags(COMMODITY_STYLE_NOMARKET) && parent().get_quote) {
+ if (optional<amount_t> quote = parent().get_quote
+ (*this, age, moment,
+ (base->history && base->history->prices.size() > 0 ?
+ (*base->history->prices.rbegin()).first : optional<datetime_t>())))
+ return *quote;
+ }
+ return price;
+}
+
+amount_t commodity_t::exchange(const amount_t& amount,
+ amount_t& final_cost, // out
+ amount_t& basis_cost, // out
+ const optional<amount_t>& total_cost_,
+ const optional<amount_t>& per_unit_cost_,
+ const optional<datetime_t>& moment,
+ const optional<string>& tag)
+{
+ // (assert (or (and total-cost (not per-unit-cost))
+ // (and per-unit-cost (not total-cost))))
+
+ assert((total_cost_ && ! per_unit_cost_) || (per_unit_cost_ && ! total_cost_));
+
+ // (let* ((commodity (amount-commodity amount))
+ // (current-annotation
+ // (and (annotated-commodity-p commodity)
+ // (commodity-annotation commodity)))
+ // (base-commodity (if (annotated-commodity-p commodity)
+ // (get-referent commodity)
+ // commodity))
+ // (per-unit-cost (or per-unit-cost
+ // (divide total-cost amount)))
+ // (total-cost (or total-cost
+ // (multiply per-unit-cost amount))))
+
+ commodity_t& commodity(amount.commodity());
+
+ annotation_t * current_annotation = NULL;
+ if (commodity.annotated)
+ current_annotation = &as_annotated_commodity(commodity).details;
+
+ commodity_t& base_commodity
+ (current_annotation ?
+ as_annotated_commodity(commodity).referent() : commodity);
+
+ amount_t per_unit_cost(per_unit_cost_ ?
+ *per_unit_cost_ : *total_cost_ / amount);
+ final_cost = total_cost_ ? *total_cost_ : *per_unit_cost_ * amount;
+
+ // Add a price history entry for this conversion if we know when it took
+ // place
+
+ // (if (and moment (not (commodity-no-market-price-p base-commodity)))
+ // (add-price base-commodity per-unit-cost moment))
+
+ if (moment && ! commodity.has_flags(COMMODITY_STYLE_NOMARKET))
+ base_commodity.add_price(*moment, per_unit_cost);
+
+ // ;; returns: ANNOTATED-AMOUNT TOTAL-COST BASIS-COST
+ // (values (annotate-commodity
+ // amount
+ // (make-commodity-annotation :price per-unit-cost
+ // :date moment
+ // :tag tag))
+ // total-cost
+ // (if current-annotation
+ // (multiply (annotation-price current-annotation) amount)
+ // total-cost))))
+
+ if (current_annotation && current_annotation->price)
+ basis_cost = *current_annotation->price * amount;
+ else
+ basis_cost = final_cost;
+
+ amount_t ann_amount(amount);
+ ann_amount.annotate(annotation_t(per_unit_cost, moment->date(), tag));
+ return ann_amount;
+}
+
+commodity_t::operator bool() const
+{
+ return this != parent().null_commodity;
+}
+
+bool commodity_t::symbol_needs_quotes(const string& symbol)
+{
+ foreach (char ch, symbol)
+ if (std::isspace(ch) || std::isdigit(ch) || ch == '-' || ch == '.')
+ return true;
+
+ return false;
+}
+
+void commodity_t::parse_symbol(std::istream& in, string& symbol)
+{
+ // Invalid commodity characters:
+ // SPACE, TAB, NEWLINE, RETURN
+ // 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, 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,
+ /* 60 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* 70 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0,
+ /* 80 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* 90 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* a0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* b0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* c0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* d0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* e0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* f0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+
+ char buf[256];
+ char c = peek_next_nonws(in);
+ if (c == '"') {
+ in.get(c);
+ READ_INTO(in, buf, 255, c, c != '"');
+ if (c == '"')
+ in.get(c);
+ else
+ throw_(amount_error, "Quoted commodity symbol lacks closing quote");
+ } else {
+ READ_INTO(in, buf, 255, c, ! invalid_chars[static_cast<unsigned char>(c)]);
+ }
+ symbol = buf;
+}
+
+void commodity_t::parse_symbol(char *& p, string& symbol)
+{
+ if (*p == '"') {
+ char * q = std::strchr(p + 1, '"');
+ if (! q)
+ throw_(amount_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_(amount_error, "Failed to parse commodity");
+}
+
+bool commodity_t::valid() const
+{
+ if (symbol().empty() && this != parent().null_commodity) {
+ DEBUG("ledger.validate",
+ "commodity_t: symbol().empty() && this != null_commodity");
+ return false;
+ }
+
+ if (annotated && ! base) {
+ DEBUG("ledger.validate", "commodity_t: annotated && ! base");
+ return false;
+ }
+
+ if (precision() > 16) {
+ DEBUG("ledger.validate", "commodity_t: precision() > 16");
+ return false;
+ }
+
+ return true;
+}
+
+void annotation_t::parse(std::istream& in)
+{
+ do {
+ 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);
+ 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_PARSE_NO_MIGRATE);
+ temp.in_place_reduce();
+
+ // 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.round(); // 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 {
+ break;
+ }
+ } while (true);
+
+ DEBUG("amounts.commodities",
+ "Parsed commodity annotations: " << std::endl << *this);
+}
+
+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 bool _keep_price,
+ const bool _keep_date,
+ const bool _keep_tag)
+{
+ DEBUG("commodity.annotated.strip",
+ "Reducing commodity " << *this << std::endl
+ << " keep price " << _keep_price << " "
+ << " keep date " << _keep_date << " "
+ << " keep tag " << _keep_tag);
+
+ commodity_t * new_comm;
+
+ 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 annotation_t& info)
+{
+ if (info.price)
+ out << " {" << *info.price << '}';
+
+ if (info.date)
+ out << " [" << *info.date << ']';
+
+ if (info.tag)
+ out << " (" << *info.tag << ')';
+}
+
+bool compare_amount_commodities::operator()(const amount_t * left,
+ const amount_t * right) const
+{
+ commodity_t& leftcomm(left->commodity());
+ commodity_t& rightcomm(right->commodity());
+
+ int cmp = leftcomm.base_symbol().compare(rightcomm.base_symbol());
+ if (cmp != 0)
+ return cmp < 0;
+
+ if (! leftcomm.annotated) {
+ assert(rightcomm.annotated);
+ return true;
+ }
+ else if (! rightcomm.annotated) {
+ assert(leftcomm.annotated);
+ return false;
+ }
+ else {
+ annotated_commodity_t& aleftcomm(static_cast<annotated_commodity_t&>(leftcomm));
+ annotated_commodity_t& arightcomm(static_cast<annotated_commodity_t&>(rightcomm));
+
+ if (! aleftcomm.details.price && arightcomm.details.price)
+ return true;
+ if (aleftcomm.details.price && ! arightcomm.details.price)
+ return false;
+
+ if (aleftcomm.details.price && arightcomm.details.price) {
+ amount_t leftprice(*aleftcomm.details.price);
+ leftprice.in_place_reduce();
+ amount_t rightprice(*arightcomm.details.price);
+ rightprice.in_place_reduce();
+
+ if (leftprice.commodity() == rightprice.commodity()) {
+ return (leftprice - rightprice).sign() < 0;
+ } else {
+ // Since we have two different amounts, there's really no way
+ // to establish a true sorting order; we'll just do it based
+ // on the numerical values.
+ leftprice.clear_commodity();
+ rightprice.clear_commodity();
+ return (leftprice - rightprice).sign() < 0;
+ }
+ }
+
+ if (! aleftcomm.details.date && arightcomm.details.date)
+ return true;
+ if (aleftcomm.details.date && ! arightcomm.details.date)
+ return false;
+
+ if (aleftcomm.details.date && arightcomm.details.date) {
+ date_duration_t diff = *aleftcomm.details.date - *arightcomm.details.date;
+ return diff.is_negative();
+ }
+
+ if (! aleftcomm.details.tag && arightcomm.details.tag)
+ return true;
+ if (aleftcomm.details.tag && ! arightcomm.details.tag)
+ return false;
+
+ if (aleftcomm.details.tag && arightcomm.details.tag)
+ return *aleftcomm.details.tag < *arightcomm.details.tag;
+
+ assert(false);
+ return true;
+ }
+}
+
+commodity_pool_t::commodity_pool_t() : default_commodity(NULL)
+{
+ TRACE_CTOR(commodity_pool_t, "");
+ null_commodity = create("");
+ null_commodity->add_flags(COMMODITY_STYLE_NOMARKET |
+ COMMODITY_STYLE_BUILTIN);
+}
+
+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() << "'");
+
+ // Start out the new commodity with the default commodity's flags
+ // and precision, if one has been defined.
+#if 0
+ // jww (2007-05-02): This doesn't do anything currently!
+ if (default_commodity)
+ commodity->drop_flags(COMMODITY_STYLE_THOUSANDS |
+ COMMODITY_STYLE_NOMARKET);
+#endif
+
+ commodity->ident = commodities.size();
+
+ commodities.push_back(commodity.get());
+ 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);
+
+ typedef commodity_pool_t::commodities_t::nth_index<1>::type
+ commodities_by_name;
+
+ commodities_by_name& name_index = commodities.get<1>();
+ commodities_by_name::const_iterator i = name_index.find(symbol);
+ if (i != name_index.end())
+ return *i;
+ else
+ return NULL;
+}
+
+commodity_t * commodity_pool_t::find(const commodity_t::ident_t ident)
+{
+ DEBUG("amounts.commodities", "Find commodity by ident " << ident);
+
+ typedef commodity_pool_t::commodities_t::nth_index<0>::type
+ commodities_by_ident;
+
+ commodities_by_ident& ident_index = commodities.get<0>();
+ return ident_index[ident];
+}
+
+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);
+ annotated_commodity_t::write_annotations(name, details);
+
+ 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->ident = commodities.size();
+ commodity->mapping_key_ = mapping_key;
+
+ commodities.push_back(commodity.get());
+ 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);
+}
+
+} // namespace ledger
diff --git a/src/commodity.h b/src/commodity.h
new file mode 100644
index 00000000..2f5ce258
--- /dev/null
+++ b/src/commodity.h
@@ -0,0 +1,420 @@
+/*
+ * Copyright (c) 2003-2008, 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 commodity.h
+ * @author John Wiegley
+ * @date Wed Apr 18 22:05:53 2007
+ *
+ * @brief Types for handling commodities.
+ *
+ * This file contains one of the most basic types in Ledger:
+ * commodity_t, and its annotated cousin, annotated_commodity_t.
+ */
+#ifndef _COMMODITY_H
+#define _COMMODITY_H
+
+namespace ledger {
+
+class commodity_t
+ : public delegates_flags<>,
+ public equality_comparable1<commodity_t, noncopyable>
+{
+ friend class commodity_pool_t;
+
+public:
+ class base_t : public noncopyable, public supports_flags<>
+ {
+ base_t();
+
+ public:
+ typedef std::map<const datetime_t, amount_t> history_map;
+ typedef std::pair<const datetime_t, amount_t> history_pair;
+
+ struct history_t {
+ history_map prices;
+ ptime last_lookup;
+ };
+
+#define COMMODITY_STYLE_DEFAULTS 0x00
+#define COMMODITY_STYLE_SUFFIXED 0x01
+#define COMMODITY_STYLE_SEPARATED 0x02
+#define COMMODITY_STYLE_EUROPEAN 0x04
+#define COMMODITY_STYLE_THOUSANDS 0x08
+#define COMMODITY_STYLE_NOMARKET 0x10
+#define COMMODITY_STYLE_BUILTIN 0x20
+
+ string symbol;
+ amount_t::precision_t precision;
+ optional<string> name;
+ optional<string> note;
+ optional<history_t> history;
+ optional<amount_t> smaller;
+ optional<amount_t> larger;
+
+ public:
+ explicit base_t(const string& _symbol)
+ : supports_flags<>(COMMODITY_STYLE_DEFAULTS),
+ symbol(_symbol), precision(0) {
+ TRACE_CTOR(base_t, "const string&");
+ }
+ ~base_t() {
+ TRACE_DTOR(base_t);
+ }
+ };
+
+public:
+ static bool symbol_needs_quotes(const string& symbol);
+
+ typedef base_t::history_t history_t;
+ typedef base_t::history_map history_map;
+ typedef uint_least32_t ident_t;
+
+ shared_ptr<base_t> base;
+
+ commodity_pool_t * parent_;
+ ident_t ident;
+ optional<string> qualified_symbol;
+ optional<string> mapping_key_;
+ bool annotated;
+
+public:
+ explicit commodity_t(commodity_pool_t * _parent,
+ const shared_ptr<base_t>& _base)
+ : delegates_flags<>(*_base.get()), base(_base),
+ parent_(_parent), annotated(false) {
+ TRACE_CTOR(commodity_t, "");
+ }
+ virtual ~commodity_t() {
+ TRACE_DTOR(commodity_t);
+ }
+
+ operator bool() const;
+
+ bool is_annotated() const {
+ return annotated;
+ }
+
+ virtual bool operator==(const commodity_t& comm) const {
+ if (comm.annotated)
+ return comm == *this;
+ return base.get() == comm.base.get();
+ }
+
+ commodity_pool_t& parent() const {
+ return *parent_;
+ }
+
+ string base_symbol() const {
+ return base->symbol;
+ }
+ string symbol() const {
+ return qualified_symbol ? *qualified_symbol : base_symbol();
+ }
+
+ string mapping_key() const {
+ if (mapping_key_)
+ return *mapping_key_;
+ else
+ return base_symbol();
+ }
+
+ optional<string> name() const {
+ return base->name;
+ }
+ void set_name(const optional<string>& arg = none) {
+ base->name = arg;
+ }
+
+ optional<string> note() const {
+ return base->note;
+ }
+ void set_note(const optional<string>& arg = none) {
+ base->note = arg;
+ }
+
+ amount_t::precision_t precision() const {
+ return base->precision;
+ }
+ void set_precision(amount_t::precision_t arg) {
+ base->precision = arg;
+ }
+
+ optional<amount_t> smaller() const {
+ return base->smaller;
+ }
+ void set_smaller(const optional<amount_t>& arg = none) {
+ base->smaller = arg;
+ }
+
+ optional<amount_t> larger() const {
+ return base->larger;
+ }
+ void set_larger(const optional<amount_t>& arg = none) {
+ base->larger = arg;
+ }
+
+ optional<history_t> history() const {
+ return base->history;
+ }
+
+ void add_price(const datetime_t& date, const amount_t& price);
+ bool remove_price(const datetime_t& date);
+
+ optional<amount_t> value(const optional<datetime_t>& moment = none);
+
+ static amount_t exchange(const amount_t& amount,
+ amount_t& final_cost, // out
+ amount_t& basis_cost, // out
+ const optional<amount_t>& total_cost,
+ const optional<amount_t>& per_unit_cost = none,
+ const optional<datetime_t>& moment = none,
+ const optional<string>& tag = none);
+
+ static void parse_symbol(std::istream& in, string& symbol);
+ static void parse_symbol(char *& p, string& symbol);
+ static string parse_symbol(std::istream& in) {
+ string temp;
+ parse_symbol(in, temp);
+ return temp;
+ }
+
+ void print(std::ostream& out) const {
+ out << symbol();
+ }
+
+ void read(std::istream& in);
+ void read(char *& data);
+ void write(std::ostream& out) const;
+
+ bool valid() const;
+};
+
+inline std::ostream& operator<<(std::ostream& out, const commodity_t& comm) {
+ comm.print(out);
+ return out;
+}
+
+struct annotation_t : public equality_comparable<annotation_t>
+{
+ 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)
+ : price(_price), date(_date), tag(_tag) {
+ TRACE_CTOR(annotation_t, "const optional<amount_t>& + date_t + string");
+ }
+ annotation_t(const annotation_t& other)
+ : 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) const {
+ out << "price " << (price ? price->to_string() : "NONE") << " "
+ << "date " << (date ? *date : date_t()) << " "
+ << "tag " << (tag ? *tag : "NONE");
+ }
+
+ bool valid() const {
+ assert(*this);
+ return true;
+ }
+};
+
+inline std::ostream& operator<<(std::ostream& out, const annotation_t& details) {
+ details.print(out);
+ return out;
+}
+
+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);
+ }
+
+ commodity_t& referent() {
+ return *ptr;
+ }
+ const commodity_t& referent() const {
+ return *ptr;
+ }
+
+ commodity_t& strip_annotations(const bool _keep_price,
+ const bool _keep_date,
+ const bool _keep_tag);
+
+ void write_annotations(std::ostream& out) const {
+ annotated_commodity_t::write_annotations(out, details);
+ }
+
+ static void write_annotations(std::ostream& out,
+ const annotation_t& info);
+};
+
+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);
+}
+
+
+struct compare_amount_commodities {
+ bool operator()(const amount_t * left, const amount_t * right) const;
+};
+
+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.
+ *
+ * The `commodities' member variable represents a collection which
+ * is indexed by two vertices: first, and ordered sequence of unique
+ * integer which identify commodities by a numerical identifier; and
+ * second, by a hashed set of symbolic names which reflect how the
+ * commodity was referred to by the user.
+ */
+ typedef multi_index_container<
+ commodity_t *,
+ multi_index::indexed_by<
+ multi_index::random_access<>,
+ multi_index::hashed_unique<
+ multi_index::const_mem_fun<commodity_t,
+ string, &commodity_t::mapping_key> >
+ >
+ > commodities_t;
+
+public:
+ typedef commodity_pool_t::commodities_t::nth_index<0>::type
+ commodities_by_ident;
+
+ commodities_t commodities;
+
+ commodity_t * null_commodity;
+ commodity_t * default_commodity;
+
+private:
+ template<typename T>
+ struct first_initialized
+ {
+ typedef T result_type;
+
+ template<typename InputIterator>
+ T operator()(InputIterator first, InputIterator last) const
+ {
+ for (; first != last; first++)
+ if (*first)
+ return *first;
+ return T();
+ }
+ };
+
+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);
+ commodities_by_ident& ident_index = commodities.get<0>();
+ for (commodities_by_ident::iterator i = ident_index.begin();
+ i != ident_index.end();
+ i++)
+ checked_delete(*i);
+ }
+
+ commodity_t * create(const string& symbol);
+ commodity_t * find(const string& name);
+ commodity_t * find(const commodity_t::ident_t ident);
+ 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);
+};
+
+} // namespace ledger
+
+#endif // _COMMODITY_H
diff --git a/src/compare.cc b/src/compare.cc
new file mode 100644
index 00000000..1cbe7082
--- /dev/null
+++ b/src/compare.cc
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2003-2008, 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 "compare.h"
+
+namespace ledger {
+
+template <>
+bool compare_items<xact_t>::operator()(xact_t * left, xact_t * right)
+{
+ assert(left);
+ assert(right);
+
+ xact_t::xdata_t& lxdata(left->xdata());
+ if (! lxdata.has_flags(XACT_EXT_SORT_CALC)) {
+ lxdata.sort_value = sort_order.calc(*left);
+ lxdata.sort_value.reduce();
+ lxdata.add_flags(XACT_EXT_SORT_CALC);
+ }
+
+ xact_t::xdata_t& rxdata(right->xdata());
+ if (! rxdata.has_flags(XACT_EXT_SORT_CALC)) {
+ rxdata.sort_value = sort_order.calc(*right);
+ rxdata.sort_value.reduce();
+ rxdata.add_flags(XACT_EXT_SORT_CALC);
+ }
+
+ DEBUG("ledger.walk.compare_items_xact",
+ "lxdata.sort_value = " << lxdata.sort_value);
+ DEBUG("ledger.walk.compare_items_xact",
+ "rxdata.sort_value = " << rxdata.sort_value);
+
+ return lxdata.sort_value < rxdata.sort_value;
+}
+
+template <>
+bool compare_items<account_t>::operator()(account_t * left, account_t * right)
+{
+ assert(left);
+ assert(right);
+
+ account_t::xdata_t& lxdata(left->xdata());
+ if (! lxdata.has_flags(ACCOUNT_EXT_SORT_CALC)) {
+ lxdata.sort_value = sort_order.calc(*left);
+ lxdata.add_flags(ACCOUNT_EXT_SORT_CALC);
+ }
+
+ account_t::xdata_t& rxdata(right->xdata());
+ if (! rxdata.has_flags(ACCOUNT_EXT_SORT_CALC)) {
+ rxdata.sort_value = sort_order.calc(*right);
+ rxdata.add_flags(ACCOUNT_EXT_SORT_CALC);
+ }
+
+ return lxdata.sort_value < rxdata.sort_value;
+}
+
+} // namespace ledger
diff --git a/src/compare.h b/src/compare.h
new file mode 100644
index 00000000..d86771ef
--- /dev/null
+++ b/src/compare.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2003-2008, 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 _COMPARE_H
+#define _COMPARE_H
+
+#include "expr.h"
+#include "xact.h"
+#include "account.h"
+
+namespace ledger {
+
+template <typename T>
+class compare_items
+{
+ expr_t sort_order;
+
+ compare_items();
+
+public:
+ compare_items(const compare_items& other) : sort_order(other.sort_order) {
+ TRACE_CTOR(compare_items, "copy");
+ }
+ compare_items(const expr_t& _sort_order) : sort_order(_sort_order) {
+ TRACE_CTOR(compare_items, "const value_expr&");
+ }
+ ~compare_items() throw() {
+ TRACE_DTOR(compare_items);
+ }
+ bool operator()(T * left, T * right);
+};
+
+template <typename T>
+bool compare_items<T>::operator()(T * left, T * right)
+{
+ assert(left);
+ assert(right);
+ return sort_order.calc(*left) < sort_order.calc(*right);
+}
+
+template <>
+bool compare_items<xact_t>::operator()(xact_t * left, xact_t * right);
+template <>
+bool compare_items<account_t>::operator()(account_t * left,
+ account_t * right);
+
+} // namespace ledger
+
+#endif // _COMPARE_H
diff --git a/src/csv.cc b/src/csv.cc
new file mode 100644
index 00000000..c075622e
--- /dev/null
+++ b/src/csv.cc
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2003-2008, 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 "csv.h"
+
+namespace ledger {
+
+namespace {
+ inline void write_escaped_string(std::ostream& out, const string& xact)
+ {
+ out << "\"";
+ foreach (char ch, xact)
+ if (ch == '"') {
+ out << "\\";
+ out << "\"";
+ } else {
+ out << ch;
+ }
+ out << "\"";
+ }
+}
+
+void format_csv_xacts::operator()(xact_t& xact)
+{
+ if (! xact.has_xdata() ||
+ ! xact.xdata().has_flags(XACT_EXT_DISPLAYED)) {
+ {
+ format_t fmt("%D");
+ std::ostringstream str;
+#if 0
+ fmt.format(str, details_t(xact));
+#endif
+ write_escaped_string(out, str.str());
+ }
+ out << ',';
+
+ {
+ format_t fmt("%P");
+ std::ostringstream str;
+#if 0
+ fmt.format(str, details_t(xact));
+#endif
+ write_escaped_string(out, str.str());
+ }
+ out << ',';
+
+ {
+ format_t fmt("%A");
+ std::ostringstream str;
+#if 0
+ fmt.format(str, details_t(xact));
+#endif
+ write_escaped_string(out, str.str());
+ }
+ out << ',';
+
+ {
+ format_t fmt("%t");
+ std::ostringstream str;
+#if 0
+ fmt.format(str, details_t(xact));
+#endif
+ write_escaped_string(out, str.str());
+ }
+ out << ',';
+
+ {
+ format_t fmt("%T");
+ std::ostringstream str;
+#if 0
+ fmt.format(str, details_t(xact));
+#endif
+ write_escaped_string(out, str.str());
+ }
+ out << ',';
+
+ switch (xact.state) {
+ case xact_t::CLEARED:
+ write_escaped_string(out, "*");
+ break;
+ case xact_t::PENDING:
+ write_escaped_string(out, "!");
+ break;
+ default: {
+ xact_t::state_t state;
+ if (xact.entry->get_state(&state))
+ switch (state) {
+ case xact_t::CLEARED:
+ write_escaped_string(out, "*");
+ break;
+ case xact_t::PENDING:
+ write_escaped_string(out, "!");
+ break;
+ default:
+ write_escaped_string(out, "");
+ break;
+ }
+ }
+ }
+ out << ',';
+
+ if (xact.entry->code)
+ write_escaped_string(out, *xact.entry->code);
+ out << ',';
+
+ {
+ format_t fmt("%N");
+ std::ostringstream str;
+#if 0
+ fmt.format(str, details_t(xact));
+#endif
+ write_escaped_string(out, str.str());
+ }
+ out << '\n';
+
+ xact.xdata().add_flags(XACT_EXT_DISPLAYED);
+ }
+}
+
+} // namespace ledger
diff --git a/src/csv.h b/src/csv.h
new file mode 100644
index 00000000..bef58ad2
--- /dev/null
+++ b/src/csv.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2003-2008, 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 _CSV_H
+#define _CSV_H
+
+#include "handler.h"
+#include "format.h"
+
+namespace ledger {
+
+class format_csv_xacts : public item_handler<xact_t>
+{
+ format_csv_xacts();
+
+protected:
+ std::ostream& out;
+
+public:
+ format_csv_xacts(std::ostream& _out) : out(_out) {
+ TRACE_CTOR(format_csv_xacts, "std::ostream&");
+ }
+ ~format_csv_xacts() {
+ TRACE_DTOR(format_csv_xacts);
+ }
+
+ virtual void flush() {
+ out.flush();
+ }
+ virtual void operator()(xact_t& xact);
+};
+
+} // namespace ledger
+
+#endif // _REPORT_H
diff --git a/src/derive.cc b/src/derive.cc
new file mode 100644
index 00000000..dd9dc4ca
--- /dev/null
+++ b/src/derive.cc
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2003-2008, 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 "derive.h"
+#include "session.h"
+#include "iterators.h"
+
+namespace ledger {
+
+entry_t * derive_new_entry(report_t& report,
+ strings_list::iterator i,
+ strings_list::iterator end)
+{
+ session_t& session(report.session);
+
+ std::auto_ptr<entry_t> added(new entry_t);
+
+ entry_t * matching = NULL;
+
+ added->_date = parse_date(*i++);
+ if (i == end)
+ throw std::runtime_error("Too few arguments to 'entry'");
+
+ mask_t regexp(*i++);
+
+ journals_iterator iter(session);
+ entries_list::reverse_iterator j;
+
+ for (journal_t * journal = iter(); journal; journal = iter()) {
+ for (j = journal->entries.rbegin();
+ j != journal->entries.rend();
+ j++) {
+ if (regexp.match((*j)->payee)) {
+ matching = *j;
+ break;
+ }
+ }
+ if (matching) break;
+ }
+
+ added->payee = matching ? matching->payee : regexp.expr.str();
+
+ if (! matching) {
+ account_t * acct;
+ if (i == end || ((*i)[0] == '-' || std::isdigit((*i)[0]))) {
+ acct = session.find_account("Expenses");
+ }
+ else if (i != end) {
+ acct = session.find_account_re(*i);
+ if (! acct)
+ acct = session.find_account(*i);
+ assert(acct);
+ i++;
+ }
+
+ if (i == end) {
+ added->add_xact(new xact_t(acct));
+ } else {
+ xact_t * xact = new xact_t(acct, amount_t(*i++));
+ added->add_xact(xact);
+
+ if (! xact->amount.commodity()) {
+ // If the amount has no commodity, we can determine it given
+ // the account by creating a final for the account and then
+ // checking if it contains only a single commodity. An
+ // account to which only dollars are applied would imply that
+ // dollars are wanted now too.
+
+ report.sum_all_accounts();
+
+ value_t total = acct->xdata().total;
+ if (total.is_type(value_t::AMOUNT))
+ xact->amount.set_commodity(total.as_amount().commodity());
+ }
+ }
+
+ acct = NULL;
+
+ if (i != end) {
+ if (! acct)
+ acct = session.find_account_re(*i);
+ if (! acct)
+ acct = session.find_account(*i);
+ }
+
+ if (! acct) {
+ if (matching && matching->journal->basket)
+ acct = matching->journal->basket;
+ else
+ acct = session.find_account("Equity");
+ }
+
+ added->add_xact(new xact_t(acct));
+ }
+ else if (i == end) {
+ // If no argument were given but the payee, assume the user wants
+ // to see the same xact as last time.
+ added->code = matching->code;
+
+ foreach (xact_t * xact, matching->xacts)
+ added->add_xact(new xact_t(*xact));
+ }
+ else if ((*i)[0] == '-' || std::isdigit((*i)[0])) {
+ xact_t * m_xact, * xact, * first;
+ m_xact = matching->xacts.front();
+
+ first = xact = new xact_t(m_xact->account, amount_t(*i++));
+ added->add_xact(xact);
+
+ if (! xact->amount.commodity())
+ xact->amount.set_commodity(m_xact->amount.commodity());
+
+ m_xact = matching->xacts.back();
+
+ xact = new xact_t(m_xact->account, - first->amount);
+ added->add_xact(xact);
+
+ if (i != end) {
+ account_t * acct = session.find_account_re(*i);
+ if (! acct)
+ acct = session.find_account(*i);
+ assert(acct);
+ added->xacts.back()->account = acct;
+ }
+ }
+ else {
+ account_t * draw_acct = NULL;
+
+ while (i != end) {
+ string& re_pat(*i++);
+ account_t * acct = NULL;
+ amount_t * amt = NULL;
+
+ mask_t acct_regex(re_pat);
+
+ for (; j != matching->journal->entries.rend(); j++)
+ if (regexp.match((*j)->payee)) {
+ entry_t * entry = *j;
+ foreach (xact_t * xact, entry->xacts)
+ if (acct_regex.match(xact->account->fullname())) {
+ acct = xact->account;
+ amt = &xact->amount;
+ matching = entry;
+ goto found;
+ }
+ }
+
+ found:
+ xact_t * xact;
+ if (i == end) {
+ if (amt)
+ xact = new xact_t(acct, *amt);
+ else
+ xact = new xact_t(acct);
+ } else {
+ amount_t amount(*i++);
+
+ strings_list::iterator x = i;
+ if (i != end && ++x == end) {
+ draw_acct = session.find_account_re(*i);
+ if (! draw_acct)
+ draw_acct = session.find_account(*i);
+ i++;
+ }
+
+ if (! acct)
+ acct = session.find_account_re(re_pat);
+ if (! acct)
+ acct = session.find_account(re_pat);
+
+ xact = new xact_t(acct, amount);
+ if (! xact->amount.commodity()) {
+ if (amt)
+ xact->amount.set_commodity(amt->commodity());
+ else if (amount_t::current_pool->default_commodity)
+ xact->amount.set_commodity(*amount_t::current_pool->default_commodity);
+ }
+ }
+ added->add_xact(xact);
+ }
+
+ if (! draw_acct) {
+ assert(matching->xacts.back()->account);
+ draw_acct = matching->xacts.back()->account;
+ }
+ if (draw_acct)
+ added->add_xact(new xact_t(draw_acct));
+ }
+
+ if ((matching &&
+ ! matching->journal->entry_finalize_hooks.run_hooks(*added, false)) ||
+ ! added->finalize() ||
+ (matching &&
+ ! matching->journal->entry_finalize_hooks.run_hooks(*added, true)))
+ throw std::runtime_error("Failed to finalize derived entry (check commodities)");
+
+ return added.release();
+}
+
+} // namespace ledger
diff --git a/src/derive.h b/src/derive.h
new file mode 100644
index 00000000..5de86cc8
--- /dev/null
+++ b/src/derive.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2003-2008, 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 _DERIVE_H
+#define _DERIVE_H
+
+#include "report.h"
+
+namespace ledger {
+
+entry_t * derive_new_entry(report_t& report,
+ strings_list::iterator begin,
+ strings_list::iterator end);
+
+} // namespace ledger
+
+#endif // _DERIVE_H
diff --git a/src/emacs.cc b/src/emacs.cc
new file mode 100644
index 00000000..cf787e75
--- /dev/null
+++ b/src/emacs.cc
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2003-2008, 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 "emacs.h"
+#include "account.h"
+
+namespace ledger {
+
+void format_emacs_xacts::write_entry(entry_t& entry)
+{
+ int idx = entry.src_idx;
+ foreach (const path& path, entry.journal->sources)
+ if (! idx--) {
+ out << "\"" << path << "\" ";
+ break;
+ }
+
+ out << (static_cast<unsigned long>(entry.beg_line) + 1) << " ";
+
+ tm when = gregorian::to_tm(entry.date());
+ std::time_t date = std::mktime(&when); // jww (2008-04-20): Is this GMT or local?
+
+ out << "(" << (date / 65536) << " " << (date % 65536) << " 0) ";
+
+ if (! entry.code)
+ out << "nil ";
+ else
+ out << "\"" << *entry.code << "\" ";
+
+ if (entry.payee.empty())
+ out << "nil";
+ else
+ out << "\"" << entry.payee << "\"";
+
+ out << "\n";
+}
+
+void format_emacs_xacts::operator()(xact_t& xact)
+{
+ if (! xact.has_xdata() ||
+ ! xact.xdata().has_flags(XACT_EXT_DISPLAYED)) {
+ if (! last_entry) {
+ out << "((";
+ write_entry(*xact.entry);
+ }
+ else if (xact.entry != last_entry) {
+ out << ")\n (";
+ write_entry(*xact.entry);
+ }
+ else {
+ out << "\n";
+ }
+
+ out << " (" << (static_cast<unsigned long>(xact.beg_line) + 1) << " ";
+ out << "\"" << xact.reported_account()->fullname() << "\" \""
+ << xact.amount << "\"";
+
+ switch (xact.state) {
+ case xact_t::CLEARED:
+ out << " t";
+ break;
+ case xact_t::PENDING:
+ out << " pending";
+ break;
+ default:
+ out << " nil";
+ break;
+ }
+
+ if (xact.cost)
+ out << " \"" << *xact.cost << "\"";
+ if (xact.note)
+ out << " \"" << *xact.note << "\"";
+ out << ")";
+
+ last_entry = xact.entry;
+
+ xact.xdata().add_flags(XACT_EXT_DISPLAYED);
+ }
+}
+
+} // namespace ledger
diff --git a/src/emacs.h b/src/emacs.h
new file mode 100644
index 00000000..59b937f8
--- /dev/null
+++ b/src/emacs.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2003-2008, 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 _EMACS_H
+#define _EMACS_H
+
+#include "handler.h"
+#include "format.h"
+
+namespace ledger {
+
+class format_emacs_xacts : public item_handler<xact_t>
+{
+ format_emacs_xacts();
+
+protected:
+ std::ostream& out;
+ entry_t * last_entry;
+
+public:
+ format_emacs_xacts(std::ostream& _out)
+ : out(_out), last_entry(NULL) {
+ TRACE_CTOR(format_emacs_xacts, "std::ostream&");
+ }
+ ~format_emacs_xacts() {
+ TRACE_DTOR(format_emacs_xacts);
+ }
+
+ virtual void write_entry(entry_t& entry);
+ virtual void flush() {
+ if (last_entry)
+ out << "))\n";
+ out.flush();
+ }
+ virtual void operator()(xact_t& xact);
+};
+
+} // namespace ledger
+
+#endif // _REPORT_H
diff --git a/src/entry.cc b/src/entry.cc
new file mode 100644
index 00000000..799de841
--- /dev/null
+++ b/src/entry.cc
@@ -0,0 +1,482 @@
+/*
+ * Copyright (c) 2003-2008, 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 "entry.h"
+#include "journal.h"
+#include "format.h"
+#include "session.h"
+#include "report.h"
+
+namespace ledger {
+
+entry_base_t::entry_base_t(const entry_base_t& e)
+ : supports_flags<>(), journal(NULL),
+ beg_pos(0), beg_line(0), end_pos(0), end_line(0)
+{
+ TRACE_CTOR(entry_base_t, "copy");
+ xacts.insert(xacts.end(), e.xacts.begin(), e.xacts.end());
+}
+
+entry_base_t::~entry_base_t()
+{
+ TRACE_DTOR(entry_base_t);
+
+ foreach (xact_t * xact, xacts) {
+ // If the transaction is a temporary, it will be destructed when the
+ // temporary is. If it's from a binary cache, we can safely destruct it
+ // but its memory will be deallocated with the cache.
+ if (! xact->has_flags(XACT_TEMP)) {
+ if (! xact->has_flags(XACT_IN_CACHE))
+ checked_delete(xact);
+ else
+ xact->~xact_t();
+ }
+ }
+}
+
+void entry_base_t::add_xact(xact_t * xact)
+{
+ xacts.push_back(xact);
+}
+
+bool entry_base_t::remove_xact(xact_t * xact)
+{
+ xacts.remove(xact);
+ return true;
+}
+
+bool entry_base_t::finalize()
+{
+ // Scan through and compute the total balance for the entry. This is used
+ // for auto-calculating the value of entries with no cost, and the per-unit
+ // price of unpriced commodities.
+
+ // (let ((balance 0)
+ // null-xact)
+
+ value_t balance;
+ xact_t * null_xact = NULL;
+
+ // (do-xacts (xact entry)
+ // (when (xact-must-balance-p xact)
+ // (let ((amt (xact-amount* xact)))
+ // (if amt
+ // (setf balance (add balance (or (xact-cost xact) amt)))
+ // (if null-xact
+ // (error "Only one xact with null amount allowed ~
+ // per entry (beg ~S end ~S)"
+ // (item-position-begin-line (entry-position entry))
+ // (item-position-end-line (entry-position entry)))
+ // (setf null-xact xact))))))
+ //
+
+ foreach (xact_t * xact, xacts) {
+ if (xact->must_balance()) {
+ amount_t& p(xact->cost ? *xact->cost : xact->amount);
+ if (! p.is_null()) {
+ if (balance.is_null())
+ balance = p;
+ else
+ balance += p;
+ } else {
+ if (null_xact)
+ throw_(std::logic_error,
+ "Only one xact with null amount allowed per entry");
+ else
+ null_xact = xact;
+ }
+ }
+ }
+ assert(balance.valid());
+
+ DEBUG("ledger.journal.finalize", "initial balance = " << balance);
+
+ // If there is only one xact, balance against the default account if
+ // one has been set.
+
+ // (when (= 1 (length (entry-xacts entry)))
+ // (if-let ((default-account
+ // (journal-default-account (entry-journal entry))))
+ // (setf null-xact
+ // (make-xact :entry entry
+ // :status (xact-status
+ // (first (entry-xacts entry)))
+ // :account default-account
+ // :generatedp t))
+ // (add-xact entry null-xact)))
+
+ if (journal && journal->basket && xacts.size() == 1) {
+ // jww (2008-07-24): Need to make the rest of the code aware of what to do
+ // when it sees a generated xact.
+ null_xact = new xact_t(journal->basket, XACT_GENERATED);
+ null_xact->state = (*xacts.begin())->state;
+ add_xact(null_xact);
+ }
+
+ if (null_xact != NULL) {
+ // If one xact has no value at all, its value will become the
+ // inverse of the rest. If multiple commodities are involved, multiple
+ // xacts are generated to balance them all.
+
+ // (progn
+ // (if (balance-p balance)
+ // (let ((first t))
+ // (dolist (amount (balance-amounts balance))
+ // (if first
+ // (setf (xact-amount* null-xact) (negate amount)
+ // first nil)
+ // (add-xact
+ // entry
+ // (make-xact :entry entry
+ // :account (xact-account null-xact)
+ // :amount (negate amount)
+ // :generatedp t)))))
+ // (setf (xact-amount* null-xact) (negate balance)
+ // (xact-calculatedp null-xact) t))
+ //
+ // (setf balance 0))
+
+ if (balance.is_balance()) {
+ bool first = true;
+ const balance_t& bal(balance.as_balance());
+ foreach (const balance_t::amounts_map::value_type& pair, bal.amounts) {
+ if (first) {
+ null_xact->amount = pair.second.negate();
+ first = false;
+ } else {
+ add_xact(new xact_t(null_xact->account, pair.second.negate(),
+ XACT_GENERATED));
+ }
+ }
+ } else {
+ null_xact->amount = balance.as_amount().negate();
+ null_xact->add_flags(XACT_CALCULATED);
+ }
+ balance = NULL_VALUE;
+
+ }
+ else if (balance.is_balance() &&
+ balance.as_balance().amounts.size() == 2) {
+ // When an entry involves two different commodities (regardless of how
+ // many xacts there are) determine the conversion ratio by dividing
+ // the total value of one commodity by the total value of the other. This
+ // establishes the per-unit cost for this xact for both
+ // commodities.
+
+ // (when (and (balance-p balance)
+ // (= 2 (balance-commodity-count balance)))
+ // (destructuring-bind (x y) (balance-amounts balance)
+ // (let ((a-commodity (amount-commodity x))
+ // (per-unit-cost (value-abs (divide x y))))
+ // (do-xacts (xact entry)
+ // (let ((amount (xact-amount* xact)))
+ // (unless (or (xact-cost xact)
+ // (not (xact-must-balance-p xact))
+ // (commodity-equal (amount-commodity amount)
+ // a-commodity))
+ // (setf balance (subtract balance amount)
+ // (xact-cost xact) (multiply per-unit-cost amount)
+ // balance (add balance (xact-cost xact))))))))))
+
+ const balance_t& bal(balance.as_balance());
+
+ balance_t::amounts_map::const_iterator a = bal.amounts.begin();
+
+ const amount_t& x((*a++).second);
+ const amount_t& y((*a++).second);
+
+ if (! y.is_realzero()) {
+ amount_t per_unit_cost = (x / y).abs();
+
+ commodity_t& comm(x.commodity());
+
+ foreach (xact_t * xact, xacts) {
+ const amount_t& x_amt(xact->amount);
+
+ if (! (xact->cost ||
+ ! xact->must_balance() ||
+ x_amt.commodity() == comm)) {
+ DEBUG("ledger.journal.finalize", "before operation 1 = " << balance);
+ balance -= x_amt;
+ DEBUG("ledger.journal.finalize", "after operation 1 = " << balance);
+ DEBUG("ledger.journal.finalize", "x_amt = " << x_amt);
+ DEBUG("ledger.journal.finalize", "per_unit_cost = " << per_unit_cost);
+
+ xact->cost = per_unit_cost * x_amt;
+ DEBUG("ledger.journal.finalize", "*xact->cost = " << *xact->cost);
+
+ balance += *xact->cost;
+ DEBUG("ledger.journal.finalize", "after operation 2 = " << balance);
+ }
+
+ }
+ }
+
+ DEBUG("ledger.journal.finalize", "resolved balance = " << balance);
+ }
+
+ // Now that the xact list has its final form, calculate the balance
+ // once more in terms of total cost, accounting for any possible gain/loss
+ // amounts.
+
+ // (do-xacts (xact entry)
+ // (when (xact-cost xact)
+ // (let ((amount (xact-amount* xact)))
+ // (assert (not (commodity-equal (amount-commodity amount)
+ // (amount-commodity (xact-cost xact)))))
+ // (multiple-value-bind (annotated-amount total-cost basis-cost)
+ // (exchange-commodity amount :total-cost (xact-cost xact)
+ // :moment (entry-date entry)
+ // :tag (entry-code entry))
+ // (if (annotated-commodity-p (amount-commodity amount))
+ // (if-let ((price (annotation-price
+ // (commodity-annotation
+ // (amount-commodity amount)))))
+ // (setf balance
+ // (add balance (subtract basis-cost total-cost))))
+ // (setf (xact-amount* xact) annotated-amount))))))
+
+ foreach (xact_t * xact, xacts) {
+ if (xact->cost) {
+ const amount_t& x_amt(xact->amount);
+
+ assert(x_amt.commodity() != xact->cost->commodity());
+
+ entry_t * entry = dynamic_cast<entry_t *>(this);
+
+ // jww (2008-07-24): Pass the entry's code here if we can, as the
+ // auto-tag
+ amount_t final_cost;
+ amount_t basis_cost;
+ amount_t ann_amount =
+ commodity_t::exchange(x_amt, final_cost, basis_cost, xact->cost, none,
+ datetime_t(xact->actual_date(),
+ time_duration_t(0, 0, 0)),
+ entry ? entry->code : optional<string>());
+
+ if (xact->amount.annotated()) {
+ if (ann_amount.annotation().price) {
+ if (balance.is_null())
+ balance = basis_cost - final_cost;
+ else
+ balance += basis_cost - final_cost;
+ }
+ } else {
+ xact->amount = ann_amount;
+ }
+ }
+ }
+
+ DEBUG("ledger.journal.finalize", "final balance = " << balance);
+
+ // (if (value-zerop balance)
+ // (prog1
+ // entry
+ // (setf (entry-normalizedp entry) t))
+ // (error "Entry does not balance (beg ~S end ~S); remaining balance is:~%~A"
+ // (item-position-begin-line (entry-position entry))
+ // (item-position-end-line (entry-position entry))
+ // (format-value balance :width 20)))
+
+ if (! balance.is_null()) {
+ balance.round();
+ if (! balance.is_zero()) {
+#if 0
+ error * err =
+ new balance_error("Entry does not balance",
+ new entry_context(*this, "While balancing entry:"));
+ err->context.push_front
+ (new value_context(balance, "Unbalanced remainder is:"));
+ throw err;
+#endif
+ }
+ }
+
+ return true;
+}
+
+entry_t::entry_t(const entry_t& e)
+ : entry_base_t(e), scope_t(), _date(e._date), _date_eff(e._date_eff),
+ code(e.code), payee(e.payee)
+{
+ TRACE_CTOR(entry_t, "copy");
+
+ foreach (xact_t * xact, xacts)
+ xact->entry = this;
+}
+
+bool entry_t::get_state(xact_t::state_t * state) const
+{
+ bool first = true;
+ bool hetero = false;
+
+ foreach (xact_t * xact, xacts) {
+ if (first) {
+ *state = xact->state;
+ first = false;
+ }
+ else if (*state != xact->state) {
+ hetero = true;
+ break;
+ }
+ }
+
+ return ! hetero;
+}
+
+void entry_t::add_xact(xact_t * xact)
+{
+ xact->entry = this;
+ entry_base_t::add_xact(xact);
+}
+
+namespace {
+ value_t get_date(entry_t& entry) {
+ return entry.date();
+ }
+
+ value_t get_payee(entry_t& entry) {
+ return string_value(entry.payee);
+ }
+
+ template <value_t (*Func)(entry_t&)>
+ value_t get_wrapper(call_scope_t& scope) {
+ return (*Func)(find_scope<entry_t>(scope));
+ }
+}
+
+expr_t::ptr_op_t entry_t::lookup(const string& name)
+{
+ switch (name[0]) {
+ case 'd':
+ if (name[1] == '\0' || name == "date")
+ return WRAP_FUNCTOR(get_wrapper<&get_date>);
+ break;
+
+ case 'f':
+ if (name.find("fmt_") == 0) {
+ switch (name[4]) {
+ case 'D':
+ return WRAP_FUNCTOR(get_wrapper<&get_date>);
+ case 'P':
+ return WRAP_FUNCTOR(get_wrapper<&get_payee>);
+ }
+ }
+ break;
+
+ case 'p':
+ if (name[1] == '\0' || name == "payee")
+ return WRAP_FUNCTOR(get_wrapper<&get_payee>);
+ break;
+ }
+ return journal->owner->current_report->lookup(name);
+}
+
+bool entry_t::valid() const
+{
+ if (! is_valid(_date) || ! journal) {
+ DEBUG("ledger.validate", "entry_t: ! _date || ! journal");
+ return false;
+ }
+
+ foreach (xact_t * xact, xacts)
+ if (xact->entry != this || ! xact->valid()) {
+ DEBUG("ledger.validate", "entry_t: xact not valid");
+ return false;
+ }
+
+ return true;
+}
+
+#if 0
+void entry_context::describe(std::ostream& out) const throw()
+{
+ if (! desc.empty())
+ out << desc << std::endl;
+
+ print_entry(out, entry, " ");
+}
+#endif
+
+void auto_entry_t::extend_entry(entry_base_t& entry, bool post)
+{
+ xacts_list initial_xacts(entry.xacts.begin(),
+ entry.xacts.end());
+
+ foreach (xact_t * initial_xact, initial_xacts) {
+ if (predicate(*initial_xact)) {
+ foreach (xact_t * xact, xacts) {
+ amount_t amt;
+ assert(xact->amount);
+ if (! xact->amount.commodity()) {
+ if (! post)
+ continue;
+ assert(initial_xact->amount);
+ amt = initial_xact->amount * xact->amount;
+ } else {
+ if (post)
+ continue;
+ amt = xact->amount;
+ }
+
+ account_t * account = xact->account;
+ string fullname = account->fullname();
+ assert(! fullname.empty());
+ if (fullname == "$account" || fullname == "@account")
+ account = initial_xact->account;
+
+ xact_t * new_xact
+ = new xact_t(account, amt, xact->flags() | XACT_AUTO);
+
+ // Copy over details so that the resulting xact is a mirror of
+ // the automated entry's one.
+ new_xact->state = xact->state;
+ new_xact->_date = xact->_date;
+ new_xact->_date_eff = xact->_date_eff;
+ new_xact->note = xact->note;
+ new_xact->beg_pos = xact->beg_pos;
+ new_xact->beg_line = xact->beg_line;
+ new_xact->end_pos = xact->end_pos;
+ new_xact->end_line = xact->end_line;
+
+ entry.add_xact(new_xact);
+ }
+ }
+ }
+}
+
+void extend_entry_base(journal_t * journal, entry_base_t& base, bool post)
+{
+ foreach (auto_entry_t * entry, journal->auto_entries)
+ entry->extend_entry(base, post);
+}
+
+} // namespace ledger
diff --git a/src/entry.h b/src/entry.h
new file mode 100644
index 00000000..2834942f
--- /dev/null
+++ b/src/entry.h
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2003-2008, 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 _ENTRY_H
+#define _ENTRY_H
+
+#include "xact.h"
+#include "predicate.h"
+
+namespace ledger {
+
+class journal_t;
+
+class entry_base_t : public supports_flags<>
+{
+public:
+#define ENTRY_IN_CACHE 0x1
+
+ journal_t * journal;
+ string note;
+ unsigned long src_idx;
+ istream_pos_type beg_pos;
+ unsigned long beg_line;
+ istream_pos_type end_pos;
+ unsigned long end_line;
+ xacts_list xacts;
+
+ entry_base_t() : journal(NULL),
+ beg_pos(0), beg_line(0), end_pos(0), end_line(0)
+ {
+ TRACE_CTOR(entry_base_t, "");
+ }
+ entry_base_t(const entry_base_t& e);
+
+ virtual ~entry_base_t();
+
+ bool operator==(const entry_base_t& entry) {
+ return this == &entry;
+ }
+ bool operator!=(const entry_base_t& entry) {
+ return ! (*this == entry);
+ }
+
+ virtual void add_xact(xact_t * xact);
+ virtual bool remove_xact(xact_t * xact);
+
+ virtual bool finalize();
+ virtual bool valid() const = 0;
+};
+
+class entry_t : public entry_base_t, public scope_t
+{
+public:
+ date_t _date;
+ optional<date_t> _date_eff;
+ optional<string> code;
+ string payee;
+
+ entry_t() {
+ TRACE_CTOR(entry_t, "");
+ }
+ entry_t(const entry_t& e);
+
+ virtual ~entry_t() {
+ TRACE_DTOR(entry_t);
+ }
+
+ date_t actual_date() const {
+ return _date;
+ }
+ date_t effective_date() const {
+ if (! _date_eff)
+ return _date;
+ return *_date_eff;
+ }
+ date_t date() const {
+ if (xact_t::use_effective_date)
+ return effective_date();
+ else
+ return actual_date();
+ }
+
+ bool get_state(xact_t::state_t * state) const;
+
+ virtual void add_xact(xact_t * xact);
+
+ virtual expr_t::ptr_op_t lookup(const string& name);
+
+ virtual bool valid() const;
+};
+
+struct entry_finalizer_t {
+ virtual ~entry_finalizer_t() {}
+ virtual bool operator()(entry_t& entry, bool post) = 0;
+};
+
+class auto_entry_t : public entry_base_t
+{
+public:
+ item_predicate<xact_t> predicate;
+
+ auto_entry_t() {
+ TRACE_CTOR(auto_entry_t, "");
+ }
+ auto_entry_t(const auto_entry_t& other)
+ : entry_base_t(), predicate(other.predicate) {
+ TRACE_CTOR(auto_entry_t, "copy");
+ }
+ auto_entry_t(const string& _predicate)
+ : predicate(_predicate)
+ {
+ TRACE_CTOR(auto_entry_t, "const string&");
+ }
+
+ virtual ~auto_entry_t() {
+ TRACE_DTOR(auto_entry_t);
+ }
+
+ virtual void extend_entry(entry_base_t& entry, bool post);
+ virtual bool valid() const {
+ return true;
+ }
+};
+
+struct auto_entry_finalizer_t : public entry_finalizer_t
+{
+ journal_t * journal;
+
+ auto_entry_finalizer_t() : journal(NULL) {
+ TRACE_CTOR(auto_entry_finalizer_t, "");
+ }
+ auto_entry_finalizer_t(const auto_entry_finalizer_t& other)
+ : entry_finalizer_t(), journal(other.journal) {
+ TRACE_CTOR(auto_entry_finalizer_t, "copy");
+ }
+ auto_entry_finalizer_t(journal_t * _journal) : journal(_journal) {
+ TRACE_CTOR(auto_entry_finalizer_t, "journal_t *");
+ }
+ ~auto_entry_finalizer_t() throw() {
+ TRACE_DTOR(auto_entry_finalizer_t);
+ }
+
+ virtual bool operator()(entry_t& entry, bool post);
+};
+
+class period_entry_t : public entry_base_t
+{
+ public:
+ interval_t period;
+ string period_string;
+
+ period_entry_t() {
+ TRACE_CTOR(period_entry_t, "");
+ }
+ period_entry_t(const period_entry_t& e)
+ : entry_base_t(e), period(e.period), period_string(e.period_string) {
+ TRACE_CTOR(period_entry_t, "copy");
+ }
+ period_entry_t(const string& _period)
+ : period(_period), period_string(_period) {
+ TRACE_CTOR(period_entry_t, "const string&");
+ }
+
+ virtual ~period_entry_t() throw() {
+ TRACE_DTOR(period_entry_t);
+ }
+
+ virtual bool valid() const {
+ return period;
+ }
+};
+
+class func_finalizer_t : public entry_finalizer_t
+{
+ func_finalizer_t();
+
+public:
+ typedef function<bool (entry_t& entry, bool post)> func_t;
+
+ func_t func;
+
+ func_finalizer_t(func_t _func) : func(_func) {
+ TRACE_CTOR(func_finalizer_t, "func_t");
+ }
+ func_finalizer_t(const func_finalizer_t& other) :
+ entry_finalizer_t(), func(other.func) {
+ TRACE_CTOR(func_finalizer_t, "copy");
+ }
+ ~func_finalizer_t() throw() {
+ TRACE_DTOR(func_finalizer_t);
+ }
+
+ virtual bool operator()(entry_t& entry, bool post) {
+ return func(entry, post);
+ }
+};
+
+void extend_entry_base(journal_t * journal, entry_base_t& entry, bool post);
+
+inline bool auto_entry_finalizer_t::operator()(entry_t& entry, bool post) {
+ extend_entry_base(journal, entry, post);
+ return true;
+}
+
+typedef std::list<entry_t *> entries_list;
+typedef std::list<auto_entry_t *> auto_entries_list;
+typedef std::list<period_entry_t *> period_entries_list;
+
+} // namespace ledger
+
+#endif // _ENTRY_H
diff --git a/src/error.h b/src/error.h
new file mode 100644
index 00000000..fafef08e
--- /dev/null
+++ b/src/error.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2003-2008, 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 _ERROR_H
+#define _ERROR_H
+
+namespace ledger {
+
+extern std::ostringstream _desc_buffer;
+
+template <typename T>
+inline void throw_func(const string& message) {
+ _desc_buffer.str("");
+ throw T(message);
+}
+
+#define throw_(cls, msg) \
+ ((_desc_buffer << msg), throw_func<cls>(_desc_buffer.str()))
+
+extern std::ostringstream _ctxt_buffer;
+
+#define add_error_context(msg) \
+ ((static_cast<unsigned long>(_ctxt_buffer.tellp()) == 0) ? \
+ (_ctxt_buffer << msg) : (_ctxt_buffer << std::endl << msg))
+
+inline string error_context() {
+ string context = _ctxt_buffer.str();
+ _ctxt_buffer.str("");
+ return context;
+}
+
+inline string file_context(const path& file, std::size_t line) {
+ std::ostringstream buf;
+ buf << "\"" << file << "\", line " << line << ": ";
+ return buf.str();
+}
+
+inline string line_context(const string& line, long pos) {
+ std::ostringstream buf;
+ buf << " " << line << std::endl << " ";
+ long idx = pos < 0 ? line.length() - 1 : pos;
+ for (int i = 0; i < idx; i++)
+ buf << " ";
+ buf << "^" << std::endl;
+ return buf.str();
+}
+
+#define DECLARE_EXCEPTION(name, kind) \
+ class name : public kind { \
+ public: \
+ explicit name(const string& why) throw() : kind(why) {} \
+ virtual ~name() throw() {} \
+ }
+
+} // namespace ledger
+
+#endif // _ERROR_H
diff --git a/src/expr.cc b/src/expr.cc
new file mode 100644
index 00000000..37433179
--- /dev/null
+++ b/src/expr.cc
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2003-2008, 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 "expr.h"
+#include "parser.h"
+#include "op.h"
+#include "scope.h" // jww (2008-08-01): not necessary
+
+namespace ledger {
+
+std::auto_ptr<expr_t::parser_t> expr_t::parser;
+
+expr_t::expr_t() : compiled(false)
+{
+ TRACE_CTOR(expr_t, "");
+}
+
+expr_t::expr_t(const expr_t& other)
+ : ptr(other.ptr), str(other.str), compiled(other.compiled)
+{
+ TRACE_CTOR(expr_t, "copy");
+}
+
+expr_t::expr_t(const string& _str, const unsigned int flags)
+ : str(_str), compiled(false)
+{
+ TRACE_CTOR(expr_t, "const string&");
+
+ if (! _str.empty())
+ ptr = parser->parse(str, flags);
+}
+
+expr_t::expr_t(std::istream& in, const unsigned int flags)
+ : compiled(false)
+{
+ TRACE_CTOR(expr_t, "std::istream&");
+
+ ptr = parser->parse(in, flags);
+}
+
+expr_t::expr_t(const ptr_op_t& _ptr, const string& _str)
+ : ptr(_ptr), str(_str), compiled(false)
+{
+ TRACE_CTOR(expr_t, "const ptr_op_t&, const string&");
+}
+
+expr_t::~expr_t() throw()
+{
+ TRACE_DTOR(expr_t);
+}
+
+expr_t& expr_t::operator=(const expr_t& _expr)
+{
+ if (this != &_expr) {
+ str = _expr.str;
+ ptr = _expr.ptr;
+ compiled = _expr.compiled;
+ }
+ return *this;
+}
+
+void expr_t::parse(const string& _str, const unsigned int flags)
+{
+ if (! parser.get())
+ throw_(parse_error, "Value expression parser not initialized");
+
+ str = _str;
+ ptr = parser->parse(str, flags);
+ compiled = false;
+}
+
+void expr_t::parse(std::istream& in, const unsigned int flags)
+{
+ if (! parser.get())
+ throw_(parse_error, "Value expression parser not initialized");
+
+ str = "<stream>";
+ ptr = parser->parse(in, flags);
+ compiled = false;
+}
+
+void expr_t::compile(scope_t& scope)
+{
+ if (ptr.get() && ! compiled) {
+ ptr = ptr->compile(scope);
+ compiled = true;
+ }
+}
+
+value_t expr_t::calc(scope_t& scope)
+{
+ if (ptr.get()) {
+ if (! compiled)
+ compile(scope);
+ return ptr->calc(scope);
+ }
+ return NULL_VALUE;
+}
+
+bool expr_t::is_constant() const
+{
+ assert(compiled);
+ return ptr.get() && ptr->is_value();
+}
+
+bool expr_t::is_function() const
+{
+ assert(compiled);
+ return ptr.get() && ptr->is_function();
+}
+
+value_t& expr_t::constant_value()
+{
+ assert(is_constant());
+ return ptr->as_value_lval();
+}
+
+const value_t& expr_t::constant_value() const
+{
+ assert(is_constant());
+ return ptr->as_value();
+}
+
+function_t& expr_t::get_function()
+{
+ assert(is_function());
+ return ptr->as_function_lval();
+}
+
+value_t expr_t::eval(const string& _expr, scope_t& scope)
+{
+ return expr_t(_expr).calc(scope);
+}
+
+void expr_t::print(std::ostream& out, scope_t& scope) const
+{
+ if (ptr) {
+ op_t::print_context_t context(scope);
+ ptr->print(out, context);
+ }
+}
+
+void expr_t::dump(std::ostream& out) const
+{
+ if (ptr) ptr->dump(out, 0);
+}
+
+void expr_t::read(const char *& data)
+{
+ if (ptr) ptr->read(data);
+}
+
+void expr_t::write(std::ostream& out) const
+{
+ if (ptr) ptr->write(out);
+}
+
+void expr_t::initialize()
+{
+ parser.reset(new expr_t::parser_t);
+}
+
+void expr_t::shutdown()
+{
+ parser.reset();
+}
+
+std::ostream& operator<<(std::ostream& out, const expr_t& expr) {
+ // jww (2008-08-01): shouldn't be necessary
+ symbol_scope_t scope;
+ expr.print(out, scope);
+ return out;
+}
+
+} // namespace ledger
diff --git a/src/expr.h b/src/expr.h
new file mode 100644
index 00000000..8c110ad9
--- /dev/null
+++ b/src/expr.h
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2003-2008, 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 _EXPR_H
+#define _EXPR_H
+
+#include "value.h"
+
+namespace ledger {
+
+DECLARE_EXCEPTION(parse_error, std::runtime_error);
+DECLARE_EXCEPTION(compile_error, std::runtime_error);
+DECLARE_EXCEPTION(calc_error, std::runtime_error);
+DECLARE_EXCEPTION(usage_error, std::runtime_error);
+
+class scope_t;
+class call_scope_t;
+
+typedef function<value_t (call_scope_t&)> function_t;
+
+class expr_t
+{
+ struct token_t;
+
+ class parser_t;
+ static std::auto_ptr<parser_t> parser;
+
+public:
+ class op_t;
+ typedef intrusive_ptr<op_t> ptr_op_t;
+
+private:
+ ptr_op_t ptr;
+ string str;
+ bool compiled;
+
+ static void initialize();
+ static void shutdown();
+
+ friend class session_t;
+
+public:
+ expr_t();
+ expr_t(const expr_t& other);
+ expr_t(const ptr_op_t& _ptr, const string& _str = "");
+
+ expr_t(const string& _str, const unsigned int flags = 0);
+ expr_t(std::istream& in, const unsigned int flags = 0);
+
+ virtual ~expr_t() throw();
+
+ expr_t& operator=(const expr_t& _expr);
+ expr_t& operator=(const string& _expr) {
+ parse(_expr);
+ return *this;
+ }
+
+ operator bool() const throw() {
+ return ptr.get() != NULL;
+ }
+ string text() const throw() {
+ return str;
+ }
+
+ // This has special use in the textual parser
+ void set_text(const string& txt) {
+ str = txt;
+ }
+
+ void parse(const string& _str, const unsigned int flags = 0);
+ void parse(std::istream& in, const unsigned int flags = 0);
+
+ void compile(scope_t& scope);
+ value_t calc(scope_t& scope);
+ value_t calc(scope_t& scope) const;
+
+ bool is_constant() const;
+ bool is_function() const;
+
+ value_t& constant_value();
+ const value_t& constant_value() const;
+
+ function_t& get_function();
+
+ void print(std::ostream& out, scope_t& scope) const;
+ void dump(std::ostream& out) const;
+ void read(const char *& data);
+ void write(std::ostream& out) const;
+
+ static value_t eval(const string& _expr, scope_t& scope);
+};
+
+std::ostream& operator<<(std::ostream& out, const expr_t& expr);
+
+} // namespace ledger
+
+#endif // _EXPR_H
diff --git a/src/fdstream.h b/src/fdstream.h
new file mode 100644
index 00000000..8a06fba2
--- /dev/null
+++ b/src/fdstream.h
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2003-2008, 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.
+ */
+
+/* The following code declares classes to read from and write to
+ * file descriptore or file handles.
+ *
+ * See
+ * http://www.josuttis.com/cppcode
+ * for details and the latest version.
+ *
+ * - open:
+ * - integrating BUFSIZ on some systems?
+ * - optimized reading of multiple characters
+ * - stream for reading AND writing
+ * - i18n
+ *
+ * (C) Copyright Nicolai M. Josuttis 2001.
+ * Permission to copy, use, modify, sell and distribute this software
+ * is granted provided this copyright notice appears in all copies.
+ * This software is provided "as is" without express or implied
+ * warranty, and with no claim as to its suitability for any purpose.
+ *
+ * Version: Jul 28, 2002
+ * History:
+ * Jul 28, 2002: bugfix memcpy() => memmove()
+ * fdinbuf::underflow(): cast for return statements
+ * Aug 05, 2001: first public version
+ */
+#ifndef BOOST_FDSTREAM_HPP
+#define BOOST_FDSTREAM_HPP
+
+#include <istream>
+#include <ostream>
+#include <streambuf>
+// for EOF:
+#include <cstdio>
+// for memmove():
+#include <cstring>
+
+
+// low-level read and write functions
+#ifdef _MSC_VER
+# include <io.h>
+#else
+# include <unistd.h>
+//extern "C" {
+// int write (int fd, const char* buf, int num);
+// int read (int fd, char* buf, int num);
+//}
+#endif
+
+
+// BEGIN namespace BOOST
+namespace boost {
+
+
+/************************************************************
+ * fdostream
+ * - a stream that writes on a file descriptor
+ ************************************************************/
+
+
+class fdoutbuf : public std::streambuf {
+ protected:
+ int fd; // file descriptor
+ public:
+ // constructor
+ fdoutbuf (int _fd) : fd(_fd) {
+ }
+ protected:
+ // write one character
+ virtual int_type overflow (int_type c) {
+ if (c != EOF) {
+ char z = c;
+ if (write (fd, &z, 1) != 1) {
+ return EOF;
+ }
+ }
+ return c;
+ }
+ // write multiple characters
+ virtual
+ std::streamsize xsputn (const char* s,
+ std::streamsize num) {
+ return write(fd,s,num);
+ }
+};
+
+class fdostream : public std::ostream {
+ protected:
+ fdoutbuf buf;
+ public:
+ fdostream (int fd) : std::ostream(0), buf(fd) {
+ rdbuf(&buf);
+ }
+};
+
+
+/************************************************************
+ * fdistream
+ * - a stream that reads on a file descriptor
+ ************************************************************/
+
+class fdinbuf : public std::streambuf {
+ protected:
+ int fd; // file descriptor
+ protected:
+ /* data buffer:
+ * - at most, pbSize characters in putback area plus
+ * - at most, bufSize characters in ordinary read buffer
+ */
+ static const int pbSize = 4; // size of putback area
+ static const int bufSize = 1024; // size of the data buffer
+ char buffer[bufSize+pbSize]; // data buffer
+
+ public:
+ /* constructor
+ * - initialize file descriptor
+ * - initialize empty data buffer
+ * - no putback area
+ * => force underflow()
+ */
+ fdinbuf (int _fd) : fd(_fd) {
+ setg (buffer+pbSize, // beginning of putback area
+ buffer+pbSize, // read position
+ buffer+pbSize); // end position
+ }
+
+ protected:
+ // insert new characters into the buffer
+ virtual int_type underflow () {
+#ifndef _MSC_VER
+ using std::memmove;
+#endif
+
+ // is read position before end of buffer?
+ if (gptr() < egptr()) {
+ return traits_type::to_int_type(*gptr());
+ }
+
+ /* process size of putback area
+ * - use number of characters read
+ * - but at most size of putback area
+ */
+ int numPutback;
+ numPutback = gptr() - eback();
+ if (numPutback > pbSize) {
+ numPutback = pbSize;
+ }
+
+ /* copy up to pbSize characters previously read into
+ * the putback area
+ */
+ memmove (buffer+(pbSize-numPutback), gptr()-numPutback,
+ numPutback);
+
+ // read at most bufSize new characters
+ int num;
+ num = read (fd, buffer+pbSize, bufSize);
+ if (num <= 0) {
+ // ERROR or EOF
+ return EOF;
+ }
+
+ // reset buffer pointers
+ setg (buffer+(pbSize-numPutback), // beginning of putback area
+ buffer+pbSize, // read position
+ buffer+pbSize+num); // end of buffer
+
+ // return next character
+ return traits_type::to_int_type(*gptr());
+ }
+};
+
+class fdistream : public std::istream {
+ protected:
+ fdinbuf buf;
+ public:
+ fdistream (int fd) : std::istream(0), buf(fd) {
+ rdbuf(&buf);
+ }
+};
+
+
+} // END namespace boost
+
+#endif /*BOOST_FDSTREAM_HPP*/
diff --git a/src/filters.cc b/src/filters.cc
new file mode 100644
index 00000000..e5455423
--- /dev/null
+++ b/src/filters.cc
@@ -0,0 +1,755 @@
+/*
+ * Copyright (c) 2003-2008, 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 "filters.h"
+#include "iterators.h"
+#include "compare.h"
+#include "session.h"
+#include "format.h"
+#include "textual.h"
+
+namespace ledger {
+
+pass_down_xacts::pass_down_xacts(xact_handler_ptr handler,
+ xacts_iterator& iter)
+ : item_handler<xact_t>(handler)
+{
+ TRACE_CTOR(pass_down_xacts, "xact_handler_ptr, xacts_iterator");
+
+ for (xact_t * xact = iter(); xact; xact = iter())
+ item_handler<xact_t>::operator()(*xact);
+}
+
+void truncate_entries::flush()
+{
+ if (! xacts.size())
+ return;
+
+ entry_t * last_entry = (*xacts.begin())->entry;
+
+ int l = 0;
+ foreach (xact_t * xact, xacts)
+ if (last_entry != xact->entry) {
+ l++;
+ last_entry = xact->entry;
+ }
+ l++;
+
+ last_entry = (*xacts.begin())->entry;
+
+ int i = 0;
+ foreach (xact_t * xact, xacts) {
+ if (last_entry != xact->entry) {
+ last_entry = xact->entry;
+ i++;
+ }
+
+ bool print = false;
+ if (head_count) {
+ if (head_count > 0 && i < head_count)
+ print = true;
+ else if (head_count < 0 && i >= - head_count)
+ print = true;
+ }
+
+ if (! print && tail_count) {
+ if (tail_count > 0 && l - i <= tail_count)
+ print = true;
+ else if (tail_count < 0 && l - i > - tail_count)
+ print = true;
+ }
+
+ if (print)
+ item_handler<xact_t>::operator()(*xact);
+ }
+ xacts.clear();
+
+ item_handler<xact_t>::flush();
+}
+
+void set_account_value::operator()(xact_t& xact)
+{
+ account_t * acct = xact.reported_account();
+
+ account_t::xdata_t& xdata(acct->xdata());
+ xact.add_to_value(xdata.value);
+
+ xdata.count++;
+ if (xact.has_flags(XACT_VIRTUAL))
+ xdata.virtuals++;
+
+ item_handler<xact_t>::operator()(xact);
+}
+
+void sort_xacts::post_accumulated_xacts()
+{
+ std::stable_sort(xacts.begin(), xacts.end(),
+ compare_items<xact_t>(sort_order));
+
+ foreach (xact_t * xact, xacts) {
+ xact->xdata().drop_flags(XACT_EXT_SORT_CALC);
+ item_handler<xact_t>::operator()(*xact);
+ }
+
+ xacts.clear();
+}
+
+void calc_xacts::operator()(xact_t& xact)
+{
+ try {
+ xact_t::xdata_t& xdata(xact.xdata());
+
+ if (last_xact && last_xact->has_xdata()) {
+ if (xdata.total.is_null())
+ xdata.total = last_xact->xdata().total;
+ else
+ xdata.total += last_xact->xdata().total;
+ xdata.index = last_xact->xdata().index + 1;
+ } else {
+ xdata.index = 0;
+ }
+
+ if (! xdata.has_flags(XACT_EXT_NO_TOTAL))
+ xact.add_to_value(xdata.total);
+
+ item_handler<xact_t>::operator()(xact);
+
+ last_xact = &xact;
+ }
+ catch (const std::exception& err) {
+ add_error_context("Calculating transaction at");
+#if 0
+ add_error_context(xact_context(xact));
+#endif
+ throw err;
+ }
+}
+
+void invert_xacts::operator()(xact_t& xact)
+{
+ if (xact.has_xdata() &&
+ xact.xdata().has_flags(XACT_EXT_COMPOUND)) {
+ xact.xdata().value.negate();
+ } else {
+ xact.amount.negate();
+ if (xact.cost)
+ xact.cost->negate();
+ }
+
+ item_handler<xact_t>::operator()(xact);
+}
+
+
+static inline
+void handle_value(const value_t& value,
+ account_t * account,
+ entry_t * entry,
+ unsigned int flags,
+ std::list<xact_t>& temps,
+ item_handler<xact_t>& handler,
+ const date_t& date = date_t(),
+ xacts_list * component_xacts = NULL)
+{
+ temps.push_back(xact_t(account));
+ xact_t& xact(temps.back());
+ xact.entry = entry;
+ xact.add_flags(XACT_TEMP);
+ entry->add_xact(&xact);
+
+ // If there are component xacts to associate with this
+ // temporary, do so now.
+
+ if (component_xacts)
+ xact.xdata().copy_component_xacts(*component_xacts);
+
+ // If the account for this xact is all virtual, then report
+ // the xact as such. This allows subtotal reports to show
+ // "(Account)" for accounts that contain only virtual xacts.
+
+ if (account && account->has_xdata())
+ if (! account->xdata().has_flags(ACCOUNT_EXT_HAS_NON_VIRTUALS)) {
+ xact.add_flags(XACT_VIRTUAL);
+ if (! account->xdata().has_flags(ACCOUNT_EXT_HAS_UNB_VIRTUALS))
+ xact.add_flags(XACT_BALANCE);
+ }
+
+ xact_t::xdata_t& xdata(xact.xdata());
+
+ if (is_valid(date))
+ xdata.date = date;
+
+ value_t temp(value);
+
+ switch (value.type()) {
+ case value_t::BOOLEAN:
+ case value_t::DATETIME:
+ case value_t::DATE:
+ case value_t::INTEGER:
+ temp.cast(value_t::AMOUNT);
+ // fall through...
+
+ case value_t::AMOUNT:
+ xact.amount = temp.as_amount();
+ break;
+
+ case value_t::BALANCE:
+ case value_t::BALANCE_PAIR:
+ xdata.value = temp;
+ flags |= XACT_EXT_COMPOUND;
+ break;
+
+ default:
+ assert(false); // jww (2008-04-24): What to do here?
+ break;
+ }
+
+ if (flags)
+ xdata.add_flags(flags);
+
+ handler(xact);
+}
+
+void collapse_xacts::report_subtotal()
+{
+ assert(count >= 1);
+
+ if (count == 1) {
+ item_handler<xact_t>::operator()(*last_xact);
+ } else {
+ entry_temps.push_back(entry_t());
+ entry_t& entry = entry_temps.back();
+ entry.payee = last_entry->payee;
+ entry._date = last_entry->_date;
+
+ handle_value(subtotal, &totals_account, last_entry, 0, xact_temps,
+ *handler);
+ }
+
+ last_entry = NULL;
+ last_xact = NULL;
+ subtotal = 0L;
+ count = 0;
+}
+
+void collapse_xacts::operator()(xact_t& xact)
+{
+ // If we've reached a new entry, report on the subtotal
+ // accumulated thus far.
+
+ if (last_entry && last_entry != xact.entry && count > 0)
+ report_subtotal();
+
+ xact.add_to_value(subtotal);
+ count++;
+
+ last_entry = xact.entry;
+ last_xact = &xact;
+}
+
+void related_xacts::flush()
+{
+ if (xacts.size() > 0) {
+ foreach (xact_t * xact, xacts) {
+ if (xact->entry) {
+ foreach (xact_t * r_xact, xact->entry->xacts) {
+ xact_t::xdata_t& xdata(r_xact->xdata());
+ if (! xdata.has_flags(XACT_EXT_HANDLED) &&
+ (! xdata.has_flags(XACT_EXT_RECEIVED) ?
+ ! r_xact->has_flags(XACT_AUTO | XACT_VIRTUAL) :
+ also_matching)) {
+ xdata.add_flags(XACT_EXT_HANDLED);
+ item_handler<xact_t>::operator()(*r_xact);
+ }
+ }
+ } else {
+ // This code should only be reachable from the "output"
+ // command, since that is the only command which attempts to
+ // output auto or period entries.
+ xact_t::xdata_t& xdata(xact->xdata());
+ if (! xdata.has_flags(XACT_EXT_HANDLED) &&
+ ! xact->has_flags(XACT_AUTO)) {
+ xdata.add_flags(XACT_EXT_HANDLED);
+ item_handler<xact_t>::operator()(*xact);
+ }
+ }
+ }
+ }
+
+ item_handler<xact_t>::flush();
+}
+
+void changed_value_xacts::output_diff(const date_t& date)
+{
+ value_t cur_bal;
+
+ last_xact->xdata().date = date;
+#if 0
+ compute_total(cur_bal, details_t(*last_xact));
+#endif
+ cur_bal.round();
+
+#if 0
+ // jww (2008-04-24): What does this do?
+ last_xact->xdata().date = 0;
+#endif
+
+ if (value_t diff = cur_bal - last_balance) {
+ entry_temps.push_back(entry_t());
+ entry_t& entry = entry_temps.back();
+ entry.payee = "Commodities revalued";
+ entry._date = date;
+
+ handle_value(diff, NULL, &entry, XACT_EXT_NO_TOTAL, xact_temps,
+ *handler);
+ }
+}
+
+void changed_value_xacts::operator()(xact_t& xact)
+{
+ if (last_xact)
+ output_diff(last_xact->reported_date());
+
+ if (changed_values_only)
+ xact.xdata().add_flags(XACT_EXT_DISPLAYED);
+
+ item_handler<xact_t>::operator()(xact);
+
+#if 0
+ compute_total(last_balance, details_t(xact));
+#endif
+ last_balance.round();
+
+ last_xact = &xact;
+}
+
+void component_xacts::operator()(xact_t& xact)
+{
+ if (handler && pred(xact)) {
+ if (xact.has_xdata() &&
+ xact.xdata().has_component_xacts())
+#if 0
+ xact.xdata().walk_component_xacts(*handler);
+#else
+ ;
+#endif
+ else
+ (*handler)(xact);
+ }
+}
+
+void subtotal_xacts::report_subtotal(const char * spec_fmt)
+{
+ std::ostringstream out_date;
+ if (! spec_fmt) {
+ string fmt = "- ";
+ fmt += output_date_format;
+ out_date << format_date(finish, string(fmt));
+ } else {
+ out_date << format_date(finish, string(spec_fmt));
+ }
+
+ entry_temps.push_back(entry_t());
+ entry_t& entry = entry_temps.back();
+ entry.payee = out_date.str();
+ entry._date = start;
+
+ foreach (values_map::value_type& pair, values)
+ handle_value(pair.second.value, pair.second.account, &entry, 0,
+ xact_temps, *handler, finish, &pair.second.components);
+
+ values.clear();
+}
+
+void subtotal_xacts::operator()(xact_t& xact)
+{
+ if (! is_valid(start) || xact.date() < start)
+ start = xact.date();
+ if (! is_valid(finish) || xact.date() > finish)
+ finish = xact.date();
+
+ account_t * acct = xact.reported_account();
+ assert(acct);
+
+ values_map::iterator i = values.find(acct->fullname());
+ if (i == values.end()) {
+ value_t temp;
+ xact.add_to_value(temp);
+ std::pair<values_map::iterator, bool> result
+ = values.insert(values_pair(acct->fullname(), acct_value_t(acct, temp)));
+ assert(result.second);
+
+ if (remember_components)
+ (*result.first).second.components.push_back(&xact);
+ } else {
+ xact.add_to_value((*i).second.value);
+
+ if (remember_components)
+ (*i).second.components.push_back(&xact);
+ }
+
+ // If the account for this xact is all virtual, mark it as
+ // such, so that `handle_value' can show "(Account)" for accounts
+ // that contain only virtual xacts.
+
+ if (! xact.has_flags(XACT_VIRTUAL))
+ xact.reported_account()->xdata().add_flags(ACCOUNT_EXT_HAS_NON_VIRTUALS);
+ else if (! xact.has_flags(XACT_BALANCE))
+ xact.reported_account()->xdata().add_flags(ACCOUNT_EXT_HAS_UNB_VIRTUALS);
+}
+
+void interval_xacts::report_subtotal(const date_t& date)
+{
+ assert(last_xact);
+
+ start = interval.begin;
+ if (is_valid(date))
+ finish = date - gregorian::days(1);
+ else
+ finish = last_xact->date();
+
+ subtotal_xacts::report_subtotal();
+
+ last_xact = NULL;
+}
+
+void interval_xacts::operator()(xact_t& xact)
+{
+ const date_t& date(xact.date());
+
+ if ((is_valid(interval.begin) && date < interval.begin) ||
+ (is_valid(interval.end) && date >= interval.end))
+ return;
+
+ if (interval) {
+ if (! started) {
+ if (! is_valid(interval.begin))
+ interval.start(date);
+ start = interval.begin;
+ started = true;
+ }
+
+ date_t quant = interval.increment(interval.begin);
+ if (date >= quant) {
+ if (last_xact)
+ report_subtotal(quant);
+
+ date_t temp;
+ while (date >= (temp = interval.increment(quant))) {
+ if (quant == temp)
+ break;
+ quant = temp;
+ }
+ start = interval.begin = quant;
+ }
+
+ subtotal_xacts::operator()(xact);
+ } else {
+ item_handler<xact_t>::operator()(xact);
+ }
+
+ last_xact = &xact;
+}
+
+by_payee_xacts::~by_payee_xacts()
+{
+ TRACE_DTOR(by_payee_xacts);
+
+ foreach (payee_subtotals_map::value_type& pair, payee_subtotals)
+ checked_delete(pair.second);
+}
+
+void by_payee_xacts::flush()
+{
+ foreach (payee_subtotals_map::value_type& pair, payee_subtotals)
+ pair.second->report_subtotal(pair.first.c_str());
+
+ item_handler<xact_t>::flush();
+
+ payee_subtotals.clear();
+}
+
+void by_payee_xacts::operator()(xact_t& xact)
+{
+ payee_subtotals_map::iterator i = payee_subtotals.find(xact.entry->payee);
+ if (i == payee_subtotals.end()) {
+ payee_subtotals_pair
+ temp(xact.entry->payee,
+ new subtotal_xacts(handler, remember_components));
+ std::pair<payee_subtotals_map::iterator, bool> result
+ = payee_subtotals.insert(temp);
+
+ assert(result.second);
+ if (! result.second)
+ return;
+ i = result.first;
+ }
+
+ if (xact.date() > (*i).second->start)
+ (*i).second->start = xact.date();
+
+ (*(*i).second)(xact);
+}
+
+void set_comm_as_payee::operator()(xact_t& xact)
+{
+ entry_temps.push_back(*xact.entry);
+ entry_t& entry = entry_temps.back();
+ entry._date = xact.date();
+ entry.code = xact.entry->code;
+
+ if (xact.amount.commodity())
+ entry.payee = xact.amount.commodity().symbol();
+ else
+ entry.payee = "<none>";
+
+ xact_temps.push_back(xact);
+ xact_t& temp = xact_temps.back();
+ temp.entry = &entry;
+ temp.state = xact.state;
+ temp.add_flags(XACT_TEMP);
+
+ entry.add_xact(&temp);
+
+ item_handler<xact_t>::operator()(temp);
+}
+
+void set_code_as_payee::operator()(xact_t& xact)
+{
+ entry_temps.push_back(*xact.entry);
+ entry_t& entry = entry_temps.back();
+ entry._date = xact.date();
+
+ if (xact.entry->code)
+ entry.payee = *xact.entry->code;
+ else
+ entry.payee = "<none>";
+
+ xact_temps.push_back(xact);
+ xact_t& temp = xact_temps.back();
+ temp.entry = &entry;
+ temp.state = xact.state;
+ temp.add_flags(XACT_TEMP);
+
+ entry.add_xact(&temp);
+
+ item_handler<xact_t>::operator()(temp);
+}
+
+void dow_xacts::flush()
+{
+ for (int i = 0; i < 7; i++) {
+ start = finish = date_t();
+ foreach (xact_t * xact, days_of_the_week[i])
+ subtotal_xacts::operator()(*xact);
+ subtotal_xacts::report_subtotal("%As");
+ days_of_the_week[i].clear();
+ }
+
+ subtotal_xacts::flush();
+}
+
+void generate_xacts::add_period_entries
+ (period_entries_list& period_entries)
+{
+ foreach (period_entry_t * entry, period_entries)
+ foreach (xact_t * xact, entry->xacts)
+ add_xact(entry->period, *xact);
+}
+
+void generate_xacts::add_xact(const interval_t& period,
+ xact_t& xact)
+{
+ pending_xacts.push_back(pending_xacts_pair(period, &xact));
+}
+
+void budget_xacts::report_budget_items(const date_t& date)
+{
+ if (pending_xacts.size() == 0)
+ return;
+
+ bool reported;
+ do {
+ reported = false;
+ foreach (pending_xacts_list::value_type& pair, pending_xacts) {
+ date_t& begin = pair.first.begin;
+ if (! is_valid(begin)) {
+ pair.first.start(date);
+ begin = pair.first.begin;
+ }
+
+ if (begin < date &&
+ (! is_valid(pair.first.end) || begin < pair.first.end)) {
+ xact_t& xact = *pair.second;
+
+ DEBUG("ledger.walk.budget", "Reporting budget for "
+ << xact.reported_account()->fullname());
+
+ entry_temps.push_back(entry_t());
+ entry_t& entry = entry_temps.back();
+ entry.payee = "Budget entry";
+ entry._date = begin;
+
+ xact_temps.push_back(xact);
+ xact_t& temp = xact_temps.back();
+ temp.entry = &entry;
+ temp.add_flags(XACT_AUTO | XACT_TEMP);
+ temp.amount.negate();
+ entry.add_xact(&temp);
+
+ begin = pair.first.increment(begin);
+
+ item_handler<xact_t>::operator()(temp);
+
+ reported = true;
+ }
+ }
+ } while (reported);
+}
+
+void budget_xacts::operator()(xact_t& xact)
+{
+ bool xact_in_budget = false;
+
+ foreach (pending_xacts_list::value_type& pair, pending_xacts)
+ for (account_t * acct = xact.reported_account();
+ acct;
+ acct = acct->parent) {
+ if (acct == (*pair.second).reported_account()) {
+ xact_in_budget = true;
+ // Report the xact as if it had occurred in the parent
+ // account.
+ if (xact.reported_account() != acct)
+ xact.xdata().account = acct;
+ goto handle;
+ }
+ }
+
+ handle:
+ if (xact_in_budget && flags & BUDGET_BUDGETED) {
+ report_budget_items(xact.date());
+ item_handler<xact_t>::operator()(xact);
+ }
+ else if (! xact_in_budget && flags & BUDGET_UNBUDGETED) {
+ item_handler<xact_t>::operator()(xact);
+ }
+}
+
+void forecast_xacts::add_xact(const interval_t& period, xact_t& xact)
+{
+ generate_xacts::add_xact(period, xact);
+
+ interval_t& i = pending_xacts.back().first;
+ if (! is_valid(i.begin)) {
+ i.start(current_date);
+ i.begin = i.increment(i.begin);
+ } else {
+ while (i.begin < current_date)
+ i.begin = i.increment(i.begin);
+ }
+}
+
+void forecast_xacts::flush()
+{
+ xacts_list passed;
+ date_t last;
+
+ while (pending_xacts.size() > 0) {
+ pending_xacts_list::iterator least = pending_xacts.begin();
+ for (pending_xacts_list::iterator i = ++pending_xacts.begin();
+ i != pending_xacts.end();
+ i++)
+ if ((*i).first.begin < (*least).first.begin)
+ least = i;
+
+ date_t& begin = (*least).first.begin;
+
+ if (is_valid((*least).first.end) && begin >= (*least).first.end) {
+ pending_xacts.erase(least);
+ passed.remove((*least).second);
+ continue;
+ }
+
+ xact_t& xact = *(*least).second;
+
+ entry_temps.push_back(entry_t());
+ entry_t& entry = entry_temps.back();
+ entry.payee = "Forecast entry";
+ entry._date = begin;
+
+ xact_temps.push_back(xact);
+ xact_t& temp = xact_temps.back();
+ temp.entry = &entry;
+ temp.add_flags(XACT_AUTO | XACT_TEMP);
+ entry.add_xact(&temp);
+
+ date_t next = (*least).first.increment(begin);
+ if (next < begin || (is_valid(last) && (next - last).days() > 365 * 5))
+ break;
+ begin = next;
+
+ item_handler<xact_t>::operator()(temp);
+
+ if (temp.has_xdata() &&
+ temp.xdata().has_flags(XACT_EXT_MATCHES)) {
+ if (! pred(temp))
+ break;
+ last = temp.date();
+ passed.clear();
+ } else {
+ bool found = false;
+ foreach (xact_t * x, passed)
+ if (x == &xact) {
+ found = true;
+ break;
+ }
+
+ if (! found) {
+ passed.push_back(&xact);
+ if (passed.size() >= pending_xacts.size())
+ break;
+ }
+ }
+ }
+
+ item_handler<xact_t>::flush();
+}
+
+pass_down_accounts::pass_down_accounts(acct_handler_ptr handler,
+ accounts_iterator& iter)
+ : item_handler<account_t>(handler)
+{
+ TRACE_CTOR(pass_down_accounts,
+ "acct_handler_ptr, accounts_iterator");
+ for (account_t * account = iter(); account; account = iter())
+ item_handler<account_t>::operator()(*account);
+}
+
+} // namespace ledger
diff --git a/src/filters.h b/src/filters.h
new file mode 100644
index 00000000..7f67cbd8
--- /dev/null
+++ b/src/filters.h
@@ -0,0 +1,702 @@
+/*
+ * Copyright (c) 2003-2008, 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 _FILTERS_H
+#define _FILTERS_H
+
+#include "handler.h"
+#include "predicate.h"
+#include "entry.h"
+
+namespace ledger {
+
+//////////////////////////////////////////////////////////////////////
+//
+// Transaction filters
+//
+
+class ignore_xacts : public item_handler<xact_t>
+{
+public:
+ virtual void operator()(xact_t&) {}
+};
+
+class clear_xact_xdata : public item_handler<xact_t>
+{
+public:
+ virtual void operator()(xact_t& xact) {
+ xact.clear_xdata();
+ }
+};
+
+class xacts_iterator;
+
+class pass_down_xacts : public item_handler<xact_t>
+{
+ pass_down_xacts();
+
+public:
+ pass_down_xacts(xact_handler_ptr handler, xacts_iterator& iter);
+
+ virtual ~pass_down_xacts() {
+ TRACE_DTOR(pass_down_xacts);
+ }
+};
+
+class push_to_xacts_list : public item_handler<xact_t>
+{
+ push_to_xacts_list();
+
+public:
+ xacts_list& xacts;
+
+ push_to_xacts_list(xacts_list& _xacts) : xacts(_xacts) {
+ TRACE_CTOR(push_to_xacts_list, "xacts_list&");
+ }
+ virtual ~push_to_xacts_list() {
+ TRACE_DTOR(push_to_xacts_list);
+ }
+
+ virtual void operator()(xact_t& xact) {
+ xacts.push_back(&xact);
+ }
+};
+
+class truncate_entries : public item_handler<xact_t>
+{
+ int head_count;
+ int tail_count;
+
+ xacts_list xacts;
+
+ truncate_entries();
+
+public:
+ truncate_entries(xact_handler_ptr handler,
+ int _head_count, int _tail_count)
+ : item_handler<xact_t>(handler),
+ head_count(_head_count), tail_count(_tail_count) {
+ TRACE_CTOR(truncate_entries, "xact_handler_ptr, int, int");
+ }
+ virtual ~truncate_entries() {
+ TRACE_DTOR(truncate_entries);
+ }
+
+ virtual void flush();
+ virtual void operator()(xact_t& xact) {
+ if (tail_count == 0 && head_count > 0 &&
+ xacts.size() >= static_cast<unsigned int>(head_count))
+ return;
+ xacts.push_back(&xact);
+ }
+};
+
+class set_account_value : public item_handler<xact_t>
+{
+public:
+ set_account_value(xact_handler_ptr handler = xact_handler_ptr())
+ : item_handler<xact_t>(handler) {}
+
+ virtual void operator()(xact_t& xact);
+};
+
+class sort_xacts : public item_handler<xact_t>
+{
+ typedef std::deque<xact_t *> xacts_deque;
+
+ xacts_deque xacts;
+ const expr_t sort_order;
+
+ sort_xacts();
+
+public:
+ sort_xacts(xact_handler_ptr handler,
+ const expr_t& _sort_order)
+ : item_handler<xact_t>(handler),
+ sort_order(_sort_order) {
+ TRACE_CTOR(sort_xacts,
+ "xact_handler_ptr, const value_expr&");
+ }
+ sort_xacts(xact_handler_ptr handler,
+ const string& _sort_order)
+ : item_handler<xact_t>(handler),
+ sort_order(_sort_order) {
+ TRACE_CTOR(sort_xacts,
+ "xact_handler_ptr, const string&");
+ }
+ virtual ~sort_xacts() {
+ TRACE_DTOR(sort_xacts);
+ }
+
+ virtual void post_accumulated_xacts();
+
+ virtual void flush() {
+ post_accumulated_xacts();
+ item_handler<xact_t>::flush();
+ }
+
+ virtual void operator()(xact_t& xact) {
+ xacts.push_back(&xact);
+ }
+};
+
+class sort_entries : public item_handler<xact_t>
+{
+ sort_xacts sorter;
+ entry_t * last_entry;
+
+ sort_entries();
+
+public:
+ sort_entries(xact_handler_ptr handler,
+ const expr_t& _sort_order)
+ : sorter(handler, _sort_order) {
+ TRACE_CTOR(sort_entries,
+ "xact_handler_ptr, const value_expr&");
+ }
+ sort_entries(xact_handler_ptr handler,
+ const string& _sort_order)
+ : sorter(handler, _sort_order) {
+ TRACE_CTOR(sort_entries,
+ "xact_handler_ptr, const string&");
+ }
+ virtual ~sort_entries() {
+ TRACE_DTOR(sort_entries);
+ }
+
+ virtual void flush() {
+ sorter.flush();
+ item_handler<xact_t>::flush();
+ }
+
+ virtual void operator()(xact_t& xact) {
+ if (last_entry && xact.entry != last_entry)
+ sorter.post_accumulated_xacts();
+
+ sorter(xact);
+
+ last_entry = xact.entry;
+ }
+};
+
+class filter_xacts : public item_handler<xact_t>
+{
+ item_predicate<xact_t> pred;
+
+ filter_xacts();
+
+public:
+ filter_xacts(xact_handler_ptr handler,
+ const expr_t& predicate)
+ : item_handler<xact_t>(handler), pred(predicate) {
+ TRACE_CTOR(filter_xacts,
+ "xact_handler_ptr, const value_expr&");
+ }
+
+ filter_xacts(xact_handler_ptr handler,
+ const string& predicate)
+ : item_handler<xact_t>(handler), pred(predicate) {
+ TRACE_CTOR(filter_xacts,
+ "xact_handler_ptr, const string&");
+ }
+ virtual ~filter_xacts() {
+ TRACE_DTOR(filter_xacts);
+ }
+
+ virtual void operator()(xact_t& xact) {
+ if (pred(xact)) {
+ xact.xdata().add_flags(XACT_EXT_MATCHES);
+ (*handler)(xact);
+ }
+ }
+};
+
+class calc_xacts : public item_handler<xact_t>
+{
+ xact_t * last_xact;
+
+ calc_xacts();
+
+public:
+ calc_xacts(xact_handler_ptr handler)
+ : item_handler<xact_t>(handler), last_xact(NULL) {
+ TRACE_CTOR(calc_xacts, "xact_handler_ptr");
+ }
+ virtual ~calc_xacts() {
+ TRACE_DTOR(calc_xacts);
+ }
+
+ virtual void operator()(xact_t& xact);
+};
+
+class invert_xacts : public item_handler<xact_t>
+{
+ invert_xacts();
+
+public:
+ invert_xacts(xact_handler_ptr handler)
+ : item_handler<xact_t>(handler) {}
+
+ virtual void operator()(xact_t& xact);
+};
+
+inline void clear_entries_xacts(std::list<entry_t>& entries_list) {
+ foreach (entry_t& entry, entries_list)
+ entry.xacts.clear();
+}
+
+class collapse_xacts : public item_handler<xact_t>
+{
+ value_t subtotal;
+ unsigned int count;
+ entry_t * last_entry;
+ xact_t * last_xact;
+ account_t totals_account;
+
+ std::list<entry_t> entry_temps;
+ std::list<xact_t> xact_temps;
+
+ collapse_xacts();
+
+public:
+ collapse_xacts(xact_handler_ptr handler)
+ : item_handler<xact_t>(handler), count(0),
+ last_entry(NULL), last_xact(NULL),
+ totals_account(NULL, "<Total>") {
+ TRACE_CTOR(collapse_xacts, "xact_handler_ptr");
+ }
+ virtual ~collapse_xacts() {
+ TRACE_DTOR(collapse_xacts);
+ clear_entries_xacts(entry_temps);
+ }
+
+ virtual void flush() {
+ if (subtotal)
+ report_subtotal();
+ item_handler<xact_t>::flush();
+ }
+
+ void report_subtotal();
+
+ virtual void operator()(xact_t& xact);
+};
+
+class component_xacts : public item_handler<xact_t>
+{
+ item_predicate<xact_t> pred;
+
+ component_xacts();
+
+public:
+ component_xacts(xact_handler_ptr handler,
+ const expr_t& predicate)
+ : item_handler<xact_t>(handler), pred(predicate) {
+ TRACE_CTOR(component_xacts,
+ "xact_handler_ptr, const value_expr&");
+ }
+ component_xacts(xact_handler_ptr handler,
+ const string& predicate)
+ : item_handler<xact_t>(handler), pred(predicate) {
+ TRACE_CTOR(component_xacts,
+ "xact_handler_ptr, const string&");
+ }
+ virtual ~component_xacts() throw() {
+ TRACE_DTOR(component_xacts);
+ }
+
+ virtual void operator()(xact_t& xact);
+};
+
+class related_xacts : public item_handler<xact_t>
+{
+ xacts_list xacts;
+ bool also_matching;
+
+ related_xacts();
+
+public:
+ related_xacts(xact_handler_ptr handler,
+ const bool _also_matching = false)
+ : item_handler<xact_t>(handler),
+ also_matching(_also_matching) {
+ TRACE_CTOR(related_xacts,
+ "xact_handler_ptr, const bool");
+ }
+ virtual ~related_xacts() throw() {
+ TRACE_DTOR(related_xacts);
+ }
+
+ virtual void flush();
+ virtual void operator()(xact_t& xact) {
+ xact.xdata().add_flags(XACT_EXT_RECEIVED);
+ xacts.push_back(&xact);
+ }
+};
+
+class changed_value_xacts : public item_handler<xact_t>
+{
+ // This filter requires that calc_xacts be used at some point
+ // later in the chain.
+
+ bool changed_values_only;
+ xact_t * last_xact;
+ value_t last_balance;
+
+ std::list<entry_t> entry_temps;
+ std::list<xact_t> xact_temps;
+
+ changed_value_xacts();
+
+public:
+ changed_value_xacts(xact_handler_ptr handler,
+ bool _changed_values_only)
+ : item_handler<xact_t>(handler),
+ changed_values_only(_changed_values_only), last_xact(NULL) {
+ TRACE_CTOR(changed_value_xacts,
+ "xact_handler_ptr, bool");
+ }
+ virtual ~changed_value_xacts() {
+ TRACE_DTOR(changed_value_xacts);
+ clear_entries_xacts(entry_temps);
+ }
+
+ virtual void flush() {
+ if (last_xact) {
+ output_diff(current_date);
+ last_xact = NULL;
+ }
+ item_handler<xact_t>::flush();
+ }
+
+ void output_diff(const date_t& current);
+
+ virtual void operator()(xact_t& xact);
+};
+
+class subtotal_xacts : public item_handler<xact_t>
+{
+ class acct_value_t
+ {
+ acct_value_t();
+
+ public:
+ account_t * account;
+ value_t value;
+
+ xacts_list components;
+
+ acct_value_t(account_t * a) : account(a) {
+ TRACE_CTOR(acct_value_t, "acount_t *");
+ }
+ acct_value_t(account_t * a, value_t& v) : account(a), value(v) {
+ TRACE_CTOR(acct_value_t, "acount_t *, value_t&");
+ }
+ acct_value_t(const acct_value_t& av)
+ : account(av.account), value(av.value),
+ components(av.components) {
+ TRACE_CTOR(acct_value_t, "copy");
+ }
+ ~acct_value_t() throw() {
+ TRACE_DTOR(acct_value_t);
+ }
+ };
+
+ typedef std::map<string, acct_value_t> values_map;
+ typedef std::pair<string, acct_value_t> values_pair;
+
+ subtotal_xacts();
+
+protected:
+ values_map values;
+ bool remember_components;
+
+ std::list<entry_t> entry_temps;
+ std::list<xact_t> xact_temps;
+
+public:
+ date_t start;
+ date_t finish;
+
+ subtotal_xacts(xact_handler_ptr handler,
+ bool _remember_components = false)
+ : item_handler<xact_t>(handler),
+ remember_components(_remember_components) {
+ TRACE_CTOR(subtotal_xacts,
+ "xact_handler_ptr, bool");
+ }
+ virtual ~subtotal_xacts() {
+ TRACE_DTOR(subtotal_xacts);
+ clear_entries_xacts(entry_temps);
+ }
+
+ void report_subtotal(const char * spec_fmt = NULL);
+
+ virtual void flush() {
+ if (values.size() > 0)
+ report_subtotal();
+ item_handler<xact_t>::flush();
+ }
+ virtual void operator()(xact_t& xact);
+};
+
+class interval_xacts : public subtotal_xacts
+{
+ interval_t interval;
+ xact_t * last_xact;
+ bool started;
+
+ interval_xacts();
+
+public:
+ interval_xacts(xact_handler_ptr _handler,
+ const interval_t& _interval,
+ bool remember_components = false)
+ : subtotal_xacts(_handler, remember_components),
+ interval(_interval), last_xact(NULL), started(false) {
+ TRACE_CTOR(interval_xacts,
+ "xact_handler_ptr, const interval_t&, bool");
+ }
+ interval_xacts(xact_handler_ptr _handler,
+ const string& _interval,
+ bool remember_components = false)
+ : subtotal_xacts(_handler, remember_components),
+ interval(_interval), last_xact(NULL), started(false) {
+ TRACE_CTOR(interval_xacts,
+ "xact_handler_ptr, const string&, bool");
+ }
+ virtual ~interval_xacts() throw() {
+ TRACE_DTOR(interval_xacts);
+ }
+
+ void report_subtotal(const date_t& moment = date_t());
+
+ virtual void flush() {
+ if (last_xact)
+ report_subtotal();
+ subtotal_xacts::flush();
+ }
+ virtual void operator()(xact_t& xact);
+};
+
+class by_payee_xacts : public item_handler<xact_t>
+{
+ typedef std::map<string, subtotal_xacts *> payee_subtotals_map;
+ typedef std::pair<string, subtotal_xacts *> payee_subtotals_pair;
+
+ payee_subtotals_map payee_subtotals;
+ bool remember_components;
+
+ by_payee_xacts();
+
+ public:
+ by_payee_xacts(xact_handler_ptr handler,
+ bool _remember_components = false)
+ : item_handler<xact_t>(handler),
+ remember_components(_remember_components) {
+ TRACE_CTOR(by_payee_xacts,
+ "xact_handler_ptr, bool");
+ }
+ virtual ~by_payee_xacts();
+
+ virtual void flush();
+ virtual void operator()(xact_t& xact);
+};
+
+class set_comm_as_payee : public item_handler<xact_t>
+{
+ std::list<entry_t> entry_temps;
+ std::list<xact_t> xact_temps;
+
+ set_comm_as_payee();
+
+public:
+ set_comm_as_payee(xact_handler_ptr handler)
+ : item_handler<xact_t>(handler) {
+ TRACE_CTOR(set_comm_as_payee, "xact_handler_ptr");
+ }
+ virtual ~set_comm_as_payee() {
+ TRACE_DTOR(set_comm_as_payee);
+ clear_entries_xacts(entry_temps);
+ }
+
+ virtual void operator()(xact_t& xact);
+};
+
+class set_code_as_payee : public item_handler<xact_t>
+{
+ std::list<entry_t> entry_temps;
+ std::list<xact_t> xact_temps;
+
+ set_code_as_payee();
+
+public:
+ set_code_as_payee(xact_handler_ptr handler)
+ : item_handler<xact_t>(handler) {
+ TRACE_CTOR(set_code_as_payee, "xact_handler_ptr");
+ }
+ virtual ~set_code_as_payee() {
+ TRACE_DTOR(set_code_as_payee);
+ clear_entries_xacts(entry_temps);
+ }
+
+ virtual void operator()(xact_t& xact);
+};
+
+class dow_xacts : public subtotal_xacts
+{
+ xacts_list days_of_the_week[7];
+
+ dow_xacts();
+
+public:
+ dow_xacts(xact_handler_ptr handler,
+ bool remember_components = false)
+ : subtotal_xacts(handler, remember_components) {
+ TRACE_CTOR(dow_xacts, "xact_handler_ptr, bool");
+ }
+ virtual ~dow_xacts() throw() {
+ TRACE_DTOR(dow_xacts);
+ }
+
+ virtual void flush();
+ virtual void operator()(xact_t& xact) {
+ days_of_the_week[xact.date().day_of_week()].push_back(&xact);
+ }
+};
+
+class generate_xacts : public item_handler<xact_t>
+{
+ generate_xacts();
+
+protected:
+ typedef std::pair<interval_t, xact_t *> pending_xacts_pair;
+ typedef std::list<pending_xacts_pair> pending_xacts_list;
+
+ pending_xacts_list pending_xacts;
+ std::list<entry_t> entry_temps;
+ std::list<xact_t> xact_temps;
+
+public:
+ generate_xacts(xact_handler_ptr handler)
+ : item_handler<xact_t>(handler) {
+ TRACE_CTOR(dow_xacts, "xact_handler_ptr");
+ }
+
+ virtual ~generate_xacts() {
+ TRACE_DTOR(generate_xacts);
+ clear_entries_xacts(entry_temps);
+ }
+
+ void add_period_entries(period_entries_list& period_entries);
+
+ virtual void add_xact(const interval_t& period, xact_t& xact);
+};
+
+class budget_xacts : public generate_xacts
+{
+#define BUDGET_NO_BUDGET 0x00
+#define BUDGET_BUDGETED 0x01
+#define BUDGET_UNBUDGETED 0x02
+
+ unsigned short flags;
+
+ budget_xacts();
+
+public:
+ budget_xacts(xact_handler_ptr handler,
+ unsigned long _flags = BUDGET_BUDGETED)
+ : generate_xacts(handler), flags(_flags) {
+ TRACE_CTOR(budget_xacts,
+ "xact_handler_ptr, unsigned long");
+ }
+ virtual ~budget_xacts() throw() {
+ TRACE_DTOR(budget_xacts);
+ }
+
+ void report_budget_items(const date_t& date);
+
+ virtual void operator()(xact_t& xact);
+};
+
+class forecast_xacts : public generate_xacts
+{
+ item_predicate<xact_t> pred;
+
+ public:
+ forecast_xacts(xact_handler_ptr handler,
+ const expr_t& predicate)
+ : generate_xacts(handler), pred(predicate) {
+ TRACE_CTOR(forecast_xacts, "xact_handler_ptr, const expr_t&");
+ }
+ forecast_xacts(xact_handler_ptr handler,
+ const string& predicate)
+ : generate_xacts(handler), pred(predicate) {
+ TRACE_CTOR(forecast_xacts, "xact_handler_ptr, const string&");
+ }
+ virtual ~forecast_xacts() throw() {
+ TRACE_DTOR(forecast_xacts);
+ }
+
+ virtual void add_xact(const interval_t& period,
+ xact_t& xact);
+ virtual void flush();
+};
+
+//////////////////////////////////////////////////////////////////////
+//
+// Account filters
+//
+
+class clear_account_xdata : public item_handler<account_t>
+{
+public:
+ virtual void operator()(account_t& acct) {
+ acct.clear_xdata();
+ }
+};
+
+class accounts_iterator;
+
+class pass_down_accounts : public item_handler<account_t>
+{
+ pass_down_accounts();
+
+public:
+ pass_down_accounts(acct_handler_ptr handler, accounts_iterator& iter);
+
+ virtual ~pass_down_accounts() {
+ TRACE_DTOR(pass_down_accounts);
+ }
+};
+
+} // namespace ledger
+
+#endif // _FILTERS_H
diff --git a/src/flags.h b/src/flags.h
new file mode 100644
index 00000000..b75fdc21
--- /dev/null
+++ b/src/flags.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2003-2008, 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 _FLAGS_H
+#define _FLAGS_H
+
+template <typename T = boost::uint_least8_t>
+class supports_flags
+{
+public:
+ typedef T flags_t;
+
+protected:
+ flags_t flags_;
+
+public:
+ supports_flags() : flags_(0) {
+ TRACE_CTOR(supports_flags, "");
+ }
+ supports_flags(const flags_t& arg) : flags_(arg) {
+ TRACE_CTOR(supports_flags, "copy");
+ }
+ ~supports_flags() throw() {
+ TRACE_DTOR(supports_flags);
+ }
+
+ flags_t flags() const {
+ return flags_;
+ }
+ bool has_flags(const flags_t arg) const {
+ return flags_ & arg;
+ }
+
+ void set_flags(const flags_t arg) {
+ flags_ = arg;
+ }
+ void clear_flags() {
+ flags_ = 0;
+ }
+ void add_flags(const flags_t arg) {
+ flags_ |= arg;
+ }
+ void drop_flags(const flags_t arg) {
+ flags_ &= ~arg;
+ }
+};
+
+template <typename T = boost::uint_least8_t>
+class delegates_flags : public boost::noncopyable
+{
+public:
+ typedef T flags_t;
+
+protected:
+ supports_flags<T>& flags_;
+
+public:
+ delegates_flags() : flags_() {
+ TRACE_CTOR(delegates_flags, "");
+ }
+ delegates_flags(supports_flags<T>& arg) : flags_(arg) {
+ TRACE_CTOR(delegates_flags, "const supports_flags<T>&");
+ }
+ ~delegates_flags() throw() {
+ TRACE_DTOR(delegates_flags);
+ }
+
+ flags_t flags() const {
+ return flags_.flags();
+ }
+ bool has_flags(const flags_t arg) const {
+ return flags_.has_flags(arg);
+ }
+
+ void set_flags(const flags_t arg) {
+ flags_.set_flags(arg);
+ }
+ void clear_flags() {
+ flags_.clear_flags();
+ }
+ void add_flags(const flags_t arg) {
+ flags_.add_flags(arg);
+ }
+ void drop_flags(const flags_t arg) {
+ flags_.drop_flags(arg);
+ }
+};
+
+#endif // _FLAGS_H
diff --git a/src/format.cc b/src/format.cc
new file mode 100644
index 00000000..65dfce95
--- /dev/null
+++ b/src/format.cc
@@ -0,0 +1,391 @@
+/*
+ * Copyright (c) 2003-2008, 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 "format.h"
+#include "account.h"
+
+namespace ledger {
+
+format_t::elision_style_t
+ format_t::elision_style = ABBREVIATE;
+int format_t::abbrev_length = 2;
+
+bool format_t::ansi_codes = false;
+bool format_t::ansi_invert = false;
+
+void format_t::element_t::dump(std::ostream& out) const
+{
+ out << "Element: ";
+
+ switch (type) {
+ case STRING: out << " STRING"; break;
+ case EXPR: out << " EXPR"; break;
+ }
+
+ out << " flags: " << int(flags);
+ out << " min: ";
+ out << std::right;
+ out.width(2);
+ out << int(min_width);
+ out << " max: ";
+ out << std::right;
+ out.width(2);
+ out << int(max_width);
+
+ switch (type) {
+ case STRING: out << " str: '" << chars << "'" << std::endl; break;
+ case EXPR: out << " expr: " << expr << std::endl; break;
+ }
+}
+
+namespace {
+ string partial_account_name(account_t& account)
+ {
+ string name;
+
+ for (account_t * acct = &account;
+ acct && acct->parent;
+ acct = acct->parent) {
+ if (acct->has_xdata() &&
+ acct->xdata().has_flags(ACCOUNT_EXT_DISPLAYED))
+ break;
+
+ if (name.empty())
+ name = acct->name;
+ else
+ name = acct->name + ":" + name;
+ }
+
+ return name;
+ }
+}
+
+format_t::element_t * format_t::parse_elements(const string& fmt)
+{
+ std::auto_ptr<element_t> result;
+
+ element_t * current = NULL;
+
+ char buf[1024];
+ char * q = buf;
+
+ // The following format codes need to be implemented as functions:
+ //
+ // d: COMPLETE_DATE_STRING
+ // D: DATE_STRING
+ // S: SOURCE; break
+ // B: ENTRY_BEG_POS
+ // b: ENTRY_BEG_LINE
+ // E: ENTRY_END_POS
+ // e: ENTRY_END_LINE
+ // X: CLEARED
+ // Y: ENTRY_CLEARED
+ // C: CODE
+ // P: PAYEE
+ // W: OPT_ACCOUNT
+ // a: ACCOUNT_NAME
+ // A: ACCOUNT_FULLNAME
+ // t: AMOUNT
+ // o: OPT_AMOUNT
+ // T: TOTAL
+ // N: NOTE
+ // n: OPT_NOTE
+ // _: DEPTH_SPACER
+ //
+ // xB: XACT_BEG_POS
+ // xb: XACT_BEG_LINE
+ // xE: XACT_END_POS
+ // xe: XACT_END_LINE
+
+ for (const char * p = fmt.c_str(); *p; p++) {
+ if (*p != '%' && *p != '\\') {
+ *q++ = *p;
+ continue;
+ }
+
+ if (! result.get()) {
+ result.reset(new element_t);
+ current = result.get();
+ } else {
+ current->next.reset(new element_t);
+ current = current->next.get();
+ }
+
+ if (q != buf) {
+ current->type = element_t::STRING;
+ current->chars = string(buf, q);
+ q = buf;
+
+ current->next.reset(new element_t);
+ current = current->next.get();
+ }
+
+ if (*p == '\\') {
+ p++;
+ current->type = element_t::STRING;
+ switch (*p) {
+ case 'b': current->chars = "\b"; break;
+ case 'f': current->chars = "\f"; break;
+ case 'n': current->chars = "\n"; break;
+ case 'r': current->chars = "\r"; break;
+ case 't': current->chars = "\t"; break;
+ case 'v': current->chars = "\v"; break;
+ }
+ continue;
+ }
+
+ ++p;
+ while (*p == '!' || *p == '-') {
+ switch (*p) {
+ case '-':
+ current->flags |= ELEMENT_ALIGN_LEFT;
+ break;
+ case '!':
+ current->flags |= ELEMENT_HIGHLIGHT;
+ break;
+ }
+ ++p;
+ }
+
+ int num = 0;
+ while (*p && std::isdigit(*p)) {
+ num *= 10;
+ num += *p++ - '0';
+ }
+ current->min_width = num;
+
+ if (*p == '.') {
+ ++p;
+ num = 0;
+ while (*p && std::isdigit(*p)) {
+ num *= 10;
+ num += *p++ - '0';
+ }
+ current->max_width = num;
+ if (current->min_width == 0)
+ current->min_width = current->max_width;
+ }
+
+ switch (*p) {
+ case '%':
+ current->type = element_t::STRING;
+ current->chars = "%";
+ break;
+
+ case '|':
+ current->type = element_t::STRING;
+ current->chars = " ";
+ break;
+
+ case '(':
+ case '[': {
+ std::istringstream str(p);
+ current->type = element_t::EXPR;
+ current->expr.parse(str);
+ current->expr.set_text(string(p, p + str.tellg()));
+ p += str.tellg();
+ break;
+ }
+
+ default: {
+ current->type = element_t::EXPR;
+ char buf[2];
+ buf[0] = *p;
+ buf[1] = '\0';
+ current->chars = buf;
+ current->expr.parse(string("fmt_") + *p);
+ break;
+ }
+ }
+ }
+
+ if (q != buf) {
+ if (! result.get()) {
+ result.reset(new element_t);
+ current = result.get();
+ } else {
+ current->next.reset(new element_t);
+ current = current->next.get();
+ }
+ current->type = element_t::STRING;
+ current->chars = string(buf, q);
+ }
+
+ return result.release();
+}
+
+namespace {
+ inline void mark_plain(std::ostream& out) {
+ out << "\e[0m";
+ }
+}
+
+void format_t::format(std::ostream& out_str, scope_t& scope)
+{
+ for (element_t * elem = elements.get(); elem; elem = elem->next.get()) {
+ std::ostringstream out;
+ string name;
+ bool ignore_max_width = false;
+
+ if (elem->flags & ELEMENT_ALIGN_LEFT)
+ out << std::left;
+ else
+ out << std::right;
+
+ if (elem->min_width > 0)
+ out.width(elem->min_width);
+
+ switch (elem->type) {
+ case element_t::STRING:
+ out << elem->chars;
+ break;
+
+ case element_t::EXPR:
+ try {
+ elem->expr.compile(scope);
+
+ value_t value;
+ if (elem->expr.is_function()) {
+ call_scope_t args(scope);
+ args.push_back(long(elem->max_width));
+ value = elem->expr.get_function()(args);
+ } else {
+ value = elem->expr.calc(scope);
+ }
+ value.strip_annotations().dump(out, elem->min_width);
+ }
+ catch (const calc_error&) {
+ out << (string("%") + elem->chars);
+ }
+ break;
+
+ default:
+ assert(false);
+ break;
+ }
+
+ string temp = out.str();
+
+ if (! ignore_max_width &&
+ elem->max_width > 0 && elem->max_width < temp.length())
+ out_str << truncate(temp, elem->max_width);
+ else
+ out_str << temp;
+ }
+}
+
+string format_t::truncate(const string& str, unsigned int width,
+ const bool is_account)
+{
+ const unsigned int len = str.length();
+ if (len <= width)
+ return str;
+
+ assert(width < 4095);
+
+ char buf[4096];
+
+ switch (elision_style) {
+ case TRUNCATE_LEADING:
+ // This method truncates at the beginning.
+ std::strncpy(buf, str.c_str() + (len - width), width);
+ buf[0] = '.';
+ buf[1] = '.';
+ break;
+
+ case TRUNCATE_MIDDLE:
+ // This method truncates in the middle.
+ std::strncpy(buf, str.c_str(), width / 2);
+ std::strncpy(buf + width / 2,
+ str.c_str() + (len - (width / 2 + width % 2)),
+ width / 2 + width % 2);
+ buf[width / 2 - 1] = '.';
+ buf[width / 2] = '.';
+ break;
+
+ case ABBREVIATE:
+ if (is_account) {
+ std::list<string> parts;
+ string::size_type beg = 0;
+ for (string::size_type pos = str.find(':');
+ pos != string::npos;
+ beg = pos + 1, pos = str.find(':', beg))
+ parts.push_back(string(str, beg, pos - beg));
+ parts.push_back(string(str, beg));
+
+ string result;
+ unsigned int newlen = len;
+ for (std::list<string>::iterator i = parts.begin();
+ i != parts.end();
+ i++) {
+ // Don't contract the last element
+ std::list<string>::iterator x = i;
+ if (++x == parts.end()) {
+ result += *i;
+ break;
+ }
+
+ if (newlen > width) {
+ result += string(*i, 0, abbrev_length);
+ result += ":";
+ newlen -= (*i).length() - abbrev_length;
+ } else {
+ result += *i;
+ result += ":";
+ }
+ }
+
+ if (newlen > width) {
+ // Even abbreviated its too big to show the last account, so
+ // abbreviate all but the last and truncate at the beginning.
+ std::strncpy(buf, result.c_str() + (result.length() - width), width);
+ buf[0] = '.';
+ buf[1] = '.';
+ } else {
+ std::strcpy(buf, result.c_str());
+ }
+ break;
+ }
+ // fall through...
+
+ case TRUNCATE_TRAILING:
+ // This method truncates at the end (the default).
+ std::strncpy(buf, str.c_str(), width - 2);
+ buf[width - 2] = '.';
+ buf[width - 1] = '.';
+ break;
+ }
+ buf[width] = '\0';
+
+ return buf;
+}
+
+} // namespace ledger
diff --git a/src/format.h b/src/format.h
new file mode 100644
index 00000000..31f2bb1d
--- /dev/null
+++ b/src/format.h
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2003-2008, 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 _FORMAT_H
+#define _FORMAT_H
+
+#include "journal.h"
+#include "expr.h"
+
+namespace ledger {
+
+DECLARE_EXCEPTION(format_error, std::runtime_error);
+
+class format_t : public noncopyable
+{
+ struct element_t : public noncopyable
+ {
+#define ELEMENT_ALIGN_LEFT 0x01
+#define ELEMENT_HIGHLIGHT 0x02
+
+ enum kind_t {
+ STRING,
+ EXPR,
+#if 0
+ DEPTH_SPACER
+#endif
+ };
+
+ kind_t type;
+ unsigned char flags;
+ unsigned char min_width;
+ unsigned char max_width;
+ string chars;
+ expr_t expr;
+
+ scoped_ptr<struct element_t> next;
+
+ element_t() throw()
+ : type(STRING), flags(false), min_width(0), max_width(0) {
+ TRACE_CTOR(element_t, "");
+ }
+ ~element_t() throw() {
+ TRACE_DTOR(element_t);
+ }
+
+ friend inline void mark_red(std::ostream& out, const element_t * elem) {
+ out.setf(std::ios::left);
+ out.width(0);
+ out << "\e[31m";
+
+ if (elem->flags & ELEMENT_ALIGN_LEFT)
+ out << std::left;
+ else
+ out << std::right;
+
+ if (elem->min_width > 0)
+ out.width(elem->min_width);
+ }
+
+ void dump(std::ostream& out) const;
+ };
+
+ string format_string;
+ scoped_ptr<element_t> elements;
+
+public:
+ enum elision_style_t {
+ TRUNCATE_TRAILING,
+ TRUNCATE_MIDDLE,
+ TRUNCATE_LEADING,
+ ABBREVIATE
+ };
+
+private:
+ // jww (2008-08-02): Should these four be here, or in session_t?
+ static elision_style_t elision_style;
+ static int abbrev_length;
+
+ static bool ansi_codes;
+ static bool ansi_invert;
+
+ static element_t * parse_elements(const string& fmt);
+
+public:
+ format_t() {
+ TRACE_CTOR(format_t, "");
+ }
+ format_t(const string& _format) {
+ TRACE_CTOR(format_t, "const string&");
+ parse(_format);
+ }
+ ~format_t() {
+ TRACE_DTOR(format_t);
+ }
+
+ void parse(const string& _format) {
+ elements.reset(parse_elements(_format));
+ format_string = _format;
+ }
+
+ void format(std::ostream& out, scope_t& scope);
+
+ void dump(std::ostream& out) const {
+ for (const element_t * elem = elements.get();
+ elem;
+ elem = elem->next.get())
+ elem->dump(out);
+ }
+
+ static string truncate(const string& str, unsigned int width,
+ const bool is_account = false);
+};
+
+} // namespace ledger
+
+#endif // _FORMAT_H
diff --git a/src/gnucash.cc b/src/gnucash.cc
new file mode 100644
index 00000000..41990cb0
--- /dev/null
+++ b/src/gnucash.cc
@@ -0,0 +1,432 @@
+/*
+ * Copyright (c) 2003-2008, 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 "gnucash.h"
+#include "account.h"
+
+namespace ledger {
+
+typedef std::map<const string, account_t *> accounts_map;
+typedef std::pair<const string, account_t *> accounts_pair;
+
+typedef std::map<account_t *, commodity_t *> account_comm_map;
+typedef std::pair<account_t *, commodity_t *> account_comm_pair;
+
+static journal_t * curr_journal;
+static account_t * master_account;
+static account_t * curr_account;
+static string curr_account_id;
+static entry_t * curr_entry;
+static commodity_t * entry_comm;
+static commodity_t * curr_comm;
+static amount_t curr_value;
+static amount_t curr_quant;
+static XML_Parser current_parser;
+static accounts_map accounts_by_id;
+static account_comm_map account_comms;
+static unsigned int count;
+static string have_error;
+
+static std::istream * instreamp;
+static unsigned int offset;
+static XML_Parser parser;
+static path pathname;
+static unsigned int src_idx;
+static istream_pos_type beg_pos;
+static unsigned long beg_line;
+
+static xact_t::state_t curr_state;
+
+static enum action_t {
+ NO_ACTION,
+ ACCOUNT_NAME,
+ ACCOUNT_ID,
+ ACCOUNT_PARENT,
+ COMM_SYM,
+ COMM_NAME,
+ COMM_PREC,
+ ENTRY_NUM,
+ ALMOST_ENTRY_DATE,
+ ENTRY_DATE,
+ ENTRY_DESC,
+ XACT_STATE,
+ XACT_AMOUNT,
+ XACT_VALUE,
+ XACT_QUANTITY,
+ XACT_ACCOUNT,
+ XACT_NOTE
+} action;
+
+static void startElement(void *userData, const char *name, const char **atts)
+{
+ if (std::strcmp(name, "gnc:account") == 0) {
+ curr_account = new account_t(master_account);
+ }
+ else if (std::strcmp(name, "act:name") == 0)
+ action = ACCOUNT_NAME;
+ else if (std::strcmp(name, "act:id") == 0)
+ action = ACCOUNT_ID;
+ else if (std::strcmp(name, "act:parent") == 0)
+ action = ACCOUNT_PARENT;
+ else if (std::strcmp(name, "gnc:commodity") == 0)
+ curr_comm = NULL;
+ else if (std::strcmp(name, "cmdty:id") == 0)
+ action = COMM_SYM;
+ else if (std::strcmp(name, "cmdty:name") == 0)
+ action = COMM_NAME;
+ else if (std::strcmp(name, "cmdty:fraction") == 0)
+ action = COMM_PREC;
+ else if (std::strcmp(name, "gnc:xact") == 0) {
+ assert(! curr_entry);
+ curr_entry = new entry_t;
+ }
+ else if (std::strcmp(name, "trn:num") == 0)
+ action = ENTRY_NUM;
+ else if (std::strcmp(name, "trn:date-posted") == 0)
+ action = ALMOST_ENTRY_DATE;
+ else if (action == ALMOST_ENTRY_DATE && std::strcmp(name, "ts:date") == 0)
+ action = ENTRY_DATE;
+ else if (std::strcmp(name, "trn:description") == 0)
+ action = ENTRY_DESC;
+ else if (std::strcmp(name, "trn:split") == 0) {
+ assert(curr_entry);
+ curr_entry->add_xact(new xact_t(curr_account));
+ }
+ else if (std::strcmp(name, "split:reconciled-state") == 0)
+ action = XACT_STATE;
+ else if (std::strcmp(name, "split:amount") == 0)
+ action = XACT_AMOUNT;
+ else if (std::strcmp(name, "split:value") == 0)
+ action = XACT_VALUE;
+ else if (std::strcmp(name, "split:quantity") == 0)
+ action = XACT_QUANTITY;
+ else if (std::strcmp(name, "split:account") == 0)
+ action = XACT_ACCOUNT;
+ else if (std::strcmp(name, "split:memo") == 0)
+ action = XACT_NOTE;
+}
+
+static void endElement(void *userData, const char *name)
+{
+ if (std::strcmp(name, "gnc:account") == 0) {
+ assert(curr_account);
+ if (curr_account->parent == master_account)
+ curr_journal->add_account(curr_account);
+ accounts_by_id.insert(accounts_pair(curr_account_id, curr_account));
+ curr_account = NULL;
+ }
+ else if (std::strcmp(name, "gnc:commodity") == 0) {
+ curr_comm = NULL;
+ }
+ else if (std::strcmp(name, "gnc:xact") == 0) {
+ assert(curr_entry);
+
+ // Add the new entry (what gnucash calls a 'xact') to the
+ // journal
+ if (! curr_journal->add_entry(curr_entry)) {
+#if 0
+ print_entry(std::cerr, *curr_entry);
+#endif
+ have_error = "The above entry does not balance";
+ checked_delete(curr_entry);
+ } else {
+ curr_entry->src_idx = src_idx;
+ curr_entry->beg_pos = beg_pos;
+ curr_entry->beg_line = beg_line;
+ curr_entry->end_pos = instreamp->tellg();
+ curr_entry->end_line = XML_GetCurrentLineNumber(parser) - offset;
+ count++;
+ }
+
+ // Clear the relevant variables for the next run
+ curr_entry = NULL;
+ entry_comm = NULL;
+ }
+ else if (std::strcmp(name, "trn:split") == 0) {
+ xact_t * xact = curr_entry->xacts.back();
+
+ // Identify the commodity to use for the value of this
+ // xact. The quantity indicates how many times that value
+ // the xact is worth.
+ amount_t value;
+ commodity_t * default_commodity = NULL;
+ account_comm_map::iterator ac = account_comms.find(xact->account);
+ if (ac != account_comms.end())
+ default_commodity = (*ac).second;
+
+ if (default_commodity) {
+ curr_quant.set_commodity(*default_commodity);
+ value = curr_quant.round();
+
+ if (curr_value.commodity() == *default_commodity)
+ curr_value = value;
+ } else {
+ value = curr_quant;
+ }
+
+ xact->state = curr_state;
+ xact->amount = value;
+ if (value != curr_value)
+ xact->cost = curr_value;
+
+ xact->beg_pos = beg_pos;
+ xact->beg_line = beg_line;
+ xact->end_pos = instreamp->tellg();
+ xact->end_line = XML_GetCurrentLineNumber(parser) - offset;
+
+ // Clear the relevant variables for the next run
+ curr_state = xact_t::UNCLEARED;
+ curr_value = amount_t();
+ curr_quant = amount_t();
+ }
+
+ action = NO_ACTION;
+}
+
+
+static amount_t convert_number(const string& number,
+ int * precision = NULL)
+{
+ const char * num = number.c_str();
+
+ if (char * p = std::strchr(num, '/')) {
+ string numer_str(num, p - num);
+ string denom_str(p + 1);
+
+ amount_t amt(numer_str);
+ amount_t den(denom_str);
+
+ if (precision)
+ *precision = denom_str.length() - 1;
+
+ if (! den) {
+ have_error = "Denominator in entry is zero!";
+ return amt;
+ } else {
+ return amt / den;
+ }
+ } else {
+ return amount_t(number);
+ }
+}
+
+static void dataHandler(void *userData, const char *s, int len)
+{
+ switch (action) {
+ case ACCOUNT_NAME:
+ curr_account->name = string(s, len);
+ break;
+
+ case ACCOUNT_ID:
+ curr_account_id = string(s, len);
+ break;
+
+ case ACCOUNT_PARENT: {
+ accounts_map::iterator i = accounts_by_id.find(string(s, len));
+ assert(i != accounts_by_id.end());
+ curr_account->parent = (*i).second;
+ curr_account->depth = curr_account->parent->depth + 1;
+ (*i).second->add_account(curr_account);
+ break;
+ }
+
+ case COMM_SYM: {
+ string symbol(s, len);
+ if (symbol == "USD") symbol = "$";
+
+ curr_comm = amount_t::current_pool->find_or_create(symbol);
+ assert(curr_comm);
+
+ if (symbol != "$")
+ curr_comm->add_flags(COMMODITY_STYLE_SEPARATED);
+
+ if (curr_account)
+ account_comms.insert(account_comm_pair(curr_account, curr_comm));
+ else if (curr_entry)
+ entry_comm = curr_comm;
+ break;
+ }
+
+ case COMM_NAME:
+ curr_comm->set_name(string(s, len));
+ break;
+
+ case COMM_PREC:
+ curr_comm->set_precision(len - 1);
+ break;
+
+ case ENTRY_NUM:
+ curr_entry->code = string(s, len);
+ break;
+
+ case ENTRY_DATE:
+ curr_entry->_date = parse_date(string(s, len));
+ break;
+
+ case ENTRY_DESC:
+ curr_entry->payee = string(s, len);
+ break;
+
+ case XACT_STATE:
+ if (*s == 'y')
+ curr_state = xact_t::CLEARED;
+ else if (*s == 'n')
+ curr_state = xact_t::UNCLEARED;
+ else
+ curr_state = xact_t::PENDING;
+ break;
+
+ case XACT_VALUE: {
+ int precision;
+ assert(entry_comm);
+ curr_value = convert_number(string(s, len), &precision);
+ curr_value.set_commodity(*entry_comm);
+
+ if (precision > entry_comm->precision())
+ entry_comm->set_precision(precision);
+ break;
+ }
+
+ case XACT_QUANTITY:
+ curr_quant = convert_number(string(s, len));
+ break;
+
+ case XACT_ACCOUNT: {
+ xact_t * xact = curr_entry->xacts.back();
+
+ accounts_map::iterator i = accounts_by_id.find(string(s, len));
+ if (i != accounts_by_id.end()) {
+ xact->account = (*i).second;
+ } else {
+ xact->account = curr_journal->find_account("<Unknown>");
+
+ have_error = (string("Could not find account ") +
+ string(s, len));
+ }
+ break;
+ }
+
+ case XACT_NOTE:
+ curr_entry->xacts.back()->note = string(s, len);
+ break;
+
+ case NO_ACTION:
+ case ALMOST_ENTRY_DATE:
+ case XACT_AMOUNT:
+ break;
+
+ default:
+ assert(false);
+ break;
+ }
+}
+
+bool gnucash_parser_t::test(std::istream& in) const
+{
+ char buf[5];
+ in.read(buf, 5);
+ in.clear();
+ in.seekg(0, std::ios::beg);
+
+ return std::strncmp(buf, "<?xml", 5) == 0;
+}
+
+unsigned int gnucash_parser_t::parse(std::istream& in,
+ session_t& session,
+ journal_t& journal,
+ account_t * master,
+ const path * original_file)
+{
+ char buf[BUFSIZ];
+
+#if 0
+ // jww (2008-05-08): Replace this
+ // This is the date format used by Gnucash, so override whatever the
+ // user specified.
+ date_t::input_format = "%Y-%m-%d %H:%M:%S %z";
+#endif
+
+ count = 0;
+ action = NO_ACTION;
+ curr_journal = &journal;
+ master_account = master ? master : journal.master;
+ curr_account = NULL;
+ curr_entry = NULL;
+ curr_comm = NULL;
+ entry_comm = NULL;
+ curr_state = xact_t::UNCLEARED;
+
+ instreamp = &in;
+ pathname = original_file ? *original_file : "<gnucash>";
+ src_idx = journal.sources.size() - 1;
+
+ // GnuCash uses the USD commodity without defining it, which really
+ // means $.
+ commodity_t * usd = amount_t::current_pool->find_or_create("$");
+ usd->set_precision(2);
+ usd->add_flags(COMMODITY_STYLE_THOUSANDS);
+
+ offset = 2;
+ parser = current_parser = XML_ParserCreate(NULL);
+
+ XML_SetElementHandler(parser, startElement, endElement);
+ XML_SetCharacterDataHandler(parser, dataHandler);
+
+ while (in.good() && ! in.eof()) {
+ beg_pos = in.tellg();
+ beg_line = (XML_GetCurrentLineNumber(parser) - offset) + 1;
+
+ in.getline(buf, BUFSIZ - 1);
+ std::strcat(buf, "\n");
+ if (! XML_Parse(parser, buf, std::strlen(buf), in.eof())) {
+ //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++;
+ const char * msg = XML_ErrorString(XML_GetErrorCode(parser));
+ XML_ParserFree(parser);
+ throw parse_error(msg);
+ }
+
+ if (! have_error.empty()) {
+ //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++;
+ parse_error err(have_error);
+ std::cerr << "Error: " << err.what() << std::endl;
+ have_error = "";
+ }
+ }
+
+ XML_ParserFree(parser);
+
+ accounts_by_id.clear();
+ curr_account_id.clear();
+
+ return count;
+}
+
+} // namespace ledger
diff --git a/src/gnucash.h b/src/gnucash.h
new file mode 100644
index 00000000..b8273a19
--- /dev/null
+++ b/src/gnucash.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2003-2008, 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 _GNUCASH_H
+#define _GNUCASH_H
+
+#include "journal.h"
+
+namespace ledger {
+
+class gnucash_parser_t : public journal_t::parser_t
+{
+ public:
+ virtual bool test(std::istream& in) const;
+
+ virtual unsigned int parse(std::istream& in,
+ session_t& session,
+ journal_t& journal,
+ account_t * master = NULL,
+ const path * original_file = NULL);
+};
+
+} // namespace ledger
+
+#endif // _GNUCASH_H
diff --git a/src/handler.h b/src/handler.h
new file mode 100644
index 00000000..6ebd6a5d
--- /dev/null
+++ b/src/handler.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2003-2008, 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 _HANDLER_H
+#define _HANDLER_H
+
+#include "utils.h"
+#include "xact.h"
+#include "account.h"
+
+namespace ledger {
+
+template <typename T>
+struct item_handler : public noncopyable
+{
+ shared_ptr<item_handler> handler;
+
+public:
+ item_handler() {
+ TRACE_CTOR(item_handler, "");
+ }
+ item_handler(shared_ptr<item_handler> _handler) : handler(_handler) {
+ TRACE_CTOR(item_handler, "shared_ptr<item_handler>");
+ }
+ virtual ~item_handler() {
+ TRACE_DTOR(item_handler);
+ }
+
+ virtual void flush() {
+ if (handler.get())
+ handler->flush();
+ }
+ virtual void operator()(T& item) {
+ if (handler.get())
+ (*handler.get())(item);
+ }
+};
+
+typedef shared_ptr<item_handler<xact_t> > xact_handler_ptr;
+typedef shared_ptr<item_handler<account_t> > acct_handler_ptr;
+
+} // namespace ledger
+
+#endif // _HANDLER_H
diff --git a/src/help.cc b/src/help.cc
new file mode 100644
index 00000000..84d5a178
--- /dev/null
+++ b/src/help.cc
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2003-2008, 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 "help.h"
+
+namespace ledger {
+
+void help(std::ostream& out)
+{
+ out << "usage: ledger [options] COMMAND [ACCT REGEX]... [-- [PAYEE REGEX]...]\n\n\
+Use -H to see all the help text on one page, or:\n\
+ --help-calc calculation options\n\
+ --help-disp display options\n\
+ --help-comm commodity options\n\n\
+Basic options:\n\
+ -h, --help display this help text\n\
+ -v, --version show version information\n\
+ -f, --file FILE read ledger data from FILE\n\
+ -o, --output FILE write output to FILE\n\
+ -i, --init-file FILE initialize ledger using FILE (default: ~/.ledgerrc)\n\
+ --cache FILE use FILE as a binary cache when --file is not used\n\
+ --no-cache don't use a cache, even if it would be appropriate\n\
+ -a, --account NAME use NAME for the default account (useful with QIF)\n\n\
+Commands:\n\
+ balance [REGEXP]... show balance totals for matching accounts\n\
+ register [REGEXP]... show register of matching transactions\n\
+ print [REGEXP]... print all matching entries\n\
+ xml [REGEXP]... print matching entries in XML format\n\
+ equity [REGEXP]... output equity entries for matching accounts\n\
+ prices [REGEXP]... display price history for matching commodities\n\
+ entry DATE PAYEE AMT output a derived entry, based on the arguments\n";
+}
+
+void calc_help(std::ostream& out)
+{
+ out << "Options to control how a report is calculated:\n\
+ -c, --current show only current and past entries (not future)\n\
+ -b, --begin DATE set report begin date\n\
+ -e, --end DATE set report end date\n\
+ -p, --period STR report using the given period\n\
+ --period-sort EXPR sort each report period's entries by EXPR\n\
+ -C, --cleared consider only cleared transactions\n\
+ -U, --uncleared consider only uncleared transactions\n\
+ -R, --real consider only real (non-virtual) transactions\n\
+ -L, --actual consider only actual (non-automated) transactions\n\
+ -r, --related calculate report using related transactions\n\
+ --budget generate budget entries based on periodic entries\n\
+ --add-budget show all transactions plus the budget\n\
+ --unbudgeted show only unbudgeted transactions\n\
+ --forecast EXPR generate forecast entries while EXPR is true\n\
+ -l, --limit EXPR calculate only transactions matching EXPR\n\
+ -t, --amount EXPR use EXPR to calculate the displayed amount\n\
+ -T, --total EXPR use EXPR to calculate the displayed total\n";
+}
+
+void disp_help(std::ostream& out)
+{
+ out << "Output to control how report results are displayed:\n\
+ -n, --collapse register: collapse entries; balance: no grand total\n\
+ -s, --subtotal balance: show sub-accounts; other: show subtotals\n\
+ -P, --by-payee show summarized totals by payee\n\
+ -x, --comm-as-payee set commodity name as the payee, for reporting\n\
+ -E, --empty balance: show accounts with zero balance\n\
+ -W, --weekly show weekly sub-totals\n\
+ -M, --monthly show monthly sub-totals\n\
+ -Y, --yearly show yearly sub-totals\n\
+ --dow show a days-of-the-week report\n\
+ -S, --sort EXPR sort report according to the value expression EXPR\n\
+ -w, --wide for the default register report, use 132 columns\n\
+ --head COUNT show only the first COUNT entries (negative inverts)\n\
+ --tail COUNT show only the last COUNT entries (negative inverts)\n\
+ --pager PAGER send all output through the given PAGER program\n\
+ -A, --average report average transaction amount\n\
+ -D, --deviation report deviation from the average\n\
+ -%, --percentage report balance totals as a percentile of the parent\n\
+ --totals in the \"xml\" report, include running total\n\
+ -j, --amount-data print only raw amount data (useful for scripting)\n\
+ -J, --total-data print only raw total data\n\
+ -d, --display EXPR display only transactions matching EXPR\n\
+ -y, --date-format STR use STR as the date format (default: %Y/%m/%d)\n\
+ -F, --format STR use STR as the format; for each report type, use:\n\
+ --balance-format --register-format --print-format\n\
+ --plot-amount-format --plot-total-format --equity-format\n\
+ --prices-format --wide-register-format\n";
+}
+
+void comm_help(std::ostream& out)
+{
+ out << "Options to control how commodity values are determined:\n\
+ --price-db FILE sets the price database to FILE (def: ~/.pricedb)\n\
+ -Z, --price-exp MINS download quotes only if newer than MINS (def: 1440)\n\
+ -Q, --download download price information when needed\n\
+ -O, --quantity report commodity totals (this is the default)\n\
+ -B, --basis report cost basis of commodities\n\
+ -V, --market report last known market value\n\
+ -g, --performance report gain/loss for each displayed transaction\n\
+ -G, --gain report net gain/loss\n";
+}
+
+void full_help(std::ostream& out)
+{
+ out << "usage: ledger [options] COMMAND [ACCT REGEX]... [-- [PAYEE REGEX]...]\n\n\
+Basic options:\n\
+ -H, --full-help display this help text\n\
+ -h, --help display summarized help text\n\
+ -v, --version show version information\n\
+ -f, --file FILE read ledger data from FILE\n\
+ -o, --output FILE write output to FILE\n\
+ -i, --init-file FILE initialize ledger using FILE (default: ~/.ledgerrc)\n\
+ --cache FILE use FILE as a binary cache when --file is not used\n\
+ --no-cache don't use a cache, even if it would be appropriate\n\
+ -a, --account NAME use NAME for the default account (useful with QIF)\n\n\
+Report filtering:\n\
+ -c, --current show only current and past entries (not future)\n\
+ -b, --begin DATE set report begin date\n\
+ -e, --end DATE set report end date\n\
+ -p, --period STR report using the given period\n\
+ --period-sort EXPR sort each report period's entries by EXPR\n\
+ -C, --cleared consider only cleared transactions\n\
+ -U, --uncleared consider only uncleared transactions\n\
+ -R, --real consider only real (non-virtual) transactions\n\
+ -L, --actual consider only actual (non-automated) transactions\n\
+ -r, --related calculate report using related transactions\n\
+ --budget generate budget entries based on periodic entries\n\
+ --add-budget show all transactions plus the budget\n\
+ --unbudgeted show only unbudgeted transactions\n\
+ --forecast EXPR generate forecast entries while EXPR is true\n\
+ -l, --limit EXPR calculate only transactions matching EXPR\n\
+ -t, --amount EXPR use EXPR to calculate the displayed amount\n\
+ -T, --total EXPR use EXPR to calculate the displayed total\n\n\
+Output customization:\n\
+ -n, --collapse register: collapse entries; balance: no grand total\n\
+ -s, --subtotal balance: show sub-accounts; other: show subtotals\n\
+ -P, --by-payee show summarized totals by payee\n\
+ -x, --comm-as-payee set commodity name as the payee, for reporting\n\
+ -E, --empty balance: show accounts with zero balance\n\
+ -W, --weekly show weekly sub-totals\n\
+ -M, --monthly show monthly sub-totals\n\
+ -Y, --yearly show yearly sub-totals\n\
+ --dow show a days-of-the-week report\n\
+ -S, --sort EXPR sort report according to the value expression EXPR\n\
+ -w, --wide for the default register report, use 132 columns\n\
+ --head COUNT show only the first COUNT entries (negative inverts)\n\
+ --tail COUNT show only the last COUNT entries (negative inverts)\n\
+ --pager PAGER send all output through the given PAGER program\n\
+ -A, --average report average transaction amount\n\
+ -D, --deviation report deviation from the average\n\
+ -%, --percentage report balance totals as a percentile of the parent\n\
+ --totals in the \"xml\" report, include running total\n\
+ -j, --amount-data print only raw amount data (useful for scripting)\n\
+ -J, --total-data print only raw total data\n\
+ -d, --display EXPR display only transactions matching EXPR\n\
+ -y, --date-format STR use STR as the date format (default: %Y/%m/%d)\n\
+ -F, --format STR use STR as the format; for each report type, use:\n\
+ --balance-format --register-format --print-format\n\
+ --plot-amount-format --plot-total-format --equity-format\n\
+ --prices-format --wide-register-format\n\n\
+Commodity reporting:\n\
+ --price-db FILE sets the price database to FILE (def: ~/.pricedb)\n\
+ -L, --price-exp MINS download quotes only if newer than MINS (def: 1440)\n\
+ -Q, --download download price information when needed\n\
+ -O, --quantity report commodity totals (this is the default)\n\
+ -B, --basis report cost basis of commodities\n\
+ -V, --market report last known market value\n\
+ -g, --performance report gain/loss for each displayed transaction\n\
+ -G, --gain report net gain/loss\n\n\
+Commands:\n\
+ balance [REGEXP]... show balance totals for matching accounts\n\
+ register [REGEXP]... show register of matching transactions\n\
+ print [REGEXP]... print all matching entries\n\
+ xml [REGEXP]... print matching entries in XML format\n\
+ equity [REGEXP]... output equity entries for matching accounts\n\
+ prices [REGEXP]... display price history for matching commodities\n\
+ entry DATE PAYEE AMT output a derived entry, based on the arguments\n";
+}
+
+} // namespace ledger
diff --git a/src/help.h b/src/help.h
new file mode 100644
index 00000000..14067be4
--- /dev/null
+++ b/src/help.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2003-2008, 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 _HELP_H
+#define _HELP_H
+
+#include "utils.h"
+
+namespace ledger {
+
+void help(std::ostream& out);
+
+void calc_help(std::ostream& out);
+void disp_help(std::ostream& out);
+void comm_help(std::ostream& out);
+
+void full_help(std::ostream& out);
+
+} // namespace ledger
+
+#endif // _HELP_H
diff --git a/src/hooks.h b/src/hooks.h
new file mode 100644
index 00000000..da197cdd
--- /dev/null
+++ b/src/hooks.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2003-2008, 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 _HOOKS_H
+#define _HOOKS_H
+
+template <typename T, typename Data>
+class hooks_t : public boost::noncopyable
+{
+public:
+ typedef boost::function<bool (Data&, bool)> function_t;
+
+protected:
+ std::list<T *> list;
+
+public:
+ hooks_t() {
+ TRACE_CTOR(hooks_t, "");
+ }
+ ~hooks_t() throw() {
+ TRACE_DTOR(hooks_t);
+ }
+
+ void add_hook(T * func, const bool prepend = false) {
+ if (prepend)
+ list.push_front(func);
+ else
+ list.push_back(func);
+ }
+
+ void remove_hook(T * func) {
+ list.remove(func);
+ }
+
+ bool run_hooks(Data& item, bool post) {
+ foreach (T * func, list)
+ if (! (*func)(item, post))
+ return false;
+ return true;
+ }
+};
+
+#endif // _HOOKS_H
diff --git a/src/iterators.cc b/src/iterators.cc
new file mode 100644
index 00000000..34beba7e
--- /dev/null
+++ b/src/iterators.cc
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2003-2008, 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 "iterators.h"
+#include "session.h"
+#include "compare.h"
+
+namespace ledger {
+
+void entries_iterator::reset(session_t& session)
+{
+ journals_i = session.journals.begin();
+ journals_end = session.journals.end();
+
+ journals_uninitialized = false;
+
+ if (journals_i != journals_end) {
+ entries_i = (*journals_i).entries.begin();
+ entries_end = (*journals_i).entries.end();
+
+ entries_uninitialized = false;
+ } else {
+ entries_uninitialized = true;
+ }
+}
+
+entry_t * entries_iterator::operator()()
+{
+ if (entries_i == entries_end) {
+ journals_i++;
+ if (journals_i == journals_end)
+ return NULL;
+
+ entries_i = (*journals_i).entries.begin();
+ entries_end = (*journals_i).entries.end();
+ }
+ return *entries_i++;
+}
+
+void session_xacts_iterator::reset(session_t& session)
+{
+ entries.reset(session);
+ entry_t * entry = entries();
+ if (entry != NULL)
+ xacts.reset(*entry);
+}
+
+xact_t * session_xacts_iterator::operator()()
+{
+ xact_t * xact = xacts();
+ if (xact == NULL) {
+ entry_t * entry = entries();
+ if (entry != NULL) {
+ xacts.reset(*entry);
+ xact = xacts();
+ }
+ }
+ return xact;
+}
+
+account_t * basic_accounts_iterator::operator()()
+{
+ while (! accounts_i.empty() &&
+ accounts_i.back() == accounts_end.back()) {
+ accounts_i.pop_back();
+ accounts_end.pop_back();
+ }
+ if (accounts_i.empty())
+ return NULL;
+
+ account_t * account = (*(accounts_i.back()++)).second;
+ assert(account);
+
+ // If this account has children, queue them up to be iterated next.
+ if (! account->accounts.empty())
+ push_back(*account);
+
+ return account;
+}
+
+void sorted_accounts_iterator::sort_accounts(account_t& account,
+ accounts_deque_t& deque)
+{
+ foreach (accounts_map::value_type& pair, account.accounts)
+ deque.push_back(pair.second);
+
+ std::stable_sort(deque.begin(), deque.end(),
+ compare_items<account_t>(sort_cmp));
+}
+
+account_t * sorted_accounts_iterator::operator()()
+{
+ while (! sorted_accounts_i.empty() &&
+ sorted_accounts_i.back() == sorted_accounts_end.back()) {
+ sorted_accounts_i.pop_back();
+ sorted_accounts_end.pop_back();
+ assert(! accounts_list.empty());
+ accounts_list.pop_back();
+ }
+ if (sorted_accounts_i.empty())
+ return NULL;
+
+ account_t * account = *sorted_accounts_i.back()++;
+ assert(account);
+
+ // If this account has children, queue them up to be iterated next.
+ if (! account->accounts.empty())
+ push_back(*account);
+
+ account->xdata().drop_flags(ACCOUNT_EXT_SORT_CALC);
+ return account;
+}
+
+void journals_iterator::reset(session_t& session)
+{
+ journals_i = session.journals.begin();
+ journals_end = session.journals.end();
+}
+
+journal_t * journals_iterator::operator()()
+{
+ if (journals_i == journals_end)
+ return NULL;
+ return &(*journals_i++);
+}
+
+#if 0
+// jww (2008-08-03): This needs to be changed into a commodities->xacts
+// iterator.
+
+// jww (2008-08-03): We then could also use a payees->xacts iterator
+
+void walk_commodities(commodity_pool_t::commodities_by_ident& commodities,
+ item_handler<xact_t>& handler)
+{
+ std::list<xact_t> xact_temps;
+ std::list<entry_t> entry_temps;
+ std::list<account_t> acct_temps;
+
+ for (commodity_pool_t::commodities_by_ident::iterator
+ i = commodities.begin();
+ i != commodities.end();
+ i++) {
+ if ((*i)->has_flags(COMMODITY_STYLE_NOMARKET))
+ continue;
+
+ entry_temps.push_back(entry_t());
+ acct_temps.push_back(account_t(NULL, (*i)->symbol()));
+
+ if ((*i)->history())
+ foreach (const commodity_t::history_map::value_type& pair,
+ (*i)->history()->prices) {
+ entry_temps.back()._date = pair.first.date();
+
+ xact_temps.push_back(xact_t(&acct_temps.back()));
+ xact_t& temp = xact_temps.back();
+ temp.entry = &entry_temps.back();
+ temp.amount = pair.second;
+ temp.add_flags(XACT_TEMP);
+ entry_temps.back().add_xact(&temp);
+
+ handler(xact_temps.back());
+ }
+ }
+
+ handler.flush();
+
+ clear_entries_xacts(entry_temps);
+}
+#endif
+
+} // namespace ledger
diff --git a/src/iterators.h b/src/iterators.h
new file mode 100644
index 00000000..c52a6827
--- /dev/null
+++ b/src/iterators.h
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2003-2008, 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 _ITERATORS_H
+#define _ITERATORS_H
+
+#include "journal.h"
+#include "entry.h"
+#include "account.h"
+
+namespace ledger {
+
+class xacts_iterator : public noncopyable
+{
+public:
+ virtual xact_t * operator()() = 0;
+};
+
+class entry_xacts_iterator : public xacts_iterator
+{
+ xacts_list::iterator xacts_i;
+ xacts_list::iterator xacts_end;
+
+ bool xacts_uninitialized;
+
+public:
+ entry_xacts_iterator() : xacts_uninitialized(true) {
+ TRACE_CTOR(entry_xacts_iterator, "");
+ }
+ entry_xacts_iterator(entry_t& entry)
+ : xacts_uninitialized(true) {
+ TRACE_CTOR(entry_xacts_iterator, "entry_t&");
+ reset(entry);
+ }
+ virtual ~entry_xacts_iterator() throw() {
+ TRACE_DTOR(entry_xacts_iterator);
+ }
+
+ void reset(entry_t& entry) {
+ xacts_i = entry.xacts.begin();
+ xacts_end = entry.xacts.end();
+
+ xacts_uninitialized = false;
+ }
+
+ virtual xact_t * operator()() {
+ if (xacts_i == xacts_end || xacts_uninitialized)
+ return NULL;
+ return *xacts_i++;
+ }
+};
+
+class entries_iterator : public noncopyable
+{
+ ptr_list<journal_t>::iterator journals_i;
+ ptr_list<journal_t>::iterator journals_end;
+
+ bool journals_uninitialized;
+
+ entries_list::iterator entries_i;
+ entries_list::iterator entries_end;
+
+ bool entries_uninitialized;
+
+public:
+ entries_iterator()
+ : journals_uninitialized(true), entries_uninitialized(true) {
+ TRACE_CTOR(entries_iterator, "");
+ }
+ entries_iterator(session_t& session)
+ : journals_uninitialized(true), entries_uninitialized(true) {
+ TRACE_CTOR(entries_iterator, "session_t&");
+ reset(session);
+ }
+ ~entries_iterator() throw() {
+ TRACE_DTOR(entries_iterator);
+ }
+
+ void reset(session_t& session);
+
+ entry_t * operator()();
+};
+
+class session_xacts_iterator : public xacts_iterator
+{
+ entries_iterator entries;
+ entry_xacts_iterator xacts;
+
+public:
+ session_xacts_iterator() {
+ TRACE_CTOR(session_xacts_iterator, "");
+ }
+ session_xacts_iterator(session_t& session) {
+ TRACE_CTOR(session_xacts_iterator, "session_t&");
+ reset(session);
+ }
+ virtual ~session_xacts_iterator() throw() {
+ TRACE_DTOR(session_xacts_iterator);
+ }
+
+ void reset(session_t& session);
+
+ virtual xact_t * operator()();
+};
+
+class accounts_iterator : public noncopyable
+{
+public:
+ virtual account_t * operator()() = 0;
+};
+
+class basic_accounts_iterator : public accounts_iterator
+{
+ std::list<accounts_map::const_iterator> accounts_i;
+ std::list<accounts_map::const_iterator> accounts_end;
+
+public:
+ basic_accounts_iterator() {
+ TRACE_CTOR(basic_accounts_iterator, "");
+ }
+ basic_accounts_iterator(account_t& account) {
+ TRACE_CTOR(basic_accounts_iterator, "account_t&");
+ push_back(account);
+ }
+ virtual ~basic_accounts_iterator() throw() {
+ TRACE_DTOR(basic_accounts_iterator);
+ }
+
+ void push_back(account_t& account) {
+ accounts_i.push_back(account.accounts.begin());
+ accounts_end.push_back(account.accounts.end());
+ }
+
+ virtual account_t * operator()();
+};
+
+class sorted_accounts_iterator : public accounts_iterator
+{
+ expr_t sort_cmp;
+
+ typedef std::deque<account_t *> accounts_deque_t;
+
+ std::list<accounts_deque_t> accounts_list;
+ std::list<accounts_deque_t::const_iterator> sorted_accounts_i;
+ std::list<accounts_deque_t::const_iterator> sorted_accounts_end;
+
+public:
+ sorted_accounts_iterator(const string& sort_order) {
+ TRACE_CTOR(sorted_accounts_iterator, "const string&");
+ sort_cmp = expr_t(sort_order);
+ }
+ sorted_accounts_iterator(account_t& account, const string& sort_order) {
+ TRACE_CTOR(sorted_accounts_iterator, "account_t&, const string&");
+ sort_cmp = expr_t(sort_order);
+ push_back(account);
+ }
+ virtual ~sorted_accounts_iterator() throw() {
+ TRACE_DTOR(sorted_accounts_iterator);
+ }
+
+ void sort_accounts(account_t& account, accounts_deque_t& deque);
+
+ void push_back(account_t& account) {
+ accounts_list.push_back(accounts_deque_t());
+ sort_accounts(account, accounts_list.back());
+
+ sorted_accounts_i.push_back(accounts_list.back().begin());
+ sorted_accounts_end.push_back(accounts_list.back().end());
+ }
+
+ virtual account_t * operator()();
+};
+
+class journals_iterator : public noncopyable
+{
+ ptr_list<journal_t>::iterator journals_i;
+ ptr_list<journal_t>::iterator journals_end;
+
+public:
+ journals_iterator() {
+ TRACE_CTOR(journals_iterator, "");
+ }
+ journals_iterator(session_t& session) {
+ TRACE_CTOR(journals_iterator, "session_t&");
+ reset(session);
+ }
+ virtual ~journals_iterator() throw() {
+ TRACE_DTOR(journals_iterator);
+ }
+
+ void reset(session_t& session);
+
+ virtual journal_t * operator()();
+};
+
+} // namespace ledger
+
+#endif // _ITERATORS_H
diff --git a/src/journal.cc b/src/journal.cc
new file mode 100644
index 00000000..7d95d72f
--- /dev/null
+++ b/src/journal.cc
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2003-2008, 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 "journal.h"
+#include "session.h"
+
+namespace ledger {
+
+const string version = PACKAGE_VERSION;
+
+journal_t::journal_t(session_t * _owner)
+ : owner(_owner), basket(NULL)
+{
+ TRACE_CTOR(journal_t, "");
+ master = owner->master.get();
+}
+
+journal_t::~journal_t()
+{
+ TRACE_DTOR(journal_t);
+
+ // Don't bother unhooking each entry's xacts from the
+ // accounts they refer to, because all accounts are about to
+ // be deleted.
+ foreach (entry_t * entry, entries)
+ if (! entry->has_flags(ENTRY_IN_CACHE))
+ checked_delete(entry);
+ else
+ entry->~entry_t();
+
+ foreach (auto_entry_t * entry, auto_entries)
+ if (! entry->has_flags(ENTRY_IN_CACHE))
+ checked_delete(entry);
+ else
+ entry->~auto_entry_t();
+
+ foreach (period_entry_t * entry, period_entries)
+ if (! entry->has_flags(ENTRY_IN_CACHE))
+ checked_delete(entry);
+ else
+ entry->~period_entry_t();
+}
+
+void journal_t::add_account(account_t * acct)
+{
+ owner->add_account(acct);
+}
+
+bool journal_t::remove_account(account_t * acct)
+{
+ return owner->remove_account(acct);
+}
+
+account_t * journal_t::find_account(const string& name, bool auto_create)
+{
+ return owner->find_account(name, auto_create);
+}
+
+account_t * journal_t::find_account_re(const string& regexp)
+{
+ return owner->find_account_re(regexp);
+}
+
+bool journal_t::add_entry(entry_t * entry)
+{
+ entry->journal = this;
+
+ if (! entry_finalize_hooks.run_hooks(*entry, false) ||
+ ! entry->finalize() ||
+ ! entry_finalize_hooks.run_hooks(*entry, true)) {
+ entry->journal = NULL;
+ return false;
+ }
+
+ entries.push_back(entry);
+
+ foreach (const xact_t * xact, entry->xacts)
+ if (xact->cost) {
+ assert(xact->amount);
+ xact->amount.commodity().add_price(datetime_t(entry->date(),
+ time_duration_t(0, 0, 0)),
+ *xact->cost / xact->amount.number());
+ }
+
+ return true;
+}
+
+bool journal_t::remove_entry(entry_t * entry)
+{
+ bool found = false;
+ entries_list::iterator i;
+ for (i = entries.begin(); i != entries.end(); i++)
+ if (*i == entry) {
+ found = true;
+ break;
+ }
+ if (! found)
+ return false;
+
+ entries.erase(i);
+ entry->journal = NULL;
+
+ return true;
+}
+
+bool journal_t::valid() const
+{
+ if (! master->valid()) {
+ DEBUG("ledger.validate", "journal_t: master not valid");
+ return false;
+ }
+
+ foreach (const entry_t * entry, entries)
+ if (! entry->valid()) {
+ DEBUG("ledger.validate", "journal_t: entry not valid");
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace ledger
diff --git a/src/journal.h b/src/journal.h
new file mode 100644
index 00000000..293439bb
--- /dev/null
+++ b/src/journal.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2003-2008, 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 _JOURNAL_H
+#define _JOURNAL_H
+
+#include "utils.h"
+#include "hooks.h"
+#include "entry.h"
+
+namespace ledger {
+
+typedef std::list<path> paths_list;
+
+class session_t;
+class account_t;
+
+class journal_t : public noncopyable
+{
+public:
+ session_t * owner;
+ account_t * master;
+ account_t * basket;
+ entries_list entries;
+ paths_list sources;
+ optional<path> price_db;
+
+ auto_entries_list auto_entries;
+ period_entries_list period_entries;
+
+ hooks_t<entry_finalizer_t, entry_t> entry_finalize_hooks;
+
+ journal_t(session_t * _owner);
+ ~journal_t();
+
+ // These four methods are delegated to 'owner', since all accounts processed
+ // are gathered together at the session level.
+ void add_account(account_t * acct);
+ bool remove_account(account_t * acct);
+ account_t * find_account(const string& name, bool auto_create = true);
+ account_t * find_account_re(const string& regexp);
+
+ bool add_entry(entry_t * entry);
+ bool remove_entry(entry_t * entry);
+
+ void add_entry_finalizer(entry_finalizer_t * finalizer) {
+ entry_finalize_hooks.add_hook(finalizer);
+ }
+ void remove_entry_finalizer(entry_finalizer_t * finalizer) {
+ entry_finalize_hooks.remove_hook(finalizer);
+ }
+
+ /**
+ * @class journal_t::parser_t
+ *
+ * @brief Provides an abstract interface for writing journal parsers.
+ *
+ * Any data format for Ledger data is possible, as long as it can be parsed
+ * into a journal_t data tree. This class provides the basic interface which
+ * must be implemented by every such journal parser.
+ */
+ class parser_t : public noncopyable
+ {
+ public:
+ parser_t() {
+ TRACE_CTOR(journal_t::parser_t, "");
+ }
+ virtual ~parser_t() {
+ TRACE_DTOR(journal_t::parser_t);
+ }
+
+ virtual bool test(std::istream& in) const = 0;
+
+ virtual unsigned int parse(std::istream& in,
+ session_t& session,
+ journal_t& journal,
+ account_t * master = NULL,
+ const path * original_file = NULL) = 0;
+ };
+
+ class binary_parser_t : public parser_t
+ {
+ public:
+ virtual bool test(std::istream& in) const;
+
+ virtual unsigned int parse(std::istream& in,
+ session_t& session,
+ journal_t& journal,
+ account_t * master = NULL,
+ const path * original_file = NULL);
+ };
+
+ bool valid() const;
+};
+
+extern const string version;
+
+} // namespace ledger
+
+#endif // _JOURNAL_H
diff --git a/src/ledger.h b/src/ledger.h
new file mode 100644
index 00000000..a87c3d55
--- /dev/null
+++ b/src/ledger.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2003-2008, 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 _LEDGER_H
+#define _LEDGER_H
+
+//////////////////////////////////////////////////////////////////////
+//
+// Ledger Accounting Tool
+//
+// A command-line tool for general double-entry accounting.
+//
+// Copyright (c) 2003-2008, John Wiegley <johnw@newartisans.com>
+//
+
+#include <utils.h>
+#include <value.h>
+#include <expr.h>
+#include <scope.h>
+#include <predicate.h>
+#include <format.h>
+#include <option.h>
+
+#include <journal.h>
+#include <entry.h>
+#include <xact.h>
+#include <account.h>
+#include <iterators.h>
+#include <compare.h>
+
+#include <textual.h>
+#include <cache.h>
+#include <emacs.h>
+#include <qif.h>
+#include <xml.h>
+#include <csv.h>
+#include <gnucash.h>
+#include <ofx.h>
+
+#include <session.h>
+#include <report.h>
+#include <handler.h>
+#include <filters.h>
+#include <output.h>
+#include <help.h>
+
+#include <derive.h>
+#include <reconcile.h>
+#include <quotes.h>
+
+#include <ledger.h>
+
+#endif // _LEDGER_H
diff --git a/src/main.cc b/src/main.cc
new file mode 100644
index 00000000..a5ca02d0
--- /dev/null
+++ b/src/main.cc
@@ -0,0 +1,593 @@
+/*
+ * Copyright (c) 2003-2008, 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 "session.h"
+#include "report.h"
+#include "option.h"
+#include "output.h"
+#include "help.h"
+
+#include "textual.h"
+#include "qif.h"
+#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
+#include "xml.h"
+#include "gnucash.h"
+#endif
+#ifdef HAVE_LIBOFX
+#include "ofx.h"
+#endif
+
+#ifdef HAVE_UNIX_PIPES
+#include <sys/types.h>
+#include <sys/wait.h>
+#include "fdstream.h"
+#endif
+
+namespace ledger {
+ string args_to_predicate(value_t::sequence_t::const_iterator begin,
+ value_t::sequence_t::const_iterator end)
+ {
+ string acct_value_expr;
+ string payee_value_expr;
+ string note_value_expr;
+
+ string * value_expr;
+
+ enum regexp_kind_t {
+ ACCOUNT_REGEXP,
+ PAYEE_REGEXP,
+ NOTE_REGEXP
+ }
+ kind = ACCOUNT_REGEXP;
+
+ value_expr = &acct_value_expr;
+
+ for ( ; begin != end; begin++) {
+ const string& arg((*begin).as_string());
+
+ if (arg == "--") {
+ kind = PAYEE_REGEXP;
+ value_expr = &payee_value_expr;
+ }
+ else if (arg == "/") {
+ kind = NOTE_REGEXP;
+ value_expr = &note_value_expr;
+ }
+ else {
+ if (! value_expr->empty())
+ *value_expr += "|";
+
+ switch (kind) {
+ case ACCOUNT_REGEXP:
+ *value_expr += "account =~ ";
+ break;
+ case PAYEE_REGEXP:
+ *value_expr += "payee =~ ";
+ break;
+ case NOTE_REGEXP:
+ *value_expr += "note =~ ";
+ break;
+ }
+
+ const char * p = arg.c_str();
+ if (*p == '-') {
+ *value_expr += "!";
+ p++;
+ }
+
+ *value_expr += "/";
+ if (kind == NOTE_REGEXP) *value_expr += ":";
+ while (*p) {
+ if (*p == '/')
+ *value_expr += "\\";
+ *value_expr += *p;
+ p++;
+ }
+ if (kind == NOTE_REGEXP) *value_expr += ":";
+ *value_expr += "/";
+ }
+ }
+
+ string final_value_expr;
+
+ if (! acct_value_expr.empty()) {
+ if (! payee_value_expr.empty() ||
+ ! note_value_expr.empty())
+ final_value_expr = string("(") + acct_value_expr + ")";
+ else
+ final_value_expr = acct_value_expr;
+ }
+
+ if (! payee_value_expr.empty()) {
+ if (! acct_value_expr.empty())
+ final_value_expr += string("&(") + payee_value_expr + ")";
+ else if (! note_value_expr.empty())
+ final_value_expr = string("(") + payee_value_expr + ")";
+ else
+ final_value_expr = payee_value_expr;
+ }
+
+ if (! note_value_expr.empty()) {
+ if (! acct_value_expr.empty() ||
+ ! payee_value_expr.empty())
+ final_value_expr += string("&(") + note_value_expr + ")";
+ else if (acct_value_expr.empty() &&
+ payee_value_expr.empty())
+ final_value_expr = note_value_expr;
+ }
+
+ DEBUG("report.predicate",
+ "Regexp predicate expression = " << final_value_expr);
+
+ return final_value_expr;
+ }
+
+ template <class Formatter = format_xacts,
+ class handler_ptr = xact_handler_ptr,
+ void (report_t::*report_method)(handler_ptr) =
+ &report_t::xacts_report>
+ class reporter
+ {
+ string format_name;
+
+ public:
+ reporter(const string& _format_name)
+ : format_name(_format_name) {}
+
+ value_t operator()(call_scope_t& args)
+ {
+ report_t& report(find_scope<report_t>(args));
+ var_t<string> format(args, format_name);
+
+ if (! report.format_string.empty())
+ *format = report.format_string;
+
+ if (args.value().is_sequence() &&
+ args.value().size() > 1) {
+ if (! report.predicate.empty())
+ report.predicate = string("(") + report.predicate + ")&";
+ report.predicate +=
+ args_to_predicate(++args.value().as_sequence().begin(),
+ args.value().as_sequence().end());
+ }
+
+ (report.*report_method)(handler_ptr(new Formatter(report, *format)));
+
+ return true;
+ }
+ };
+
+ int read_and_report(ledger::report_t& report,
+ int argc, char * argv[], char * envp[])
+ {
+ using namespace ledger;
+
+ session_t& session(report.session);
+
+ // Handle the command-line arguments
+
+ strings_list args;
+ process_arguments(argc - 1, argv + 1, false, report, args);
+
+ if (args.empty()) {
+ ledger::help(std::cout);
+ return 1;
+ }
+ strings_list::iterator arg = args.begin();
+
+ if (! session.cache_file)
+ session.use_cache = false;
+ else
+ session.use_cache = ! session.data_file.empty() && session.price_db;
+
+ DEBUG("ledger.session.cache", "1. use_cache = " << session.use_cache);
+
+ // Process the environment settings
+
+ TRACE_START(environment, 1, "Processed environment variables");
+ process_environment(const_cast<const char **>(envp), "LEDGER_", report);
+ TRACE_FINISH(environment, 1);
+
+ optional<path> home;
+ if (const char * home_var = std::getenv("HOME"))
+ home = home_var;
+
+ if (! session.init_file)
+ session.init_file = home ? *home / ".ledgerrc" : "./.ledgerrc";
+ if (! session.price_db)
+ session.price_db = home ? *home / ".pricedb" : "./.pricedb";
+
+ if (! session.cache_file)
+ session.cache_file = home ? *home / ".ledger-cache" : "./.ledger-cache";
+
+ if (session.data_file == *session.cache_file)
+ session.use_cache = false;
+
+ DEBUG("ledger.session.cache", "2. use_cache = " << session.use_cache);
+
+ INFO("Initialization file is " << session.init_file->string());
+ INFO("Price database is " << session.price_db->string());
+ INFO("Binary cache is " << session.cache_file->string());
+ INFO("Journal file is " << session.data_file.string());
+
+ if (! session.use_cache)
+ INFO("Binary cache mechanism will not be used");
+
+ // Configure the output stream
+
+#ifdef HAVE_UNIX_PIPES
+ int status, pfd[2]; // Pipe file descriptors
+#endif
+ report.output_stream = &std::cout;
+
+ if (report.output_file) {
+ report.output_stream = new ofstream(*report.output_file);
+ }
+#ifdef HAVE_UNIX_PIPES
+ else if (report.pager) {
+ status = pipe(pfd);
+ if (status == -1)
+ throw_(std::logic_error, "Failed to create pipe");
+
+ status = fork();
+ if (status < 0) {
+ throw_(std::logic_error, "Failed to fork child process");
+ }
+ else if (status == 0) { // child
+ // Duplicate pipe's reading end into stdin
+ status = dup2(pfd[0], STDIN_FILENO);
+ if (status == -1)
+ perror("dup2");
+
+ // Close unuseful file descriptors: the pipe's writing and
+ // reading ends (the latter is not needed anymore, after the
+ // duplication).
+ close(pfd[1]);
+ close(pfd[0]);
+
+ // Find command name: its the substring starting right of the
+ // rightmost '/' character in the pager pathname. See manpage
+ // for strrchr.
+ execlp(report.pager->native_file_string().c_str(),
+ basename(*report.pager).c_str(), NULL);
+ perror("execl");
+ exit(1);
+ }
+ else { // parent
+ close(pfd[0]);
+ report.output_stream = new boost::fdostream(pfd[1]);
+ }
+ }
+#endif
+
+ // Read the command word and see if it's any of the debugging commands
+ // that Ledger supports.
+
+ std::ostream& out(*report.output_stream);
+
+ string verb = *arg++;
+
+ if (verb == "parse") {
+ expr_t expr(*arg);
+
+ out << "Value expression as input: " << *arg << std::endl;
+
+ out << "Value expression as parsed: ";
+ expr.print(out, report);
+ out << std::endl;
+
+ out << std::endl;
+ out << "--- Parsed tree ---" << std::endl;
+ expr.dump(out);
+
+ out << std::endl;
+ out << "--- Calculated value ---" << std::endl;
+ expr.calc(report).print(out);
+ out << std::endl;
+
+ return 0;
+ }
+ else if (verb == "compile") {
+ expr_t expr(*arg);
+
+ out << "Value expression as input: " << *arg << std::endl;
+ out << "Value expression as parsed: ";
+ expr.print(out, report);
+ out << std::endl;
+
+ out << std::endl;
+ out << "--- Parsed tree ---" << std::endl;
+ expr.dump(out);
+
+ expr.compile(report);
+
+ out << std::endl;
+ out << "--- Compiled tree ---" << std::endl;
+ expr.dump(out);
+
+ out << std::endl;
+ out << "--- Calculated value ---" << std::endl;
+ expr.calc(report).print(out);
+ out << std::endl;
+
+ return 0;
+ }
+ else if (verb == "eval") {
+ expr_t expr(*arg);
+ out << expr.calc(report).strip_annotations() << std::endl;
+ return 0;
+ }
+ else if (verb == "format") {
+ format_t fmt(*arg);
+ fmt.dump(out);
+ return 0;
+ }
+ else if (verb == "period") {
+ interval_t interval(*arg);
+
+ if (! is_valid(interval.begin)) {
+ out << "Time period has no beginning." << std::endl;
+ } else {
+ out << "begin: " << format_date(interval.begin) << std::endl;
+ out << " end: " << format_date(interval.end) << std::endl;
+ out << std::endl;
+
+ date_t date = interval.first();
+
+ for (int i = 0; i < 20; i++) {
+ out << std::right;
+ out.width(2);
+
+ out << i << ": " << format_date(date) << std::endl;
+
+ date = interval.increment(date);
+ if (is_valid(interval.end) && date >= interval.end)
+ break;
+ }
+ }
+ return 0;
+ }
+
+ // Parse the initialization file, which can only be textual; then
+ // parse the journal data.
+
+ session.read_init();
+
+ INFO_START(journal, "Read journal file");
+
+ journal_t& journal(*session.create_journal());
+
+ std::size_t count = session.read_data(journal, report.account);
+ if (count == 0)
+ throw_(parse_error, "Failed to locate any journal entries; "
+ "did you specify a valid file with -f?");
+
+ INFO_FINISH(journal);
+
+ INFO("Found " << count << " entries");
+
+ TRACE_FINISH(entry_text, 1);
+ TRACE_FINISH(entry_date, 1);
+ TRACE_FINISH(entry_details, 1);
+ TRACE_FINISH(entry_xacts, 1);
+ TRACE_FINISH(entries, 1);
+ TRACE_FINISH(parsing_total, 1);
+
+ // Create a command object based on the command verb that was seen
+ // above.
+
+ function_t command;
+
+ if (verb == "register" || verb == "reg" || verb == "r")
+ command = reporter<>("register_format");
+ else if (verb == "print" || verb == "p")
+ command = reporter<>("print_format");
+ else if (verb == "balance" || verb == "bal" || verb == "b")
+ command = reporter<format_accounts, acct_handler_ptr,
+ &report_t::accounts_report>("balance_format");
+ else if (verb == "equity")
+ command = reporter<format_equity, acct_handler_ptr,
+ &report_t::accounts_report>("print_format");
+#if 0
+ else if (verb == "entry")
+ command = entry_command();
+ else if (verb == "dump")
+ command = dump_command();
+ else if (verb == "output")
+ command = output_command();
+ else if (verb == "prices")
+ command = prices_command();
+ else if (verb == "pricesdb")
+ command = pricesdb_command();
+ else if (verb == "csv")
+ command = csv_command();
+ else if (verb == "emacs" || verb == "lisp")
+ command = emacs_command();
+ else if (verb == "xml")
+ command = xml_command();
+#endif
+ else {
+ char buf[128];
+ std::strcpy(buf, "cmd_");
+ std::strcat(buf, verb.c_str());
+
+ if (expr_t::ptr_op_t def = report.lookup(buf))
+ command = def->as_function();
+
+ if (! command)
+ throw_(std::logic_error, string("Unrecognized command '") + verb + "'");
+ }
+
+ // Create an argument scope containing the report command's
+ // arguments, and then invoke the command.
+
+ call_scope_t command_args(report);
+
+ for (strings_list::iterator i = arg; i != args.end(); i++)
+ command_args.push_back(string_value(*i));
+
+ INFO_START(command, "Did user command '" << verb << "'");
+
+ command(command_args);
+
+ INFO_FINISH(command);
+
+#if 0
+ // Write out the binary cache, if need be
+
+ if (session.use_cache && session.cache_dirty && session.cache_file) {
+ TRACE_START(binary_cache, 1, "Wrote binary journal file");
+
+ ofstream stream(*session.cache_file);
+ journal.write(stream);
+
+ TRACE_FINISH(binary_cache, 1);
+ }
+#endif
+
+ // If the user specified a pager, wait for it to exit now
+
+#ifdef HAVE_UNIX_PIPES
+ if (! report.output_file && report.pager) {
+ checked_delete(report.output_stream);
+ close(pfd[1]);
+
+ // Wait for child to finish
+ wait(&status);
+ if (status & 0xffff != 0)
+ throw_(std::logic_error, "Something went wrong in the pager");
+ }
+#endif
+ else if (DO_VERIFY() && report.output_file) {
+ checked_delete(report.output_stream);
+ }
+
+ return 0;
+ }
+}
+
+int main(int argc, char * argv[], char * envp[])
+{
+ int status = 1;
+
+ for (int i = 1; i < argc; i++)
+ if (argv[i][0] == '-') {
+ if (std::strcmp(argv[i], "--verify") == 0) {
+#if defined(VERIFY_ON)
+ ledger::verify_enabled = true;
+#endif
+ }
+ else if (std::strcmp(argv[i], "--verbose") == 0 ||
+ std::strcmp(argv[i], "-v") == 0) {
+#if defined(LOGGING_ON)
+ ledger::_log_level = ledger::LOG_INFO;
+#endif
+ }
+ else if (i + 1 < argc && std::strcmp(argv[i], "--debug") == 0) {
+#if defined(DEBUG_ON)
+ ledger::_log_level = ledger::LOG_DEBUG;
+ ledger::_log_category = argv[i + 1];
+ i++;
+#endif
+ }
+ else if (i + 1 < argc && std::strcmp(argv[i], "--trace") == 0) {
+#if defined(TRACING_ON)
+ ledger::_log_level = ledger::LOG_TRACE;
+ try {
+ ledger::_trace_level = boost::lexical_cast<int>(argv[i + 1]);
+ }
+ catch (const boost::bad_lexical_cast& e) {
+ std::cerr << "Argument to --trace must be an integer."
+ << std::endl;
+ return 1;
+ }
+ i++;
+#endif
+ }
+ }
+
+ IF_VERIFY()
+ ledger::initialize_memory_tracing();
+
+ try {
+ std::ios::sync_with_stdio(false);
+
+ boost::filesystem::path::default_name_check
+ (boost::filesystem::portable_posix_name);
+
+ INFO("Ledger starting");
+
+ std::auto_ptr<ledger::session_t> session(new ledger::session_t);
+
+ ledger::set_session_context(session.get());
+
+#if 0
+ session->register_parser(new ledger::journal_t::binary_parser_t);
+#endif
+#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
+ session->register_parser(new ledger::xml_parser_t);
+ session->register_parser(new ledger::gnucash_parser_t);
+#endif
+#ifdef HAVE_LIBOFX
+ session->register_parser(new ledger::ofx_parser_t);
+#endif
+ session->register_parser(new ledger::qif_parser_t);
+ session->register_parser(new ledger::textual_parser_t);
+
+ session->current_report.reset(new ledger::report_t(*session.get()));
+
+ status = read_and_report(*session->current_report.get(), argc, argv, envp);
+
+ if (DO_VERIFY())
+ ledger::set_session_context();
+ else
+ session.release(); // don't free anything! just let it leak
+ }
+ catch (const std::exception& err) {
+ std::cout.flush();
+ std::cerr << "Error: " << ledger::error_context() << err.what()
+ << std::endl;
+ }
+ catch (int _status) {
+ status = _status;
+ }
+
+ IF_VERIFY() {
+ INFO("Ledger ended (Boost/libstdc++ may still hold memory)");
+ ledger::shutdown_memory_tracing();
+ } else {
+ INFO("Ledger ended");
+ }
+
+ return status;
+}
+
+// main.cc ends here.
diff --git a/src/mask.cc b/src/mask.cc
new file mode 100644
index 00000000..b32be359
--- /dev/null
+++ b/src/mask.cc
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2003-2008, 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 "mask.h"
+#include "binary.h"
+
+namespace ledger {
+
+mask_t::mask_t(const string& pat) : expr()
+{
+ TRACE_CTOR(mask_t, "const string&");
+ *this = pat;
+}
+
+mask_t& mask_t::operator=(const string& pat)
+{
+ expr.assign(pat.c_str(), regex::perl | regex::icase);
+ return *this;
+}
+
+void mask_t::read(const char *& data)
+{
+ *this = binary::read_string(data);
+}
+
+void mask_t::write(std::ostream& out) const
+{
+ binary::write_string(out, expr.str());
+}
+
+} // namespace ledger
diff --git a/src/mask.h b/src/mask.h
new file mode 100644
index 00000000..06161d12
--- /dev/null
+++ b/src/mask.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2003-2008, 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 _MASK_H
+#define _MASK_H
+
+#include "utils.h"
+
+namespace ledger {
+
+class mask_t
+{
+ mask_t();
+
+public:
+ boost::regex expr;
+
+ explicit mask_t(const string& pattern);
+
+ mask_t(const mask_t& m) : expr(m.expr) {
+ TRACE_CTOR(mask_t, "copy");
+ }
+ ~mask_t() throw() {
+ TRACE_DTOR(mask_t);
+ }
+
+ mask_t& operator=(const string& other);
+
+ bool match(const string& str) const {
+ return boost::regex_search(str, expr);
+ }
+
+ void read(const char *& data);
+ void write(std::ostream& out) const;
+};
+
+} // namespace ledger
+
+#endif // _MASK_H
diff --git a/src/ofx.cc b/src/ofx.cc
new file mode 100644
index 00000000..5b49bb29
--- /dev/null
+++ b/src/ofx.cc
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2003-2008, 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 "journal.h"
+#include "ofx.h"
+#include "format.h"
+#include "datetime.h"
+#include "error.h"
+#include "debug.h"
+#include "util.h"
+
+#include <libofx.h>
+
+namespace ledger {
+
+typedef std::map<const string, account_t *> accounts_map;
+typedef std::pair<const string, account_t *> accounts_pair;
+
+typedef std::map<const string, commodity_t *> commodities_map;
+typedef std::pair<const string, commodity_t *> commodities_pair;
+
+journal_t * curr_journal;
+accounts_map ofx_accounts;
+commodities_map ofx_account_currencies;
+commodities_map ofx_securities;
+account_t * master_account;
+
+int ofx_proc_statement_cb(struct OfxStatementData data, void * statement_data)
+{
+}
+
+int ofx_proc_account_cb(struct OfxAccountData data, void * account_data)
+{
+ if (! data.account_id_valid)
+ return -1;
+
+ DEBUG("ledger.ofx.parse", "account " << data.account_name);
+ account_t * account = new account_t(master_account, data.account_name);
+ curr_journal->add_account(account);
+ ofx_accounts.insert(accounts_pair(data.account_id, account));
+
+ if (data.currency_valid) {
+ commodity_t * commodity = commodity_t::find_or_create(data.currency);
+ commodity->add_flags(COMMODITY_STYLE_SUFFIXED | COMMODITY_STYLE_SEPARATED);
+
+ commodities_map::iterator i = ofx_account_currencies.find(data.account_id);
+ if (i == ofx_account_currencies.end())
+ ofx_account_currencies.insert(commodities_pair(data.account_id,
+ commodity));
+ }
+
+ return 0;
+}
+
+int ofx_proc_xact_cb(struct OfxXactData data,
+ void * xact_data)
+{
+ if (! data.account_id_valid || ! data.units_valid)
+ return -1;
+
+ accounts_map::iterator i = ofx_accounts.find(data.account_id);
+ assert(i != ofx_accounts.end());
+ account_t * account = (*i).second;
+
+ entry_t * entry = new entry_t;
+
+ entry->add_xact(new xact_t(account));
+ xact_t * xact = entry->xacts.back();
+
+ // get the account's default currency
+ commodities_map::iterator ac = ofx_account_currencies.find(data.account_id);
+ assert(ac != ofx_account_currencies.end());
+ commodity_t * default_commodity = (*ac).second;
+
+ std::ostringstream stream;
+ stream << - data.units;
+
+ // jww (2005-02-09): what if the amount contains fees?
+
+ if (data.unique_id_valid) {
+ commodities_map::iterator s = ofx_securities.find(data.unique_id);
+ assert(s != ofx_securities.end());
+ xact->amount = stream.str() + " " + (*s).second->base_symbol();
+ } else {
+ xact->amount = stream.str() + " " + default_commodity->base_symbol();
+ }
+
+ if (data.unitprice_valid && data.unitprice != 1.0) {
+ std::ostringstream cstream;
+ stream << - data.unitprice;
+ xact->cost = new amount_t(stream.str() + " " + default_commodity->base_symbol());
+ }
+
+ DEBUG("ledger.ofx.parse", "xact " << xact->amount
+ << " from " << *xact->account);
+
+ if (data.date_initiated_valid)
+ entry->_date = data.date_initiated;
+ else if (data.date_posted_valid)
+ entry->_date = data.date_posted;
+
+ if (data.check_number_valid)
+ entry->code = data.check_number;
+ else if (data.reference_number_valid)
+ entry->code = data.reference_number;
+
+ if (data.name_valid)
+ entry->payee = data.name;
+
+ if (data.memo_valid)
+ xact->note = data.memo;
+
+ // jww (2005-02-09): check for fi_id_corrected? or is this handled
+ // by the library?
+
+ // Balance all entries into <Unknown>, since it is not specified.
+ account = curr_journal->find_account("<Unknown>");
+ entry->add_xact(new xact_t(account));
+
+ if (! curr_journal->add_entry(entry)) {
+ print_entry(std::cerr, *entry);
+ // jww (2005-02-09): uncomment
+ //have_error = "The above entry does not balance";
+ checked_delete(entry);
+ return -1;
+ }
+ return 0;
+}
+
+int ofx_proc_security_cb(struct OfxSecurityData data, void * security_data)
+{
+ if (! data.unique_id_valid)
+ return -1;
+
+ string symbol;
+ if (data.ticker_valid)
+ symbol = data.ticker;
+ else if (data.currency_valid)
+ symbol = data.currency;
+ else
+ return -1;
+
+ commodity_t * commodity = commodity_t::find_or_create(symbol);
+ commodity->add_flags(COMMODITY_STYLE_SUFFIXED | COMMODITY_STYLE_SEPARATED);
+
+ if (data.secname_valid)
+ commodity->set_name(data.secname);
+
+ if (data.memo_valid)
+ commodity->set_note(data.memo);
+
+ commodities_map::iterator i = ofx_securities.find(data.unique_id);
+ if (i == ofx_securities.end()) {
+ DEBUG("ledger.ofx.parse", "security " << symbol);
+ ofx_securities.insert(commodities_pair(data.unique_id, commodity));
+ }
+
+ // jww (2005-02-09): What is the commodity for data.unitprice?
+ if (data.date_unitprice_valid && data.unitprice_valid) {
+ DEBUG("ledger.ofx.parse", " price " << data.unitprice);
+ commodity->add_price(data.date_unitprice, amount_t(data.unitprice));
+ }
+
+ return 0;
+}
+
+int ofx_proc_status_cb(struct OfxStatusData data, void * status_data)
+{
+}
+
+bool ofx_parser_t::test(std::istream& in) const
+{
+ char buf[80];
+
+ in.getline(buf, 79);
+ if (std::strncmp(buf, "OFXHEADER", 9) == 0) {
+ in.clear();
+ in.seekg(0, std::ios::beg);
+ return true;
+ }
+ else if (std::strncmp(buf, "<?xml", 5) != 0) {
+ in.clear();
+ in.seekg(0, std::ios::beg);
+ return false;
+ }
+
+ in.getline(buf, 79);
+ if (std::strncmp(buf, "<?OFX", 5) != 0 &&
+ std::strncmp(buf, "<?ofx", 5) != 0) {
+ in.clear();
+ in.seekg(0, std::ios::beg);
+ return false;
+ }
+
+ in.clear();
+ in.seekg(0, std::ios::beg);
+ return true;
+}
+
+unsigned int ofx_parser_t::parse(std::istream& in,
+ session_t& session,
+ journal_t& journal,
+ account_t * master,
+ const path * original_file)
+{
+ if (! original_file)
+ return 0;
+
+ curr_journal = journal;
+ master_account = master ? master : journal->master;
+
+ LibofxContextPtr libofx_context = libofx_get_new_context();
+
+ ofx_set_statement_cb (libofx_context, ofx_proc_statement_cb, 0);
+ ofx_set_account_cb (libofx_context, ofx_proc_account_cb, 0);
+ ofx_set_xact_cb (libofx_context, ofx_proc_xact_cb, 0);
+ ofx_set_security_cb (libofx_context, ofx_proc_security_cb, 0);
+ ofx_set_status_cb (libofx_context, ofx_proc_status_cb, 0);
+
+ // The processing is done by way of callbacks, which are all defined
+ // above.
+ libofx_proc_file(libofx_context, original_file->c_str(), AUTODETECT);
+
+ libofx_free_context(libofx_context);
+
+ return 1; // jww (2005-02-09): count;
+}
+
+} // namespace ledger
diff --git a/src/ofx.h b/src/ofx.h
new file mode 100644
index 00000000..58512e8b
--- /dev/null
+++ b/src/ofx.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2003-2008, 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 _OFX_H
+#define _OFX_H
+
+#include "journal.h"
+
+namespace ledger {
+
+class ofx_parser_t : public journal_t::parser_t
+{
+public:
+ virtual bool test(std::istream& in) const;
+
+ virtual unsigned int parse(std::istream& in,
+ session_t& session,
+ journal_t& journal,
+ account_t * master = NULL,
+ const path * original_file = NULL);
+};
+
+} // namespace ledger
+
+#endif // _OFX_H
diff --git a/src/op.cc b/src/op.cc
new file mode 100644
index 00000000..b412c47f
--- /dev/null
+++ b/src/op.cc
@@ -0,0 +1,1130 @@
+/*
+ * Copyright (c) 2003-2008, 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 "op.h"
+#include "scope.h"
+#include "binary.h"
+
+namespace ledger {
+
+#if 0
+void expr_t::op_t::compute(value_t& result,
+ const details_t& details,
+ ptr_op_t context) const
+{
+ try {
+ switch (kind) {
+ case INDEX:
+ throw compute_error("Cannot directly compute an argument index");
+
+ case VALUE:
+ result = as_value();
+ break;
+
+ case F_NOW:
+ result = terminus;
+ break;
+
+ case AMOUNT:
+ if (details.xact) {
+ if (xact_has_xdata(*details.xact) &&
+ xact_xdata_(*details.xact).dflags & XACT_COMPOUND)
+ result = xact_xdata_(*details.xact).value;
+ else
+ result = details.xact->amount;
+ }
+ else if (details.account && account_has_xdata(*details.account)) {
+ result = account_xdata(*details.account).value;
+ }
+ else {
+ result = 0L;
+ }
+ break;
+
+ case PRICE:
+ if (details.xact) {
+ bool set = false;
+ if (xact_has_xdata(*details.xact)) {
+ xact_xdata_t& xdata(xact_xdata_(*details.xact));
+ if (xdata.dflags & XACT_COMPOUND) {
+ result = xdata.value.value();
+ set = true;
+ }
+ }
+ if (! set) {
+ optional<amount_t> value = details.xact->amount.value();
+ if (value)
+ result = *value;
+ else
+ result = 0L;
+ }
+ }
+ else if (details.account && account_has_xdata(*details.account)) {
+ result = account_xdata(*details.account).value.value();
+ }
+ else {
+ result = 0L;
+ }
+ break;
+
+ case COST:
+ if (details.xact) {
+ bool set = false;
+ if (xact_has_xdata(*details.xact)) {
+ xact_xdata_t& xdata(xact_xdata_(*details.xact));
+ if (xdata.dflags & XACT_COMPOUND) {
+ result = xdata.value.cost();
+ set = true;
+ }
+ }
+
+ if (! set) {
+ if (details.xact->cost)
+ result = *details.xact->cost;
+ else
+ result = details.xact->amount;
+ }
+ }
+ else if (details.account && account_has_xdata(*details.account)) {
+ result = account_xdata(*details.account).value.cost();
+ }
+ else {
+ result = 0L;
+ }
+ break;
+
+ case TOTAL:
+ if (details.xact && xact_has_xdata(*details.xact))
+ result = xact_xdata_(*details.xact).total;
+ else if (details.account && account_has_xdata(*details.account))
+ result = account_xdata(*details.account).total;
+ else
+ result = 0L;
+ break;
+ case PRICE_TOTAL:
+ if (details.xact && xact_has_xdata(*details.xact))
+ result = xact_xdata_(*details.xact).total.value();
+ else if (details.account && account_has_xdata(*details.account))
+ result = account_xdata(*details.account).total.value();
+ else
+ result = 0L;
+ break;
+ case COST_TOTAL:
+ if (details.xact && xact_has_xdata(*details.xact))
+ result = xact_xdata_(*details.xact).total.cost();
+ else if (details.account && account_has_xdata(*details.account))
+ result = account_xdata(*details.account).total.cost();
+ else
+ result = 0L;
+ break;
+
+ case VALUE_EXPR:
+ if (value_expr::amount_expr.get())
+ value_expr::amount_expr->compute(result, details, context);
+ else
+ result = 0L;
+ break;
+ case TOTAL_EXPR:
+ if (value_expr::total_expr.get())
+ value_expr::total_expr->compute(result, details, context);
+ else
+ result = 0L;
+ break;
+
+ case DATE:
+ if (details.xact && xact_has_xdata(*details.xact) &&
+ is_valid(xact_xdata_(*details.xact).date))
+ result = xact_xdata_(*details.xact).date;
+ else if (details.xact)
+ result = details.xact->date();
+ else if (details.entry)
+ result = details.entry->date();
+ else
+ result = terminus;
+ break;
+
+ case ACT_DATE:
+ if (details.xact && xact_has_xdata(*details.xact) &&
+ is_valid(xact_xdata_(*details.xact).date))
+ result = xact_xdata_(*details.xact).date;
+ else if (details.xact)
+ result = details.xact->actual_date();
+ else if (details.entry)
+ result = details.entry->actual_date();
+ else
+ result = terminus;
+ break;
+
+ case EFF_DATE:
+ if (details.xact && xact_has_xdata(*details.xact) &&
+ is_valid(xact_xdata_(*details.xact).date))
+ result = xact_xdata_(*details.xact).date;
+ else if (details.xact)
+ result = details.xact->effective_date();
+ else if (details.entry)
+ result = details.entry->effective_date();
+ else
+ result = terminus;
+ break;
+
+ case CLEARED:
+ if (details.xact)
+ result = details.xact->state == xact_t::CLEARED;
+ else
+ result = false;
+ break;
+ case PENDING:
+ if (details.xact)
+ result = details.xact->state == xact_t::PENDING;
+ else
+ result = false;
+ break;
+
+ case REAL:
+ if (details.xact)
+ result = ! (details.xact->has_flags(XACT_VIRTUAL));
+ else
+ result = true;
+ break;
+
+ case ACTUAL:
+ if (details.xact)
+ result = ! (details.xact->has_flags(XACT_AUTO));
+ else
+ result = true;
+ break;
+
+ case INDEX:
+ if (details.xact && xact_has_xdata(*details.xact))
+ result = long(xact_xdata_(*details.xact).index + 1);
+ else if (details.account && account_has_xdata(*details.account))
+ result = long(account_xdata(*details.account).count);
+ else
+ result = 0L;
+ break;
+
+ case COUNT:
+ if (details.xact && xact_has_xdata(*details.xact))
+ result = long(xact_xdata_(*details.xact).index + 1);
+ else if (details.account && account_has_xdata(*details.account))
+ result = long(account_xdata(*details.account).total_count);
+ else
+ result = 0L;
+ break;
+
+ case DEPTH:
+ if (details.account)
+ result = long(details.account->depth);
+ else
+ result = 0L;
+ break;
+
+ case F_PRICE: {
+ long arg_index = 0;
+ ptr_op_t expr = find_leaf(context, 0, arg_index);
+ expr->compute(result, details, context);
+ result = result.value();
+ break;
+ }
+
+ case F_DATE: {
+ long arg_index = 0;
+ ptr_op_t expr = find_leaf(context, 0, arg_index);
+ expr->compute(result, details, context);
+ result = result.as_datetime();
+ break;
+ }
+
+ case F_DATECMP: {
+ long arg_index = 0;
+ ptr_op_t expr = find_leaf(context, 0, arg_index);
+ expr->compute(result, details, context);
+ result = result.as_datetime();
+ if (! result)
+ break;
+
+ arg_index = 0;
+ expr = find_leaf(context, 1, arg_index);
+ value_t moment;
+ expr->compute(moment, details, context);
+ if (moment.is_type(value_t::DATETIME)) {
+ result.cast(value_t::INTEGER);
+ moment.cast(value_t::INTEGER);
+ result -= moment;
+ } else {
+ add_error_context(expr_context(expr));
+ throw compute_error("Invalid date passed to datecmp(value,date)");
+ }
+ break;
+ }
+
+ case F_YEAR:
+ case F_MONTH:
+ case F_DAY: {
+ long arg_index = 0;
+ ptr_op_t expr = find_leaf(context, 0, arg_index);
+ expr->compute(result, details, context);
+
+ if (! result.is_type(value_t::DATETIME)) {
+ add_error_context(expr_context(expr));
+ throw compute_error("Invalid date passed to year|month|day(date)");
+ }
+
+ const date_t& moment(result.as_date());
+ switch (kind) {
+ case F_YEAR:
+ result = (long)moment.year();
+ break;
+ case F_MONTH:
+ result = (long)moment.month();
+ break;
+ case F_DAY:
+ result = (long)moment.day();
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+
+ case F_ARITH_MEAN: {
+ long arg_index = 0;
+ ptr_op_t expr = find_leaf(context, 0, arg_index);
+ if (details.xact && xact_has_xdata(*details.xact)) {
+ expr->compute(result, details, context);
+ result /= amount_t(long(xact_xdata_(*details.xact).index + 1));
+ }
+ else if (details.account && account_has_xdata(*details.account) &&
+ account_xdata(*details.account).total_count) {
+ expr->compute(result, details, context);
+ result /= amount_t(long(account_xdata(*details.account).total_count));
+ }
+ else {
+ result = 0L;
+ }
+ break;
+ }
+
+ case F_PARENT:
+ if (details.account && details.account->parent)
+ left()->compute(result, details_t(*details.account->parent), context);
+ break;
+
+ case F_ABS: {
+ long arg_index = 0;
+ ptr_op_t expr = find_leaf(context, 0, arg_index);
+ expr->compute(result, details, context);
+ result.abs();
+ break;
+ }
+
+ case F_ROUND: {
+ long arg_index = 0;
+ ptr_op_t expr = find_leaf(context, 0, arg_index);
+ expr->compute(result, details, context);
+ result.round();
+ break;
+ }
+
+ case F_COMMODITY: {
+ long arg_index = 0;
+ ptr_op_t expr = find_leaf(context, 0, arg_index);
+ expr->compute(result, details, context);
+ if (! result.is_type(value_t::AMOUNT)) {
+ add_error_context(expr_context(expr));
+ throw compute_error("Argument to commodity() must be a commoditized amount");
+ }
+ amount_t temp("1");
+ temp.set_commodity(result.as_amount().commodity());
+ result = temp;
+ break;
+ }
+
+ case F_SET_COMMODITY: {
+ long arg_index = 0;
+ ptr_op_t expr = find_leaf(context, 0, arg_index);
+ value_t temp;
+ expr->compute(temp, details, context);
+
+ arg_index = 0;
+ expr = find_leaf(context, 1, arg_index);
+ expr->compute(result, details, context);
+ if (! result.is_type(value_t::AMOUNT)) {
+ add_error_context(expr_context(expr));
+ throw compute_error("Second argument to set_commodity() must be a commoditized amount");
+ }
+ amount_t one("1");
+ one.set_commodity(result.as_amount().commodity());
+ result = one;
+
+ result *= temp;
+ break;
+ }
+
+ case F_QUANTITY: {
+ long arg_index = 0;
+ ptr_op_t expr = find_leaf(context, 0, arg_index);
+ expr->compute(result, details, context);
+
+ const balance_t * bal = NULL;
+ switch (result.type()) {
+ case value_t::BALANCE_PAIR:
+ bal = &(result.as_balance_pair().quantity());
+ // fall through...
+
+ case value_t::BALANCE:
+ if (! bal)
+ bal = &result.as_balance();
+
+ if (bal->amounts.size() < 2) {
+ result.cast(value_t::AMOUNT);
+ } else {
+ value_t temp;
+ for (balance_t::amounts_map::value_type pair, bal->amounts) {
+ amount_t x = pair.second;
+ x.clear_commodity();
+ temp += x;
+ }
+ result = temp;
+ assert(temp.is_type(value_t::AMOUNT));
+ }
+ // fall through...
+
+ case value_t::AMOUNT:
+ result.as_amount_lval().clear_commodity();
+ break;
+
+ default:
+ break;
+ }
+ break;
+ }
+
+ case O_ARG: {
+ long arg_index = 0;
+ assert(left()->kind == INDEX);
+ ptr_op_t expr = find_leaf(context, left()->as_long(), arg_index);
+ if (expr)
+ expr->compute(result, details, context);
+ else
+ result = 0L;
+ break;
+ }
+
+ case O_COMMA:
+ if (! left()) {
+ add_error_context(expr_context(*this));
+ throw compute_error("Comma operator missing left operand");
+ }
+ if (! right()) {
+ add_error_context(expr_context(*this));
+ throw compute_error("Comma operator missing right operand");
+ }
+ right()->compute(result, details, context);
+ break;
+
+ case O_DEF:
+ result = 0L;
+ break;
+
+ case O_REF: {
+ assert(left());
+ if (right()) {
+ value_expr args(reduce_leaves(right(), details, context));
+ left()->compute(result, details, args.get());
+ } else {
+ left()->compute(result, details, context);
+ }
+ break;
+ }
+
+ case F_VALUE: {
+ long arg_index = 0;
+ ptr_op_t expr = find_leaf(context, 0, arg_index);
+ expr->compute(result, details, context);
+
+ arg_index = 0;
+ expr = find_leaf(context, 1, arg_index);
+ value_t moment;
+ expr->compute(moment, details, context);
+ if (! moment.is_type(value_t::DATETIME)) {
+ add_error_context(expr_context(expr));
+ throw compute_error("Invalid date passed to P(value,date)");
+ }
+ result = result.value(moment.as_datetime());
+ break;
+ }
+
+ case O_NOT:
+ left()->compute(result, details, context);
+ if (result.strip_annotations())
+ result = false;
+ else
+ result = true;
+ break;
+
+ case O_QUES: {
+ assert(left());
+ assert(right());
+ assert(right()->kind == O_COL);
+ left()->compute(result, details, context);
+ if (result.strip_annotations())
+ right()->left()->compute(result, details, context);
+ else
+ right()->right()->compute(result, details, context);
+ break;
+ }
+
+ case O_AND:
+ assert(left());
+ assert(right());
+ left()->compute(result, details, context);
+ result = result.strip_annotations();
+ if (result)
+ right()->compute(result, details, context);
+ break;
+
+ case O_OR:
+ assert(left());
+ assert(right());
+ left()->compute(result, details, context);
+ if (! result.strip_annotations())
+ right()->compute(result, details, context);
+ break;
+
+ case O_NEQ:
+ case O_EQ:
+ case O_LT:
+ case O_LTE:
+ case O_GT:
+ case O_GTE: {
+ assert(left());
+ assert(right());
+ value_t temp;
+ left()->compute(temp, details, context);
+ right()->compute(result, details, context);
+ switch (kind) {
+ case O_NEQ: result = temp != result; break;
+ case O_EQ: result = temp == result; break;
+ case O_LT: result = temp < result; break;
+ case O_LTE: result = temp <= result; break;
+ case O_GT: result = temp > result; break;
+ case O_GTE: result = temp >= result; break;
+ default: assert(false); break;
+ }
+ break;
+ }
+
+ case O_NEG:
+ assert(left());
+ left()->compute(result, details, context);
+ result.negate();
+ break;
+
+ case O_ADD:
+ case O_SUB:
+ case O_MUL:
+ case O_DIV: {
+ assert(left());
+ assert(right());
+ value_t temp;
+ right()->compute(temp, details, context);
+ left()->compute(result, details, context);
+ switch (kind) {
+ case O_ADD: result += temp; break;
+ case O_SUB: result -= temp; break;
+ case O_MUL: result *= temp; break;
+ case O_DIV: result /= temp; break;
+ default: assert(false); break;
+ }
+ break;
+ }
+
+ case O_PERC: {
+ assert(left());
+ result = "100.0%";
+ value_t temp;
+ left()->compute(temp, details, context);
+ result *= temp;
+ break;
+ }
+
+ case LAST:
+ default:
+ assert(false);
+ break;
+ }
+ }
+ catch (const std::exception& err) {
+ add_error_context(expr_context(*this));
+ throw err;
+ }
+}
+#endif
+
+expr_t::ptr_op_t expr_t::op_t::compile(scope_t& scope)
+{
+ switch (kind) {
+ case IDENT:
+ if (ptr_op_t def = scope.lookup(as_ident())) {
+#if 1
+ return def;
+#else
+ // jww (2008-08-02): Aren't definitions compiled when they go in?
+ // Would recompiling here really add any benefit?
+ return def->compile(scope);
+#endif
+ }
+ return this;
+
+ default:
+ break;
+ }
+
+ if (kind < TERMINALS)
+ return this;
+
+ ptr_op_t lhs(left()->compile(scope));
+ ptr_op_t rhs(kind > UNARY_OPERATORS ? right()->compile(scope) : ptr_op_t());
+
+ if (lhs == left() && (! rhs || rhs == right()))
+ return this;
+
+ ptr_op_t intermediate(copy(lhs, rhs));
+
+ if (lhs->is_value() && (! rhs || rhs->is_value()))
+ return wrap_value(intermediate->calc(scope));
+
+ return intermediate;
+}
+
+value_t expr_t::op_t::calc(scope_t& scope)
+{
+ switch (kind) {
+ case VALUE:
+ return as_value();
+
+ case IDENT:
+#if 0
+ if (ptr_op_t reference = compile(scope)) {
+ if (reference != this)
+ return reference->calc(scope);
+ }
+#endif
+ throw_(calc_error, "Unknown identifier '" << as_ident() << "'");
+
+ case FUNCTION: {
+ // Evaluating a FUNCTION is the same as calling it directly; this happens
+ // when certain functions-that-look-like-variables (such as "amount") are
+ // resolved.
+ call_scope_t call_args(scope);
+ return as_function()(call_args);
+ }
+
+ case O_CALL: {
+ call_scope_t call_args(scope);
+
+ if (right())
+ call_args.set_args(right()->calc(scope));
+
+ ptr_op_t func = left();
+ string name;
+
+#if 0
+ // The expression must be compiled beforehand in order to resolve this
+ // into a function.
+ if (func->kind == IDENT) {
+ name = func->as_ident();
+ ptr_op_t def = func->compile(scope);
+ if (def == func)
+ throw_(calc_error,
+ "Calling unknown function '" << name << "'");
+ func = def;
+ }
+#endif
+
+ if (func->kind != FUNCTION)
+ throw_(calc_error, "Calling non-function");
+
+ return func->as_function()(call_args);
+ }
+
+ case O_MATCH:
+ assert(right()->is_mask());
+ return right()->as_mask().match(left()->calc(scope).to_string());
+
+ case INDEX: {
+ const call_scope_t& args(downcast<const call_scope_t>(scope));
+
+ if (as_index() < args.size())
+ return args[as_index()];
+ else
+ throw_(calc_error, "Reference to non-existing argument " << as_index());
+ break;
+ }
+
+ case O_NEQ:
+ return left()->calc(scope) != right()->calc(scope);
+ case O_EQ:
+ return left()->calc(scope) == right()->calc(scope);
+ case O_LT:
+ return left()->calc(scope) < right()->calc(scope);
+ case O_LTE:
+ return left()->calc(scope) <= right()->calc(scope);
+ case O_GT:
+ return left()->calc(scope) > right()->calc(scope);
+ case O_GTE:
+ return left()->calc(scope) >= right()->calc(scope);
+
+ case O_ADD:
+ return left()->calc(scope) + right()->calc(scope);
+ case O_SUB:
+ return left()->calc(scope) - right()->calc(scope);
+ case O_MUL:
+ return left()->calc(scope) * right()->calc(scope);
+ case O_DIV:
+ return left()->calc(scope) / right()->calc(scope);
+
+ case O_NEG:
+ assert(! right());
+ return left()->calc(scope).negate();
+
+ case O_NOT:
+ assert(! right());
+ return ! left()->calc(scope);
+
+ case O_AND:
+ return ! left()->calc(scope) ? value_t(false) : right()->calc(scope);
+
+ case O_OR:
+ if (value_t temp = left()->calc(scope))
+ return temp;
+ else
+ return right()->calc(scope);
+
+ case O_COMMA: {
+ value_t result(left()->calc(scope));
+
+ ptr_op_t next = right();
+ while (next) {
+ ptr_op_t value_op;
+ if (next->kind == O_COMMA /* || next->kind == O_UNION */) {
+ value_op = next->left();
+ next = next->right();
+ } else {
+ value_op = next;
+ next = NULL;
+ }
+
+ result.push_back(value_op->calc(scope));
+ }
+ return result;
+ }
+
+ case LAST:
+ default:
+ assert(false);
+ break;
+ }
+
+ return NULL_VALUE;
+}
+
+bool expr_t::op_t::print(std::ostream& out, print_context_t& context) const
+{
+ bool found = false;
+
+ if (context.start_pos && this == context.op_to_find) {
+ *context.start_pos = static_cast<unsigned long>(out.tellp()) - 1;
+ found = true;
+ }
+
+ string symbol;
+
+ switch (kind) {
+ case VALUE: {
+ as_value().print(out, context.relaxed);
+ break;
+ }
+
+ case IDENT:
+ out << as_ident();
+ break;
+
+ case FUNCTION:
+ out << "<FUNCTION>";
+ break;
+
+ case INDEX:
+ out << '@' << as_index();
+ break;
+
+ case O_NOT:
+ out << "!";
+ if (left() && left()->print(out, context))
+ found = true;
+ break;
+ case O_NEG:
+ out << "-";
+ if (left() && left()->print(out, context))
+ found = true;
+ break;
+
+ case O_ADD:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " + ";
+ if (right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_SUB:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " - ";
+ if (right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_MUL:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " * ";
+ if (right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_DIV:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " / ";
+ if (right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+
+ case O_NEQ:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " != ";
+ if (right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_EQ:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " == ";
+ if (right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_LT:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " < ";
+ if (right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_LTE:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " <= ";
+ if (right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_GT:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " > ";
+ if (right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_GTE:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " >= ";
+ if (right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+
+ case O_AND:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " & ";
+ if (right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_OR:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " | ";
+ if (right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+
+ case O_COMMA:
+ if (left() && left()->print(out, context))
+ found = true;
+ out << ", ";
+ if (right() && right()->print(out, context))
+ found = true;
+ break;
+
+ case O_CALL:
+ if (left() && left()->print(out, context))
+ found = true;
+ out << "(";
+ if (right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+
+ case O_MATCH:
+ out << '/';
+ if (left() && left()->print(out, context))
+ found = true;
+ out << "/ =~ ";
+ if (right() && right()->print(out, context))
+ found = true;
+ break;
+
+ case LAST:
+ default:
+ assert(false);
+ break;
+ }
+
+ if (! symbol.empty()) {
+ if (amount_t::current_pool->find(symbol))
+ out << '@';
+ out << symbol;
+ }
+
+ if (context.end_pos && this == context.op_to_find)
+ *context.end_pos = static_cast<unsigned long>(out.tellp()) - 1;
+
+ return found;
+}
+
+void expr_t::op_t::dump(std::ostream& out, const int depth) const
+{
+ out.setf(std::ios::left);
+ out.width(10);
+ out << this;
+
+ for (int i = 0; i < depth; i++)
+ out << " ";
+
+ switch (kind) {
+ case VALUE:
+ out << "VALUE - " << as_value();
+ break;
+
+ case IDENT:
+ out << "IDENT - " << as_ident();
+ break;
+
+ case INDEX:
+ out << "INDEX - " << as_index();
+ break;
+
+ case FUNCTION:
+ out << "FUNCTION";
+ break;
+
+ case O_CALL: out << "O_CALL"; break;
+ case O_MATCH: out << "O_MATCH"; break;
+
+ case O_NOT: out << "O_NOT"; break;
+ case O_NEG: out << "O_NEG"; break;
+
+ case O_ADD: out << "O_ADD"; break;
+ case O_SUB: out << "O_SUB"; break;
+ case O_MUL: out << "O_MUL"; break;
+ case O_DIV: out << "O_DIV"; break;
+
+ case O_NEQ: out << "O_NEQ"; break;
+ case O_EQ: out << "O_EQ"; break;
+ case O_LT: out << "O_LT"; break;
+ case O_LTE: out << "O_LTE"; break;
+ case O_GT: out << "O_GT"; break;
+ case O_GTE: out << "O_GTE"; break;
+
+ case O_AND: out << "O_AND"; break;
+ case O_OR: out << "O_OR"; break;
+
+ case O_COMMA: out << "O_COMMA"; break;
+
+ case LAST:
+ default:
+ assert(false);
+ break;
+ }
+
+ out << " (" << refc << ')' << std::endl;
+
+ if (kind > TERMINALS) {
+ if (left()) {
+ left()->dump(out, depth + 1);
+ if (right())
+ right()->dump(out, depth + 1);
+ } else {
+ assert(! right());
+ }
+ }
+}
+
+void expr_t::op_t::read(const char *& data)
+{
+#if 0
+ if (! read_bool(data))
+ return expr_t::ptr_op_t();
+
+ expr_t::op_t::kind_t kind;
+ read_number(data, kind);
+
+ expr_t::ptr_op_t expr = new expr_t::op_t(kind);
+
+ if (kind > expr_t::op_t::TERMINALS)
+ expr->set_left(read_value_expr(data));
+
+ switch (expr->kind) {
+ case expr_t::op_t::INDEX: {
+ long temp;
+ read_long(data, temp);
+ expr->set_index(temp);
+ break;
+ }
+ case expr_t::op_t::VALUE: {
+ value_t temp;
+ read_value(data, temp);
+ expr->set_value(temp);
+ break;
+ }
+
+ case expr_t::op_t::MASK:
+ if (read_bool(data))
+ read_mask(data, expr->as_mask_lval());
+ break;
+
+ default:
+ if (kind > expr_t::op_t::TERMINALS)
+ expr->set_right(read_value_expr(data));
+ break;
+ }
+
+ return expr;
+#endif
+}
+
+void expr_t::op_t::write(std::ostream& out) const
+{
+#if 0
+ if (! expr) {
+ write_bool(out, false);
+ return;
+ }
+
+ write_bool(out, true);
+ write_number(out, expr->kind);
+
+ if (expr->kind > expr_t::op_t::TERMINALS)
+ write_value_expr(out, expr->left());
+
+ switch (expr->kind) {
+ case expr_t::op_t::INDEX:
+ write_long(out, expr->as_index());
+ break;
+ case expr_t::op_t::IDENT:
+ write_long(out, expr->as_ident());
+ break;
+ case expr_t::op_t::VALUE:
+ write_value(out, expr->as_value());
+ break;
+
+ case expr_t::op_t::MASK:
+ if (expr->as_mask()) {
+ write_bool(out, true);
+ write_mask(out, expr->as_mask());
+ } else {
+ write_bool(out, false);
+ }
+ break;
+
+ default:
+ if (expr->kind > expr_t::op_t::TERMINALS)
+ write_value_expr(out, expr->right());
+ break;
+ }
+#endif
+}
+
+#if 0
+class op_predicate : public noncopyable
+{
+ ptr_op_t op;
+
+ op_predicate();
+
+public:
+ explicit op_predicate(ptr_op_t _op) : op(_op) {
+ TRACE_CTOR(op_predicate, "ptr_op_t");
+ }
+ ~op_predicate() throw() {
+ TRACE_DTOR(op_predicate);
+ }
+ bool operator()(scope_t& scope) {
+ return op->calc(scope).to_boolean();
+ }
+};
+
+#endif
+
+} // namespace ledger
diff --git a/src/op.h b/src/op.h
new file mode 100644
index 00000000..66618360
--- /dev/null
+++ b/src/op.h
@@ -0,0 +1,330 @@
+/*
+ * Copyright (c) 2003-2008, 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 _OP_H
+#define _OP_H
+
+#include "expr.h"
+#include "mask.h"
+
+namespace ledger {
+
+class expr_t::op_t : public noncopyable
+{
+ friend class expr_t;
+ friend class expr_t::parser_t;
+
+ op_t();
+
+public:
+ typedef expr_t::ptr_op_t ptr_op_t;
+
+private:
+ mutable short refc;
+ ptr_op_t left_;
+
+ variant<unsigned int, // used by constant INDEX
+ value_t, // used by constant VALUE
+ string, // used by constant IDENT
+ mask_t, // used by constant MASK
+ function_t, // used by terminal FUNCTION
+ ptr_op_t> // used by all binary operators
+ data;
+
+public:
+ enum kind_t {
+ // Constants
+ VALUE,
+ IDENT,
+ MASK,
+ INDEX,
+
+ CONSTANTS,
+
+ FUNCTION,
+
+ TERMINALS,
+
+ // Binary operators
+ O_NOT,
+ O_NEG,
+
+ UNARY_OPERATORS,
+
+ O_EQ,
+ O_NEQ,
+ O_LT,
+ O_LTE,
+ O_GT,
+ O_GTE,
+
+ O_AND,
+ O_OR,
+
+ O_ADD,
+ O_SUB,
+ O_MUL,
+ O_DIV,
+
+ O_QUERY,
+ O_COLON,
+
+ O_COMMA,
+
+ O_CALL,
+ O_MATCH,
+
+ BINARY_OPERATORS,
+
+ OPERATORS,
+
+ LAST
+ };
+
+ kind_t kind;
+
+ explicit op_t(const kind_t _kind) : refc(0), kind(_kind) {
+ TRACE_CTOR(op_t, "const kind_t");
+ }
+ ~op_t() {
+ TRACE_DTOR(op_t);
+ assert(refc == 0);
+ }
+
+ bool is_index() const {
+ return data.type() == typeid(unsigned int);
+ }
+ unsigned int& as_index_lval() {
+ assert(kind == INDEX);
+ return boost::get<unsigned int>(data);
+ }
+ const unsigned int& as_index() const {
+ return const_cast<op_t *>(this)->as_index_lval();
+ }
+ void set_index(unsigned int val) {
+ data = val;
+ }
+
+ bool is_value() const {
+ if (kind == VALUE) {
+ assert(data.type() == typeid(value_t));
+ return true;
+ }
+ return false;
+ }
+ value_t& as_value_lval() {
+ assert(is_value());
+ value_t& val(boost::get<value_t>(data));
+ assert(val.valid());
+ return val;
+ }
+ const value_t& as_value() const {
+ return const_cast<op_t *>(this)->as_value_lval();
+ }
+ void set_value(const value_t& val) {
+ assert(val.valid());
+ data = val;
+ }
+
+ bool is_ident() const {
+ if (kind == IDENT) {
+ assert(data.type() == typeid(string));
+ return true;
+ }
+ return false;
+ }
+ string& as_ident_lval() {
+ assert(is_ident());
+ return boost::get<string>(data);
+ }
+ const string& as_ident() const {
+ return const_cast<op_t *>(this)->as_ident_lval();
+ }
+ void set_ident(const string& val) {
+ data = val;
+ }
+
+ bool is_mask() const {
+ if (kind == MASK) {
+ assert(data.type() == typeid(mask_t));
+ return true;
+ }
+ return false;
+ }
+ mask_t& as_mask_lval() {
+ assert(is_mask());
+ return boost::get<mask_t>(data);
+ }
+ const mask_t& as_mask() const {
+ return const_cast<op_t *>(this)->as_mask_lval();
+ }
+ void set_mask(const mask_t& val) {
+ data = val;
+ }
+ void set_mask(const string& expr) {
+ data = mask_t(expr);
+ }
+
+ bool is_function() const {
+ return kind == FUNCTION;
+ }
+ function_t& as_function_lval() {
+ assert(kind == FUNCTION);
+ return boost::get<function_t>(data);
+ }
+ const function_t& as_function() const {
+ return const_cast<op_t *>(this)->as_function_lval();
+ }
+ void set_function(const function_t& val) {
+ data = val;
+ }
+
+ ptr_op_t& left() {
+ return left_;
+ }
+ const ptr_op_t& left() const {
+ assert(kind > TERMINALS);
+ return left_;
+ }
+ void set_left(const ptr_op_t& expr) {
+ assert(kind > TERMINALS);
+ left_ = expr;
+ }
+
+ ptr_op_t& as_op_lval() {
+ assert(kind > TERMINALS);
+ return boost::get<ptr_op_t>(data);
+ }
+ const ptr_op_t& as_op() const {
+ return const_cast<op_t *>(this)->as_op_lval();
+ }
+
+ ptr_op_t& right() {
+ assert(kind > TERMINALS);
+ return as_op_lval();
+ }
+ const ptr_op_t& right() const {
+ assert(kind > TERMINALS);
+ return as_op();
+ }
+ void set_right(const ptr_op_t& expr) {
+ assert(kind > TERMINALS);
+ data = expr;
+ }
+
+private:
+ void acquire() const {
+ DEBUG("ledger.xpath.memory",
+ "Acquiring " << this << ", refc now " << refc + 1);
+ assert(refc >= 0);
+ refc++;
+ }
+ void release() const {
+ DEBUG("ledger.xpath.memory",
+ "Releasing " << this << ", refc now " << refc - 1);
+ assert(refc > 0);
+ if (--refc == 0)
+ checked_delete(this);
+ }
+
+ friend inline void intrusive_ptr_add_ref(op_t * op) {
+ op->acquire();
+ }
+ friend inline void intrusive_ptr_release(op_t * op) {
+ op->release();
+ }
+
+ static ptr_op_t new_node(kind_t _kind, ptr_op_t _left = NULL,
+ ptr_op_t _right = NULL);
+
+ ptr_op_t copy(ptr_op_t _left = NULL, ptr_op_t _right = NULL) const {
+ return new_node(kind, _left, _right);
+ }
+
+public:
+ ptr_op_t compile(scope_t& scope);
+ value_t calc(scope_t& scope);
+
+ struct print_context_t
+ {
+ scope_t& scope;
+ const bool relaxed;
+ const ptr_op_t& op_to_find;
+ unsigned long * start_pos;
+ unsigned long * end_pos;
+
+ // jww (2008-08-01): Is a scope needed here?
+ print_context_t(scope_t& _scope,
+ const bool _relaxed = false,
+ const ptr_op_t& _op_to_find = ptr_op_t(),
+ unsigned long * _start_pos = NULL,
+ unsigned long * _end_pos = NULL)
+ : scope(_scope), relaxed(_relaxed), op_to_find(_op_to_find),
+ start_pos(_start_pos), end_pos(_end_pos) {}
+ };
+
+ bool print(std::ostream& out, print_context_t& context) const;
+ void dump(std::ostream& out, const int depth) const;
+
+ void read(const char *& data);
+ void write(std::ostream& out) const;
+
+ static ptr_op_t wrap_value(const value_t& val);
+ static ptr_op_t wrap_functor(const function_t& fobj);
+};
+
+inline expr_t::ptr_op_t
+expr_t::op_t::new_node(kind_t _kind, ptr_op_t _left, ptr_op_t _right)
+{
+ ptr_op_t node(new op_t(_kind));
+ node->set_left(_left);
+ node->set_right(_right);
+ return node;
+}
+
+inline expr_t::ptr_op_t expr_t::op_t::wrap_value(const value_t& val) {
+ ptr_op_t temp(new op_t(op_t::VALUE));
+ temp->set_value(val);
+ return temp;
+}
+
+inline expr_t::ptr_op_t expr_t::op_t::wrap_functor(const function_t& fobj) {
+ ptr_op_t temp(new op_t(op_t::FUNCTION));
+ temp->set_function(fobj);
+ return temp;
+}
+
+#define MAKE_FUNCTOR(x) expr_t::op_t::wrap_functor(bind(&x, this, _1))
+#define WRAP_FUNCTOR(x) expr_t::op_t::wrap_functor(x)
+
+} // namespace ledger
+
+#endif // _OP_H
diff --git a/src/option.cc b/src/option.cc
new file mode 100644
index 00000000..b633de32
--- /dev/null
+++ b/src/option.cc
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2003-2008, 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 "option.h"
+
+namespace ledger {
+
+namespace {
+ typedef tuple<expr_t::ptr_op_t, bool> op_bool_tuple;
+
+ op_bool_tuple find_option(scope_t& scope, const string& name)
+ {
+ char buf[128];
+ std::strcpy(buf, "opt_");
+ char * p = &buf[4];
+ foreach (char ch, name) {
+ if (ch == '-')
+ *p++ = '_';
+ else
+ *p++ = ch;
+ }
+ *p = '\0';
+
+ expr_t::ptr_op_t op = scope.lookup(buf);
+ if (op)
+ return op_bool_tuple(op, false);
+
+ *p++ = '_';
+ *p = '\0';
+
+ return op_bool_tuple(scope.lookup(buf), true);
+ }
+
+ op_bool_tuple find_option(scope_t& scope, const char letter)
+ {
+ char buf[10];
+ std::strcpy(buf, "opt_");
+ buf[4] = letter;
+ buf[5] = '\0';
+
+ expr_t::ptr_op_t op = scope.lookup(buf);
+ if (op)
+ return op_bool_tuple(op, false);
+
+ buf[5] = '_';
+ buf[6] = '\0';
+
+ return op_bool_tuple(scope.lookup(buf), true);
+ }
+
+ void process_option(const function_t& opt, scope_t& scope,
+ const char * arg)
+ {
+ try {
+ call_scope_t args(scope);
+ if (arg)
+ args.push_back(string_value(arg));
+
+ opt(args);
+ }
+ catch (const std::exception& err) {
+#if 0
+ add_error_context("While parsing option '--" << opt->long_opt
+ << "'" << (opt->short_opt != '\0' ?
+ (string(" (-") + opt->short_opt + "):") :
+ ": "));
+#endif
+ throw err;
+ }
+ }
+}
+
+void process_option(const string& name, scope_t& scope,
+ const char * arg)
+{
+ op_bool_tuple opt(find_option(scope, name));
+ if (opt.get<0>())
+ process_option(opt.get<0>()->as_function(), scope, arg);
+}
+
+void process_environment(const char ** envp, const string& tag,
+ scope_t& scope)
+{
+ const char * tag_p = tag.c_str();
+ unsigned int tag_len = tag.length();
+
+ for (const char ** p = envp; *p; p++)
+ if (! tag_p || std::strncmp(*p, tag_p, tag_len) == 0) {
+ char buf[128];
+ char * r = buf;
+ const char * q;
+ for (q = *p + tag_len;
+ *q && *q != '=' && r - buf < 128;
+ q++)
+ if (*q == '_')
+ *r++ = '-';
+ else
+ *r++ = std::tolower(*q);
+ *r = '\0';
+
+ if (*q == '=') {
+ try {
+ process_option(string(buf), scope, q + 1);
+ }
+ catch (const std::exception& err) {
+ add_error_context("While parsing environment variable option '"
+ << *p << "':");
+ throw err;
+ }
+ }
+ }
+}
+
+void process_arguments(int, char ** argv, const bool anywhere,
+ scope_t& scope, std::list<string>& args)
+{
+ for (char ** i = argv; *i; i++) {
+ if ((*i)[0] != '-') {
+ if (anywhere) {
+ args.push_back(*i);
+ continue;
+ } else {
+ for (; *i; i++)
+ args.push_back(*i);
+ break;
+ }
+ }
+
+ // --long-option or -s
+ if ((*i)[1] == '-') {
+ if ((*i)[2] == '\0')
+ break;
+
+ char * name = *i + 2;
+ char * value = NULL;
+ if (char * p = std::strchr(name, '=')) {
+ *p++ = '\0';
+ value = p;
+ }
+
+ op_bool_tuple opt(find_option(scope, name));
+ if (! opt.get<0>())
+ throw_(option_error, "illegal option --" << name);
+
+ if (opt.get<1>() && value == NULL) {
+ value = *++i;
+ if (value == NULL)
+ throw_(option_error, "missing option argument for --" << name);
+ }
+ process_option(opt.get<0>()->as_function(), scope, value);
+ }
+ else if ((*i)[1] == '\0') {
+ throw_(option_error, "illegal option -");
+ }
+ else {
+ typedef tuple<expr_t::ptr_op_t, bool, char> op_bool_char_tuple;
+
+ std::list<op_bool_char_tuple> option_queue;
+
+ int x = 1;
+ for (char c = (*i)[x]; c != '\0'; x++, c = (*i)[x]) {
+ op_bool_tuple opt(find_option(scope, c));
+ if (! opt.get<0>())
+ throw_(option_error, "illegal option -" << c);
+
+ option_queue.push_back
+ (op_bool_char_tuple(opt.get<0>(), opt.get<1>(), c));
+ }
+
+ foreach (op_bool_char_tuple& o, option_queue) {
+ char * value = NULL;
+ if (o.get<1>()) {
+ value = *++i;
+ if (value == NULL)
+ throw_(option_error,
+ "missing option argument for -" << o.get<2>());
+ }
+ process_option(o.get<0>()->as_function(), scope, value);
+ }
+ }
+ }
+}
+
+} // namespace ledger
diff --git a/src/option.h b/src/option.h
new file mode 100644
index 00000000..23439b9d
--- /dev/null
+++ b/src/option.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2003-2008, 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 _OPTION_H
+#define _OPTION_H
+
+#include "scope.h"
+
+namespace ledger {
+
+void process_option(const string& name, scope_t& scope,
+ const char * arg = NULL);
+
+void process_environment(const char ** envp, const string& tag,
+ scope_t& scope);
+
+void process_arguments(int argc, char ** argv, const bool anywhere,
+ scope_t& scope, std::list<string>& args);
+
+DECLARE_EXCEPTION(option_error, std::runtime_error);
+
+} // namespace ledger
+
+#endif // _OPTION_H
diff --git a/src/output.cc b/src/output.cc
new file mode 100644
index 00000000..324a20e4
--- /dev/null
+++ b/src/output.cc
@@ -0,0 +1,310 @@
+/*
+ * Copyright (c) 2003-2008, 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 "output.h"
+
+namespace ledger {
+
+format_xacts::format_xacts(report_t& _report, const string& format)
+ : report(_report), last_entry(NULL), last_xact(NULL)
+{
+ TRACE_CTOR(format_xacts, "report&, const string&");
+
+ const char * f = format.c_str();
+
+ if (const char * p = std::strstr(f, "%/")) {
+ first_line_format.parse(string(f, 0, p - f));
+ next_lines_format.parse(string(p + 2));
+ } else {
+ first_line_format.parse(format);
+ next_lines_format.parse(format);
+ }
+}
+
+void format_xacts::operator()(xact_t& xact)
+{
+ std::ostream& out(*report.output_stream);
+
+ if (! xact.has_xdata() ||
+ ! xact.xdata().has_flags(XACT_EXT_DISPLAYED)) {
+ if (last_entry != xact.entry) {
+ first_line_format.format(out, xact);
+ last_entry = xact.entry;
+ }
+ else if (last_xact && last_xact->date() != xact.date()) {
+ first_line_format.format(out, xact);
+ }
+ else {
+ next_lines_format.format(out, xact);
+ }
+
+ xact.xdata().add_flags(XACT_EXT_DISPLAYED);
+ last_xact = &xact;
+ }
+}
+
+void format_entries::format_last_entry()
+{
+ bool first = true;
+ std::ostream& out(*report.output_stream);
+
+ foreach (xact_t * xact, last_entry->xacts) {
+ if (xact->has_xdata() &&
+ xact->xdata().has_flags(XACT_EXT_TO_DISPLAY)) {
+ if (first) {
+ first_line_format.format(out, *xact);
+ first = false;
+ } else {
+ next_lines_format.format(out, *xact);
+ }
+ xact->xdata().add_flags(XACT_EXT_DISPLAYED);
+ }
+ }
+}
+
+void format_entries::operator()(xact_t& xact)
+{
+ xact.xdata().add_flags(XACT_EXT_TO_DISPLAY);
+
+ if (last_entry && xact.entry != last_entry)
+ format_last_entry();
+
+ last_entry = xact.entry;
+}
+
+void print_entry(std::ostream& out, const entry_base_t& entry_base,
+ const string& prefix)
+{
+ string print_format;
+
+ if (dynamic_cast<const entry_t *>(&entry_base)) {
+ print_format = (prefix + "%D %X%C%P\n" +
+ prefix + " %-34A %12o\n%/" +
+ prefix + " %-34A %12o\n");
+ }
+ else if (const auto_entry_t * entry =
+ dynamic_cast<const auto_entry_t *>(&entry_base)) {
+ out << "= " << entry->predicate.predicate.text() << '\n';
+ print_format = prefix + " %-34A %12o\n";
+ }
+ else if (const period_entry_t * entry =
+ dynamic_cast<const period_entry_t *>(&entry_base)) {
+ out << "~ " << entry->period_string << '\n';
+ print_format = prefix + " %-34A %12o\n";
+ }
+ else {
+ assert(false);
+ }
+
+#if 0
+ format_entries formatter(out, print_format);
+ walk_xacts(const_cast<xacts_list&>(entry_base.xacts), formatter);
+ formatter.flush();
+
+ clear_xact_xdata cleaner;
+ walk_xacts(const_cast<xacts_list&>(entry_base.xacts), cleaner);
+#endif
+}
+
+void format_accounts::flush()
+{
+ std::ostream& out(*report.output_stream);
+
+#if 0
+ // jww (2008-08-02): I need access to the output formatter before this is
+ // going to work.
+ if (print_final_total) {
+ assert(out);
+ assert(account_has_xdata(*session.master));
+
+ account_xdata_t& xdata(account_xdata(*session.master));
+
+ if (! show_collapsed && xdata.total) {
+ out << "--------------------\n";
+ xdata.value = xdata.total;
+ handler->format.format(out, *session.master);
+ }
+ }
+#endif
+
+ out.flush();
+}
+
+void format_accounts::operator()(account_t& account)
+{
+ if (display_account(account)) {
+ if (! account.parent) {
+ account.xdata().add_flags(ACCOUNT_EXT_TO_DISPLAY);
+ } else {
+ format.format(*report.output_stream, account);
+ account.xdata().add_flags(ACCOUNT_EXT_DISPLAYED);
+ }
+ }
+}
+
+bool format_accounts::disp_subaccounts_p(account_t& account,
+ account_t *& to_show)
+{
+ bool display = false;
+ unsigned int counted = 0;
+ bool matches = disp_pred(account);
+ bool computed = false;
+ value_t acct_total;
+ value_t result;
+
+ to_show = NULL;
+
+ foreach (accounts_map::value_type pair, account.accounts) {
+ if (! disp_pred(*pair.second))
+ continue;
+
+ call_scope_t args(*pair.second);
+ result = report.get_total_expr(args);
+ if (! computed) {
+ call_scope_t args(account);
+ acct_total = report.get_total_expr(args);
+ computed = true;
+ }
+
+ if ((result != acct_total) || counted > 0) {
+ display = matches;
+ break;
+ }
+ to_show = pair.second;
+ counted++;
+ }
+
+ return display;
+}
+
+bool format_accounts::display_account(account_t& account)
+{
+ // Never display an account that has already been displayed.
+ if (account.has_xdata() &&
+ account.xdata().has_flags(ACCOUNT_EXT_DISPLAYED))
+ return false;
+
+ // At this point, one of two possibilities exists: the account is a
+ // leaf which matches the predicate restrictions; or it is a parent
+ // and two or more children must be subtotaled; or it is a parent
+ // and its child has been hidden by the predicate. So first,
+ // determine if it is a parent that must be displayed regardless of
+ // the predicate.
+
+ account_t * account_to_show = NULL;
+ if (disp_subaccounts_p(account, account_to_show))
+ return true;
+
+ return ! account_to_show && disp_pred(account);
+}
+
+format_equity::format_equity(report_t& _report,
+ const string& _format,
+ const string& display_predicate)
+ : format_accounts(_report, "", display_predicate)
+{
+ const char * f = _format.c_str();
+
+ if (const char * p = std::strstr(f, "%/")) {
+ first_line_format.parse(string(f, 0, p - f));
+ next_lines_format.parse(string(p + 2));
+ } else {
+ first_line_format.parse(_format);
+ next_lines_format.parse(_format);
+ }
+
+ entry_t header_entry;
+ header_entry.payee = "Opening Balances";
+ header_entry._date = current_date;
+ first_line_format.format(*report.output_stream, header_entry);
+}
+
+void format_equity::flush()
+{
+ account_t summary(NULL, "Equity:Opening Balances");
+
+ account_t::xdata_t& xdata(summary.xdata());
+ std::ostream& out(*report.output_stream);
+
+ xdata.value = total.negate();
+
+ if (total.type() >= value_t::BALANCE) {
+ const balance_t * bal;
+ if (total.is_type(value_t::BALANCE))
+ bal = &(total.as_balance());
+ else if (total.is_type(value_t::BALANCE_PAIR))
+ bal = &(total.as_balance_pair().quantity());
+ else
+ assert(false);
+
+ foreach (balance_t::amounts_map::value_type pair, bal->amounts) {
+ xdata.value = pair.second;
+ xdata.value.negate();
+ next_lines_format.format(out, summary);
+ }
+ } else {
+ next_lines_format.format(out, summary);
+ }
+ out.flush();
+}
+
+void format_equity::operator()(account_t& account)
+{
+ std::ostream& out(*report.output_stream);
+
+ if (display_account(account)) {
+ if (account.has_xdata()) {
+ value_t val = account.xdata().value;
+
+ if (val.type() >= value_t::BALANCE) {
+ const balance_t * bal;
+ if (val.is_type(value_t::BALANCE))
+ bal = &(val.as_balance());
+ else if (val.is_type(value_t::BALANCE_PAIR))
+ bal = &(val.as_balance_pair().quantity());
+ else
+ assert(false);
+
+ foreach (balance_t::amounts_map::value_type pair, bal->amounts) {
+ account.xdata().value = pair.second;
+ next_lines_format.format(out, account);
+ }
+ account.xdata().value = val;
+ } else {
+ next_lines_format.format(out, account);
+ }
+ total += val;
+ }
+ account.xdata().add_flags(ACCOUNT_EXT_DISPLAYED);
+ }
+}
+
+} // namespace ledger
diff --git a/src/output.h b/src/output.h
new file mode 100644
index 00000000..885b8bc9
--- /dev/null
+++ b/src/output.h
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2003-2008, 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 _OUTPUT_H
+#define _OUTPUT_H
+
+#include "report.h"
+#include "handler.h"
+#include "format.h"
+
+namespace ledger {
+
+class format_xacts : public item_handler<xact_t>
+{
+protected:
+ report_t& report;
+ format_t first_line_format;
+ format_t next_lines_format;
+ entry_t * last_entry;
+ xact_t * last_xact;
+
+public:
+ format_xacts(report_t& _report, const string& format);
+ virtual ~format_xacts() {
+ TRACE_DTOR(format_xacts);
+ }
+
+ virtual void flush() {
+ report.output_stream->flush();
+ }
+ virtual void operator()(xact_t& xact);
+};
+
+class format_entries : public format_xacts
+{
+ public:
+ format_entries(report_t& _report, const string& format)
+ : format_xacts(_report, format) {
+ TRACE_CTOR(format_entries, "report_t&, const string&");
+ }
+ virtual ~format_entries() {
+ TRACE_DTOR(format_entries);
+ }
+
+ virtual void format_last_entry();
+
+ virtual void flush() {
+ if (last_entry) {
+ format_last_entry();
+ last_entry = NULL;
+ }
+ format_xacts::flush();
+ }
+ virtual void operator()(xact_t& xact);
+
+private:
+ void print_entry(std::ostream& out,
+ const entry_base_t& entry,
+ const string& prefix = "");
+};
+
+class format_accounts : public item_handler<account_t>
+{
+protected:
+ report_t& report;
+
+ item_predicate<account_t> disp_pred;
+
+ bool disp_subaccounts_p(account_t& account, account_t *& to_show);
+ bool display_account(account_t& account);
+
+public:
+ format_t format;
+
+ format_accounts(report_t& _report,
+ const string& _format,
+ const string& display_predicate = "" /*,
+ const bool print_final_total = true */)
+ : report(_report), disp_pred(display_predicate), format(_format) {
+ TRACE_CTOR(format_accounts, "report&, const string&, const string&");
+ }
+ virtual ~format_accounts() {
+ TRACE_DTOR(format_accounts);
+ }
+
+ virtual void flush();
+
+ virtual void operator()(account_t& account);
+};
+
+class format_equity : public format_accounts
+{
+ format_t first_line_format;
+ format_t next_lines_format;
+
+ mutable value_t total;
+
+ public:
+ format_equity(report_t& _report,
+ const string& _format,
+ const string& display_predicate = "");
+ virtual ~format_equity() {
+ TRACE_DTOR(format_equity);
+ }
+
+ virtual void flush();
+ virtual void operator()(account_t& account);
+};
+
+} // namespace ledger
+
+#endif // _OUTPUT_H
diff --git a/src/parser.cc b/src/parser.cc
new file mode 100644
index 00000000..4795c8f7
--- /dev/null
+++ b/src/parser.cc
@@ -0,0 +1,441 @@
+/*
+ * Copyright (c) 2003-2008, 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 "parser.h"
+
+namespace ledger {
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_value_term(std::istream& in,
+ const flags_t tflags) const
+{
+ ptr_op_t node;
+
+ token_t& tok = next_token(in, tflags);
+
+ switch (tok.kind) {
+ case token_t::VALUE:
+ node = new op_t(op_t::VALUE);
+ node->set_value(tok.value);
+ break;
+
+ case token_t::MASK:
+ node = new op_t(op_t::MASK);
+ node->set_mask(tok.value.as_string());
+ break;
+
+ case token_t::IDENT: {
+#if 0
+#ifdef USE_BOOST_PYTHON
+ if (tok.value->as_string() == "lambda") // special
+ try {
+ char c, buf[4096];
+
+ std::strcpy(buf, "lambda ");
+ READ_INTO(in, &buf[7], 4000, c, true);
+
+ ptr_op_t eval = new op_t(op_t::O_EVAL);
+ ptr_op_t lambda = new op_t(op_t::FUNCTION);
+ lambda->functor = new python_functor_t(python_eval(buf));
+ eval->set_left(lambda);
+ ptr_op_t sym = new op_t(op_t::SYMBOL);
+ sym->name = new string("__ptr");
+ eval->set_right(sym);
+
+ node = eval;
+
+ goto done;
+ }
+ catch(const boost::python::error_already_set&) {
+ throw_(parse_error, "Error parsing lambda expression");
+ }
+#endif /* USE_BOOST_PYTHON */
+#endif
+
+ string ident = tok.value.as_string();
+
+ // An identifier followed by ( represents a function call
+ tok = next_token(in, tflags);
+
+ if (tok.kind == token_t::LPAREN) {
+ node = new op_t(op_t::IDENT);
+ node->set_ident(ident);
+
+ ptr_op_t call_node(new op_t(op_t::O_CALL));
+ call_node->set_left(node);
+ call_node->set_right(parse_value_expr(in, tflags | EXPR_PARSE_PARTIAL));
+
+ tok = next_token(in, tflags);
+ if (tok.kind != token_t::RPAREN)
+ tok.expected(')');
+
+ node = call_node;
+ } else {
+ if (std::isdigit(ident[0])) {
+ node = new op_t(op_t::INDEX);
+ node->set_index(lexical_cast<unsigned int>(ident.c_str()));
+ } else {
+ node = new op_t(op_t::IDENT);
+ node->set_ident(ident);
+ }
+ push_token(tok);
+ }
+ break;
+ }
+
+ case token_t::LPAREN:
+ node = parse_value_expr(in, tflags | EXPR_PARSE_PARTIAL);
+ if (! node)
+ throw_(parse_error, tok.symbol << " operator not followed by expression");
+
+ tok = next_token(in, tflags);
+ if (tok.kind != token_t::RPAREN)
+ tok.expected(')');
+ break;
+
+ default:
+ push_token(tok);
+ break;
+ }
+
+#if 0
+#ifdef USE_BOOST_PYTHON
+ done:
+#endif
+#endif
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_unary_expr(std::istream& in,
+ const flags_t tflags) const
+{
+ ptr_op_t node;
+
+ token_t& tok = next_token(in, tflags);
+
+ switch (tok.kind) {
+ case token_t::EXCLAM: {
+ ptr_op_t term(parse_value_term(in, tflags));
+ if (! term)
+ throw_(parse_error,
+ tok.symbol << " operator not followed by argument");
+
+ // A very quick optimization
+ if (term->kind == op_t::VALUE) {
+ term->as_value_lval().in_place_negate();
+ node = term;
+ } else {
+ node = new op_t(op_t::O_NOT);
+ node->set_left(term);
+ }
+ break;
+ }
+
+ case token_t::MINUS: {
+ ptr_op_t term(parse_value_term(in, tflags));
+ if (! term)
+ throw_(parse_error,
+ tok.symbol << " operator not followed by argument");
+
+ // A very quick optimization
+ if (term->kind == op_t::VALUE) {
+ term->as_value_lval().in_place_negate();
+ node = term;
+ } else {
+ node = new op_t(op_t::O_NEG);
+ node->set_left(term);
+ }
+ break;
+ }
+
+ default:
+ push_token(tok);
+ node = parse_value_term(in, tflags);
+ break;
+ }
+
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_mul_expr(std::istream& in,
+ const flags_t tflags) const
+{
+ ptr_op_t node(parse_unary_expr(in, tflags));
+
+ if (node) {
+ token_t& tok = next_token(in, tflags);
+
+ if (tok.kind == token_t::STAR || tok.kind == token_t::KW_DIV) {
+ ptr_op_t prev(node);
+ node = new op_t(tok.kind == token_t::STAR ?
+ op_t::O_MUL : op_t::O_DIV);
+ node->set_left(prev);
+ node->set_right(parse_mul_expr(in, tflags));
+ if (! node->right())
+ throw_(parse_error,
+ tok.symbol << " operator not followed by argument");
+
+ tok = next_token(in, tflags);
+ }
+ push_token(tok);
+ }
+
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_add_expr(std::istream& in,
+ const flags_t tflags) const
+{
+ ptr_op_t node(parse_mul_expr(in, tflags));
+
+ if (node) {
+ token_t& tok = next_token(in, tflags);
+
+ if (tok.kind == token_t::PLUS ||
+ tok.kind == token_t::MINUS) {
+ ptr_op_t prev(node);
+ node = new op_t(tok.kind == token_t::PLUS ?
+ op_t::O_ADD : op_t::O_SUB);
+ node->set_left(prev);
+ node->set_right(parse_add_expr(in, tflags));
+ if (! node->right())
+ throw_(parse_error,
+ tok.symbol << " operator not followed by argument");
+
+ tok = next_token(in, tflags);
+ }
+ push_token(tok);
+ }
+
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_logic_expr(std::istream& in,
+ const flags_t tflags) const
+{
+ ptr_op_t node(parse_add_expr(in, tflags));
+
+ if (node) {
+ op_t::kind_t kind = op_t::LAST;
+ flags_t _flags = tflags;
+ token_t& tok = next_token(in, tflags);
+
+ switch (tok.kind) {
+ case token_t::EQUAL:
+ if (tflags & EXPR_PARSE_NO_ASSIGN)
+ tok.rewind(in);
+ else
+ kind = op_t::O_EQ;
+ break;
+ case token_t::NEQUAL:
+ kind = op_t::O_NEQ;
+ break;
+ case token_t::MATCH:
+ kind = op_t::O_MATCH;
+ break;
+ case token_t::LESS:
+ kind = op_t::O_LT;
+ break;
+ case token_t::LESSEQ:
+ kind = op_t::O_LTE;
+ break;
+ case token_t::GREATER:
+ kind = op_t::O_GT;
+ break;
+ case token_t::GREATEREQ:
+ kind = op_t::O_GTE;
+ break;
+ default:
+ push_token(tok);
+ break;
+ }
+
+ if (kind != op_t::LAST) {
+ ptr_op_t prev(node);
+ node = new op_t(kind);
+ node->set_left(prev);
+ node->set_right(parse_add_expr(in, _flags));
+
+ if (! node->right())
+ throw_(parse_error,
+ tok.symbol << " operator not followed by argument");
+ }
+ }
+
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_and_expr(std::istream& in,
+ const flags_t tflags) const
+{
+ ptr_op_t node(parse_logic_expr(in, tflags));
+
+ if (node) {
+ token_t& tok = next_token(in, tflags);
+
+ if (tok.kind == token_t::KW_AND) {
+ ptr_op_t prev(node);
+ node = new op_t(op_t::O_AND);
+ node->set_left(prev);
+ node->set_right(parse_and_expr(in, tflags));
+ if (! node->right())
+ throw_(parse_error,
+ tok.symbol << " operator not followed by argument");
+ } else {
+ push_token(tok);
+ }
+ }
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_or_expr(std::istream& in,
+ const flags_t tflags) const
+{
+ ptr_op_t node(parse_and_expr(in, tflags));
+
+ if (node) {
+ token_t& tok = next_token(in, tflags);
+
+ if (tok.kind == token_t::KW_OR) {
+ ptr_op_t prev(node);
+ node = new op_t(op_t::O_OR);
+ node->set_left(prev);
+ node->set_right(parse_or_expr(in, tflags));
+ if (! node->right())
+ throw_(parse_error,
+ tok.symbol << " operator not followed by argument");
+ } else {
+ push_token(tok);
+ }
+ }
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_querycolon_expr(std::istream& in,
+ const flags_t tflags) const
+{
+ ptr_op_t node(parse_or_expr(in, tflags));
+
+ if (node) {
+ token_t& tok = next_token(in, tflags);
+
+ if (tok.kind == token_t::QUERY) {
+ ptr_op_t prev(node);
+ node = new op_t(op_t::O_AND);
+ node->set_left(prev);
+ node->set_right(parse_or_expr(in, tflags));
+ if (! node->right())
+ throw_(parse_error,
+ tok.symbol << " operator not followed by argument");
+
+ token_t& next_tok = next_token(in, tflags);
+ if (next_tok.kind != token_t::COLON)
+ next_tok.expected(':');
+
+ prev = node;
+ node = new op_t(op_t::O_OR);
+ node->set_left(prev);
+ node->set_right(parse_or_expr(in, tflags));
+ if (! node->right())
+ throw_(parse_error,
+ tok.symbol << " operator not followed by argument");
+ } else {
+ push_token(tok);
+ }
+ }
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_value_expr(std::istream& in,
+ const flags_t tflags) const
+{
+ ptr_op_t node(parse_querycolon_expr(in, tflags));
+
+ if (node) {
+ token_t& tok = next_token(in, tflags);
+
+ if (tok.kind == token_t::COMMA) {
+ ptr_op_t prev(node);
+ node = new op_t(op_t::O_COMMA);
+ node->set_left(prev);
+ node->set_right(parse_value_expr(in, tflags));
+ if (! node->right())
+ throw_(parse_error,
+ tok.symbol << " operator not followed by argument");
+ tok = next_token(in, tflags);
+ }
+
+ if (tok.kind != token_t::TOK_EOF) {
+ if (tflags & EXPR_PARSE_PARTIAL)
+ push_token(tok);
+ else
+ tok.unexpected();
+ }
+ }
+ else if (! (tflags & EXPR_PARSE_PARTIAL)) {
+ throw_(parse_error, "Failed to parse value expression");
+ }
+
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse(std::istream& in, const flags_t flags)
+{
+ try {
+ ptr_op_t top_node = parse_value_expr(in, flags);
+
+ if (use_lookahead) {
+ use_lookahead = false;
+ lookahead.rewind(in);
+ }
+ lookahead.clear();
+
+ return top_node;
+ }
+ catch (const std::exception& err) {
+ add_error_context("While parsing value expression:\n");
+#if 0
+ add_error_context(line_context(str, (long)in.tellg() - 1));
+#endif
+ throw err;
+ }
+}
+
+} // namespace ledger
diff --git a/src/parser.h b/src/parser.h
new file mode 100644
index 00000000..294d28ff
--- /dev/null
+++ b/src/parser.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2003-2008, 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 _PARSER_H
+#define _PARSER_H
+
+#include "token.h"
+#include "op.h"
+
+namespace ledger {
+
+class expr_t::parser_t : public noncopyable
+{
+#define EXPR_PARSE_NORMAL 0x00
+#define EXPR_PARSE_PARTIAL 0x01
+#define EXPR_PARSE_NO_MIGRATE 0x02
+#define EXPR_PARSE_NO_REDUCE 0x04
+#define EXPR_PARSE_NO_ASSIGN 0x08
+#define EXPR_PARSE_NO_DATES 0x10
+
+public:
+ typedef uint_least8_t flags_t;
+
+private:
+ mutable token_t lookahead;
+ mutable bool use_lookahead;
+
+ token_t& next_token(std::istream& in, flags_t tflags) const
+ {
+ if (use_lookahead)
+ use_lookahead = false;
+ else
+ lookahead.next(in, tflags);
+ return lookahead;
+ }
+
+ void push_token(const token_t& tok) const
+ {
+ assert(&tok == &lookahead);
+ use_lookahead = true;
+ }
+
+ void push_token() const
+ {
+ use_lookahead = true;
+ }
+
+ ptr_op_t parse_value_term(std::istream& in, const flags_t flags) const;
+ ptr_op_t parse_unary_expr(std::istream& in, const flags_t flags) const;
+ ptr_op_t parse_mul_expr(std::istream& in, const flags_t flags) const;
+ ptr_op_t parse_add_expr(std::istream& in, const flags_t flags) const;
+ ptr_op_t parse_logic_expr(std::istream& in, const flags_t flags) const;
+ ptr_op_t parse_and_expr(std::istream& in, const flags_t flags) const;
+ ptr_op_t parse_or_expr(std::istream& in, const flags_t flags) const;
+ ptr_op_t parse_querycolon_expr(std::istream& in, const flags_t flags) const;
+ ptr_op_t parse_value_expr(std::istream& in, const flags_t flags) const;
+
+public:
+ parser_t() : use_lookahead(false) {
+ TRACE_CTOR(parser_t, "");
+ }
+ ~parser_t() throw() {
+ TRACE_DTOR(parser_t);
+ }
+
+ ptr_op_t parse(std::istream& in, const flags_t flags = EXPR_PARSE_NORMAL);
+ ptr_op_t parse(string& str, const flags_t flags = EXPR_PARSE_NORMAL) {
+ std::istringstream stream(str);
+ return parse(stream, flags);
+ }
+};
+
+} // namespace ledger
+
+#endif // _PARSER_H
diff --git a/src/predicate.h b/src/predicate.h
new file mode 100644
index 00000000..624d3d65
--- /dev/null
+++ b/src/predicate.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2003-2008, 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 _PREDICATE_H
+#define _PREDICATE_H
+
+#include "expr.h"
+#include "scope.h"
+
+namespace ledger {
+
+template <typename T>
+class item_predicate
+{
+public:
+ expr_t predicate;
+
+ item_predicate() {
+ TRACE_CTOR(item_predicate, "");
+ }
+ item_predicate(const item_predicate& other) : predicate(other.predicate) {
+ TRACE_CTOR(item_predicate, "copy");
+ }
+ item_predicate(const expr_t& _predicate) : predicate(_predicate) {
+ TRACE_CTOR(item_predicate, "const expr_t&");
+ }
+ item_predicate(const string& _predicate) : predicate(expr_t(_predicate)) {
+ TRACE_CTOR(item_predicate, "const string&");
+ }
+ ~item_predicate() throw() {
+ TRACE_DTOR(item_predicate);
+ }
+
+ bool operator()(T& item) {
+ return ! predicate || predicate.calc(item).strip_annotations();
+ }
+};
+
+} // namespace ledger
+
+#endif // _PREDICATE_H
diff --git a/src/pushvar.h b/src/pushvar.h
new file mode 100644
index 00000000..a6ec0fab
--- /dev/null
+++ b/src/pushvar.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2003-2008, 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 pushvar.h
+ * @author John Wiegley
+ * @date Sun May 6 20:10:52 2007
+ *
+ * @brief Adds a facility to C++ for handling "scoped yet global".
+ */
+
+#ifndef _PUSHVAR_H
+#define _PUSHVAR_H
+
+template <typename T>
+class push_variable : public boost::noncopyable
+{
+ push_variable();
+
+public:
+ T& var;
+ T prev;
+ bool enabled;
+
+ explicit push_variable(T& _var)
+ : var(_var), prev(var), enabled(true) {
+ TRACE_CTOR(push_variable, "T&");
+ }
+ explicit push_variable(T& _var, const T& value)
+ : var(_var), prev(var), enabled(true) {
+ TRACE_CTOR(push_variable, "T&, constT&");
+ var = value;
+ }
+ ~push_variable() throw() {
+ TRACE_DTOR(push_variable);
+ if (enabled)
+ var = prev;
+ }
+
+ T& operator*() {
+ return var;
+ }
+ T * operator->() {
+ return &var;
+ }
+
+ void clear() {
+ enabled = false;
+ }
+};
+
+#endif // _PUSHVAR_H
diff --git a/src/qif.cc b/src/qif.cc
new file mode 100644
index 00000000..1c0f01b7
--- /dev/null
+++ b/src/qif.cc
@@ -0,0 +1,276 @@
+/*
+ * Copyright (c) 2003-2008, 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 "journal.h"
+#include "qif.h"
+#include "utils.h"
+
+namespace ledger {
+
+#define MAX_LINE 1024
+
+static char line[MAX_LINE + 1];
+static path pathname;
+static unsigned int src_idx;
+static unsigned int linenum;
+
+static inline char * get_line(std::istream& in) {
+ in.getline(line, MAX_LINE);
+ int len = std::strlen(line);
+ if (line[len - 1] == '\r')
+ line[len - 1] = '\0';
+ linenum++;
+ return line;
+}
+
+bool qif_parser_t::test(std::istream& in) const
+{
+ char magic[sizeof(unsigned int) + 1];
+ in.read(magic, sizeof(unsigned int));
+ magic[sizeof(unsigned int)] = '\0';
+ in.clear();
+ in.seekg(0, std::ios::beg);
+
+ return (std::strcmp(magic, "!Typ") == 0 ||
+ std::strcmp(magic, "\n!Ty") == 0 ||
+ std::strcmp(magic, "\r\n!T") == 0);
+}
+
+unsigned int qif_parser_t::parse(std::istream& in,
+ session_t& session,
+ journal_t& journal,
+ account_t * master,
+ const path * original_file)
+{
+ std::auto_ptr<entry_t> entry;
+ std::auto_ptr<amount_t> amount;
+
+ xact_t * xact;
+ unsigned int count = 0;
+ account_t * misc = NULL;
+ commodity_t * def_commodity = NULL;
+ bool saw_splits = false;
+ bool saw_category = false;
+ xact_t * total = NULL;
+
+ entry.reset(new entry_t);
+ xact = new xact_t(master);
+ entry->add_xact(xact);
+
+ pathname = journal.sources.back();
+ src_idx = journal.sources.size() - 1;
+ linenum = 1;
+
+ istream_pos_type beg_pos = 0;
+ unsigned long beg_line = 0;
+
+#define SET_BEG_POS_AND_LINE() \
+ if (! beg_line) { \
+ beg_pos = in.tellg(); \
+ beg_line = linenum; \
+ }
+
+ while (in.good() && ! in.eof()) {
+ char c;
+ in.get(c);
+ switch (c) {
+ case ' ':
+ case '\t':
+ if (peek_next_nonws(in) != '\n') {
+ get_line(in);
+ throw parse_error("Line begins with whitespace");
+ }
+ // fall through...
+
+ case '\n':
+ linenum++;
+ case '\r': // skip blank lines
+ break;
+
+ case '!':
+ get_line(in);
+
+ if (std::strcmp(line, "Type:Invst") == 0 ||
+ std::strcmp(line, "Account") == 0 ||
+ std::strcmp(line, "Type:Cat") == 0 ||
+ std::strcmp(line, "Type:Class") == 0 ||
+ std::strcmp(line, "Type:Memorized") == 0)
+ throw_(parse_error, "QIF files of type " << line << " are not supported.");
+ break;
+
+ case 'D':
+ SET_BEG_POS_AND_LINE();
+ get_line(in);
+ // jww (2008-08-01): Is this just a date?
+ entry->_date = parse_date(line);
+ break;
+
+ case 'T':
+ case '$': {
+ SET_BEG_POS_AND_LINE();
+ get_line(in);
+ xact->amount.parse(line);
+
+ unsigned char flags = xact->amount.commodity().flags();
+ unsigned char prec = xact->amount.commodity().precision();
+
+ if (! def_commodity) {
+ def_commodity = amount_t::current_pool->find_or_create("$");
+ assert(def_commodity);
+ }
+ xact->amount.set_commodity(*def_commodity);
+
+ def_commodity->add_flags(flags);
+ if (prec > def_commodity->precision())
+ def_commodity->set_precision(prec);
+
+ if (c == '$') {
+ saw_splits = true;
+ xact->amount.negate();
+ } else {
+ total = xact;
+ }
+ break;
+ }
+
+ case 'C':
+ SET_BEG_POS_AND_LINE();
+ c = in.peek();
+ if (c == '*' || c == 'X') {
+ in.get(c);
+ xact->state = xact_t::CLEARED;
+ }
+ break;
+
+ case 'N':
+ SET_BEG_POS_AND_LINE();
+ get_line(in);
+ entry->code = line;
+ break;
+
+ case 'P':
+ case 'M':
+ case 'L':
+ case 'S':
+ case 'E': {
+ SET_BEG_POS_AND_LINE();
+ get_line(in);
+
+ switch (c) {
+ case 'P':
+ entry->payee = line;
+ break;
+
+ case 'S':
+ xact = new xact_t(NULL);
+ entry->add_xact(xact);
+ // fall through...
+ case 'L': {
+ int len = std::strlen(line);
+ if (line[len - 1] == ']')
+ line[len - 1] = '\0';
+ xact->account = journal.find_account(line[0] == '[' ?
+ line + 1 : line);
+ if (c == 'L')
+ saw_category = true;
+ break;
+ }
+
+ case 'M':
+ case 'E':
+ xact->note = line;
+ break;
+ }
+ break;
+ }
+
+ case 'A':
+ SET_BEG_POS_AND_LINE();
+ // jww (2004-08-19): these are ignored right now
+ get_line(in);
+ break;
+
+ case '^': {
+ account_t * other;
+ if (xact->account == master) {
+ if (! misc)
+ misc = journal.find_account("Miscellaneous");
+ other = misc;
+ } else {
+ other = master;
+ }
+
+ if (total && saw_category) {
+ if (! saw_splits)
+ total->amount.negate(); // negate, to show correct flow
+ else
+ total->account = other;
+ }
+
+ if (! saw_splits) {
+ xact_t * nxact = new xact_t(other);
+ // The amount doesn't need to be set because the code below
+ // will balance this xact against the other.
+ entry->add_xact(nxact);
+ }
+
+ if (journal.add_entry(entry.get())) {
+ entry->src_idx = src_idx;
+ entry->beg_pos = beg_pos;
+ entry->beg_line = beg_line;
+ entry->end_pos = in.tellg();
+ entry->end_line = linenum;
+ entry.release();
+ count++;
+ }
+
+ // reset things for the next entry
+ entry.reset(new entry_t);
+ xact = new xact_t(master);
+ entry->add_xact(xact);
+
+ saw_splits = false;
+ saw_category = false;
+ total = NULL;
+ beg_line = 0;
+ break;
+ }
+
+ default:
+ get_line(in);
+ break;
+ }
+ }
+
+ return count;
+}
+
+} // namespace ledger
diff --git a/src/qif.h b/src/qif.h
new file mode 100644
index 00000000..018f85bf
--- /dev/null
+++ b/src/qif.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2003-2008, 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 _QIF_H
+#define _QIF_H
+
+#include "journal.h"
+
+namespace ledger {
+
+class qif_parser_t : public journal_t::parser_t
+{
+ public:
+ virtual bool test(std::istream& in) const;
+
+ virtual unsigned int parse(std::istream& in,
+ session_t& session,
+ journal_t& journal,
+ account_t * master = NULL,
+ const path * original_file = NULL);
+};
+
+} // namespace ledger
+
+#endif // _QIF_H
diff --git a/src/quotes.cc b/src/quotes.cc
new file mode 100644
index 00000000..ac6cb96e
--- /dev/null
+++ b/src/quotes.cc
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2003-2008, 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 "quotes.h"
+#include "utils.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)
+{
+ 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);
+
+ char buf[256];
+ buf[0] = '\0';
+
+ bool success = true;
+
+ if (FILE * fp = popen((string("getquote \"") +
+ commodity.symbol + "\"").c_str(), "r")) {
+ if (feof(fp) || ! fgets(buf, 255, fp))
+ success = false;
+ if (pclose(fp) != 0)
+ success = false;
+ } else {
+ success = false;
+ }
+
+ 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;
+ cache_dirty = true;
+
+ if (price && ! price_db.empty()) {
+#if defined(__GNUG__) && __GNUG__ < 3
+ ofstream database(price_db.c_str(), ios::out | ios::app);
+#else
+ ofstream database(price_db.c_str(), ios_base::out | ios_base::app);
+#endif
+ database << "P " << current_moment.to_string("%Y/%m/%d %H:%M:%S")
+ << " " << commodity.symbol << " " << price << endl;
+ }
+ } else {
+ throw_(std::runtime_error,
+ "Failed to download price for '" << commodity.symbol
+ << "' (command: \"getquote " << commodity.symbol << "\")");
+ }
+}
+#endif
+
+} // namespace ledger
diff --git a/src/quotes.h b/src/quotes.h
new file mode 100644
index 00000000..3ff70c68
--- /dev/null
+++ b/src/quotes.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2003-2008, 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 _QUOTES_H
+#define _QUOTES_H
+
+#include "amount.h"
+
+namespace ledger {
+
+#if 0
+class quotes_by_script : public noncopyable, public commodity_t::base_t::updater_t
+{
+ string price_db;
+ unsigned long pricing_leeway;
+ bool& cache_dirty;
+
+ quotes_by_script();
+
+public:
+ quotes_by_script(path _price_db,
+ unsigned long _pricing_leeway,
+ bool& _cache_dirty)
+ : price_db(_price_db), pricing_leeway(_pricing_leeway),
+ cache_dirty(_cache_dirty) {
+ TRACE_CTOR(quotes_by_script, "path, unsigned long, 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
+
+} // namespace ledger
+
+#endif // _QUOTES_H
diff --git a/src/reconcile.cc b/src/reconcile.cc
new file mode 100644
index 00000000..aca1732e
--- /dev/null
+++ b/src/reconcile.cc
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2003-2008, 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 "reconcile.h"
+
+namespace ledger {
+
+#define xact_next(x) reinterpret_cast<xact_t *>(x->xdata().ptr)
+#define xact_next_ptr(x) reinterpret_cast<xact_t **>(&x->xdata().ptr)
+
+static bool search_for_balance(amount_t& amount,
+ xact_t ** prev, xact_t * next)
+{
+ for (; next; next = xact_next(next)) {
+ xact_t * temp = *prev;
+ *prev = next;
+
+ amount -= next->amount;
+ if (! amount ||
+ search_for_balance(amount, xact_next_ptr(next), xact_next(next)))
+ return true;
+ amount += next->amount;
+
+ *prev = temp;
+ }
+ return false;
+}
+
+void reconcile_xacts::push_to_handler(xact_t * first)
+{
+ for (; first; first = xact_next(first))
+ item_handler<xact_t>::operator()(*first);
+
+ item_handler<xact_t>::flush();
+}
+
+void reconcile_xacts::flush()
+{
+ value_t cleared_balance;
+ value_t pending_balance;
+
+ xact_t * first = NULL;
+ xact_t ** last_ptr = &first;
+
+ foreach (xact_t * xact, xacts) {
+ if (! is_valid(cutoff) || xact->date() < cutoff) {
+ switch (xact->state) {
+ case xact_t::CLEARED:
+ cleared_balance += xact->amount;
+ break;
+ case xact_t::UNCLEARED:
+ case xact_t::PENDING:
+ pending_balance += xact->amount;
+ *last_ptr = xact;
+ last_ptr = xact_next_ptr(xact);
+ break;
+ }
+ }
+ }
+
+ if (cleared_balance.type() >= value_t::BALANCE)
+ throw std::runtime_error("Cannot reconcile accounts with multiple commodities");
+
+ cleared_balance.cast(value_t::AMOUNT);
+ balance.cast(value_t::AMOUNT);
+
+ commodity_t& cb_comm = cleared_balance.as_amount().commodity();
+ commodity_t& b_comm = balance.as_amount().commodity();
+
+ balance -= cleared_balance;
+ if (balance.type() >= value_t::BALANCE)
+ throw_(std::runtime_error,
+ "Reconcile balance is not of the same commodity ('"
+ << b_comm.symbol() << "' != '" << cb_comm.symbol() << "')");
+
+ // If the amount to reconcile is the same as the pending balance,
+ // then assume an exact match and return the results right away.
+ amount_t& to_reconcile(balance.as_amount_lval());
+ pending_balance.cast(value_t::AMOUNT);
+ if (to_reconcile == pending_balance.as_amount() ||
+ search_for_balance(to_reconcile, &first, first)) {
+ push_to_handler(first);
+ } else {
+ throw std::runtime_error("Could not reconcile account!");
+ }
+}
+
+} // namespace ledger
diff --git a/src/reconcile.h b/src/reconcile.h
new file mode 100644
index 00000000..2e133087
--- /dev/null
+++ b/src/reconcile.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2003-2008, 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 _RECONCILE_H
+#define _RECONCILE_H
+
+#include "value.h"
+#include "iterators.h"
+#include "filters.h"
+
+namespace ledger {
+
+class reconcile_xacts : public item_handler<xact_t>
+{
+ value_t balance;
+ date_t cutoff;
+ xacts_list xacts;
+
+ reconcile_xacts();
+
+public:
+ reconcile_xacts(xact_handler_ptr handler,
+ const value_t& _balance,
+ const date_t& _cutoff)
+ : item_handler<xact_t>(handler),
+ balance(_balance), cutoff(_cutoff) {
+ TRACE_CTOR(reconcile_xacts,
+ "xact_handler_ptr, const value_t&, const date_t&");
+ }
+ virtual ~reconcile_xacts() throw() {
+ TRACE_DTOR(reconcile_xacts);
+ }
+
+ void push_to_handler(xact_t * first);
+
+ virtual void flush();
+ virtual void operator()(xact_t& xact) {
+ xacts.push_back(&xact);
+ }
+};
+
+} // namespace ledger
+
+#endif // _RECONCILE_H
diff --git a/src/report.cc b/src/report.cc
new file mode 100644
index 00000000..92f20d4f
--- /dev/null
+++ b/src/report.cc
@@ -0,0 +1,432 @@
+/*
+ * Copyright (c) 2003-2008, 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 "report.h"
+#include "reconcile.h"
+
+namespace ledger {
+
+#if 0
+void report_t::process_options(const std::string& command,
+ strings_list::iterator arg,
+ strings_list::iterator args_end)
+{
+ // Configure some other options depending on report type
+
+ if (command == "p" || command == "e" || command == "w") {
+ show_related =
+ show_all_related = true;
+ }
+ else if (command == "E") {
+ show_subtotal = true;
+ }
+ else if (show_related) {
+ if (command == "r") {
+ show_inverted = true;
+ } else {
+ show_subtotal = true;
+ show_all_related = true;
+ }
+ }
+
+ if (command != "b" && command != "r")
+ amount_t::keep_base = true;
+
+ // Process remaining command-line arguments
+
+ if (command != "e") {
+ // Treat the remaining command-line arguments as regular
+ // expressions, used for refining report results.
+
+ std::list<std::string>::iterator i = arg;
+ for (; i != args_end; i++)
+ if (*i == "--")
+ break;
+
+ if (i != arg)
+ regexps_to_predicate(command, arg, i, true,
+ (command == "b" && ! show_subtotal &&
+ display_predicate.empty()));
+ if (i != args_end && ++i != args_end)
+ regexps_to_predicate(command, i, args_end);
+ }
+
+ // Setup the default value for the display predicate
+
+ if (display_predicate.empty()) {
+ if (command == "b") {
+ if (! show_empty)
+ display_predicate = "T";
+ if (! show_subtotal) {
+ if (! display_predicate.empty())
+ display_predicate += "&";
+ display_predicate += "l<=1";
+ }
+ }
+ else if (command == "E") {
+ display_predicate = "t";
+ }
+ else if (command == "r" && ! show_empty) {
+ display_predicate = "a";
+ }
+ }
+
+ DEBUG_PRINT("ledger.config.predicates", "Predicate: " << predicate);
+ DEBUG_PRINT("ledger.config.predicates", "Display P: " << display_predicate);
+
+ // Setup the values of %t and %T, used in format strings
+
+ if (! amount_expr.empty())
+ ledger::amount_expr = amount_expr;
+ if (! total_expr.empty())
+ ledger::total_expr = total_expr;
+
+ // Now setup the various formatting strings
+
+ if (! date_output_format.empty())
+ date_t::output_format = date_output_format;
+
+ amount_t::keep_price = keep_price;
+ amount_t::keep_date = keep_date;
+ amount_t::keep_tag = keep_tag;
+
+ if (! report_period.empty() && ! sort_all)
+ entry_sort = true;
+}
+#endif
+
+xact_handler_ptr
+report_t::chain_xact_handlers(xact_handler_ptr base_handler,
+ const bool handle_individual_xacts)
+{
+ bool remember_components = false;
+
+ xact_handler_ptr handler(base_handler);
+
+ // format_xacts write each xact received to the
+ // output stream.
+ if (handle_individual_xacts) {
+ // truncate_entries cuts off a certain number of _entries_ from
+ // being displayed. It does not affect calculation.
+ if (head_entries || tail_entries)
+ handler.reset(new truncate_entries(handler, head_entries, tail_entries));
+
+ // filter_xacts will only pass through xacts
+ // matching the `display_predicate'.
+ if (! display_predicate.empty())
+ handler.reset(new filter_xacts(handler, display_predicate));
+
+ // calc_xacts computes the running total. When this
+ // appears will determine, for example, whether filtered
+ // xacts are included or excluded from the running total.
+ handler.reset(new calc_xacts(handler));
+
+ // component_xacts looks for reported xact that
+ // match the given `descend_expr', and then reports the
+ // xacts which made up the total for that reported
+ // xact.
+ if (! descend_expr.empty()) {
+ std::list<std::string> descend_exprs;
+
+ std::string::size_type beg = 0;
+ for (std::string::size_type pos = descend_expr.find(';');
+ pos != std::string::npos;
+ beg = pos + 1, pos = descend_expr.find(';', beg))
+ descend_exprs.push_back(std::string(descend_expr, beg, pos - beg));
+ descend_exprs.push_back(std::string(descend_expr, beg));
+
+ for (std::list<std::string>::reverse_iterator i =
+ descend_exprs.rbegin();
+ i != descend_exprs.rend();
+ i++)
+ handler.reset(new component_xacts(handler, *i));
+
+ remember_components = true;
+ }
+
+ // reconcile_xacts will pass through only those
+ // xacts which can be reconciled to a given balance
+ // (calculated against the xacts which it receives).
+ if (! reconcile_balance.empty()) {
+ date_t cutoff = current_date;
+ if (! reconcile_date.empty())
+ cutoff = parse_date(reconcile_date);
+ handler.reset(new reconcile_xacts
+ (handler, value_t(reconcile_balance), cutoff));
+ }
+
+ // filter_xacts will only pass through xacts
+ // matching the `secondary_predicate'.
+ if (! secondary_predicate.empty())
+ handler.reset(new filter_xacts(handler, secondary_predicate));
+
+ // sort_xacts will sort all the xacts it sees, based
+ // on the `sort_order' value expression.
+ if (! sort_string.empty()) {
+ if (entry_sort)
+ handler.reset(new sort_entries(handler, sort_string));
+ else
+ handler.reset(new sort_xacts(handler, sort_string));
+ }
+
+ // changed_value_xacts adds virtual xacts to the
+ // list to account for changes in market value of commodities,
+ // which otherwise would affect the running total unpredictably.
+ if (show_revalued)
+ handler.reset(new changed_value_xacts(handler, show_revalued_only));
+
+ // collapse_xacts causes entries with multiple xacts
+ // to appear as entries with a subtotaled xact for each
+ // commodity used.
+ if (show_collapsed)
+ handler.reset(new collapse_xacts(handler));
+
+ // subtotal_xacts combines all the xacts it receives
+ // into one subtotal entry, which has one xact for each
+ // commodity in each account.
+ //
+ // period_xacts is like subtotal_xacts, but it
+ // subtotals according to time periods rather than totalling
+ // everything.
+ //
+ // dow_xacts is like period_xacts, except that it
+ // reports all the xacts that fall on each subsequent day
+ // of the week.
+ if (show_subtotal)
+ handler.reset(new subtotal_xacts(handler, remember_components));
+
+ if (days_of_the_week)
+ handler.reset(new dow_xacts(handler, remember_components));
+ else if (by_payee)
+ handler.reset(new by_payee_xacts(handler, remember_components));
+
+ // interval_xacts groups xacts together based on a
+ // time period, such as weekly or monthly.
+ if (! report_period.empty()) {
+ handler.reset(new interval_xacts(handler, report_period,
+ remember_components));
+ handler.reset(new sort_xacts(handler, "d"));
+ }
+ }
+
+ // invert_xacts inverts the value of the xacts it
+ // receives.
+ if (show_inverted)
+ handler.reset(new invert_xacts(handler));
+
+ // related_xacts will pass along all xacts related
+ // to the xact received. If `show_all_related' is true,
+ // then all the entry's xacts are passed; meaning that if
+ // one xact of an entry is to be printed, all the
+ // xact for that entry will be printed.
+ if (show_related)
+ handler.reset(new related_xacts(handler, show_all_related));
+
+ // This filter_xacts will only pass through xacts
+ // matching the `predicate'.
+ if (! predicate.empty()) {
+ DEBUG("report.predicate",
+ "Report predicate expression = " << predicate);
+ handler.reset(new filter_xacts(handler, predicate));
+ }
+
+#if 0
+ // budget_xacts takes a set of xacts from a data
+ // file and uses them to generate "budget xacts" which
+ // balance against the reported xacts.
+ //
+ // forecast_xacts is a lot like budget_xacts, except
+ // that it adds entries only for the future, and does not balance
+ // them against anything but the future balance.
+
+ if (budget_flags) {
+ budget_xacts * budget_handler
+ = new budget_xacts(handler, budget_flags);
+ budget_handler->add_period_entries(journal->period_entries);
+ handler.reset(budget_handler);
+
+ // Apply this before the budget handler, so that only matching
+ // xacts are calculated toward the budget. The use of
+ // filter_xacts above will further clean the results so
+ // that no automated xacts that don't match the filter get
+ // reported.
+ if (! predicate.empty())
+ handler.reset(new filter_xacts(handler, predicate));
+ }
+ else if (! forecast_limit.empty()) {
+ forecast_xacts * forecast_handler
+ = new forecast_xacts(handler, forecast_limit);
+ forecast_handler->add_period_entries(journal->period_entries);
+ handler.reset(forecast_handler);
+
+ // See above, under budget_xacts.
+ if (! predicate.empty())
+ handler.reset(new filter_xacts(handler, predicate));
+ }
+#endif
+
+ if (comm_as_payee)
+ handler.reset(new set_comm_as_payee(handler));
+ else if (code_as_payee)
+ handler.reset(new set_code_as_payee(handler));
+
+ return handler;
+}
+
+void report_t::xacts_report(xact_handler_ptr handler)
+{
+ session_xacts_iterator walker(session);
+ pass_down_xacts(chain_xact_handlers(handler), walker);
+ handler->flush();
+
+ if (DO_VERIFY())
+ session.clean_xacts();
+}
+
+void report_t::entry_report(xact_handler_ptr handler, entry_t& entry)
+{
+ entry_xacts_iterator walker(entry);
+ pass_down_xacts(chain_xact_handlers(handler), walker);
+ handler->flush();
+
+ if (DO_VERIFY())
+ session.clean_xacts(entry);
+}
+
+void report_t::sum_all_accounts()
+{
+ session_xacts_iterator walker(session);
+ pass_down_xacts
+ (chain_xact_handlers(xact_handler_ptr(new set_account_value), false),
+ walker);
+ // no flush() needed with set_account_value
+ session.master->calculate_sums();
+}
+
+void report_t::accounts_report(acct_handler_ptr handler)
+{
+ sum_all_accounts();
+
+ if (sort_string.empty()) {
+ basic_accounts_iterator walker(*session.master);
+ pass_down_accounts(handler, walker);
+ } else {
+ sorted_accounts_iterator walker(*session.master, sort_string);
+ pass_down_accounts(handler, walker);
+ }
+ handler->flush();
+
+ if (DO_VERIFY()) {
+ session.clean_xacts();
+ session.clean_accounts();
+ }
+}
+
+void report_t::commodities_report(const string& format)
+{
+}
+
+value_t report_t::get_amount_expr(call_scope_t& scope)
+{
+ return amount_expr.calc(scope);
+}
+
+value_t report_t::get_total_expr(call_scope_t& scope)
+{
+ return total_expr.calc(scope);
+}
+
+expr_t::ptr_op_t report_t::lookup(const string& name)
+{
+ const char * p = name.c_str();
+ switch (*p) {
+ case 'f':
+ if (std::strncmp(p, "fmt_", 4) == 0) {
+ p = p + 4;
+ switch (*p) {
+ case 't':
+ return MAKE_FUNCTOR(report_t::get_amount_expr);
+ case 'T':
+ return MAKE_FUNCTOR(report_t::get_total_expr);
+ }
+ }
+ break;
+
+ case 'o':
+ if (std::strncmp(p, "opt_", 4) == 0) {
+ p = p + 4;
+ switch (*p) {
+ case 'a':
+ if (std::strcmp(p, "amount_") == 0)
+ return MAKE_FUNCTOR(report_t::option_amount_);
+ break;
+
+ case 'f':
+ if (std::strcmp(p, "F_") == 0 ||
+ std::strcmp(p, "format_") == 0)
+ return MAKE_FUNCTOR(report_t::option_format_);
+ break;
+
+ case 'j':
+ if (! (*p + 1))
+ return MAKE_FUNCTOR(report_t::option_amount_data);
+ break;
+
+ case 'J':
+ if (! (*p + 1))
+ return MAKE_FUNCTOR(report_t::option_total_data);
+ break;
+
+ case 'l':
+ if (std::strcmp(p, "l_") || std::strcmp(p, "limit_"))
+ return MAKE_FUNCTOR(report_t::option_limit_);
+ break;
+
+ case 't':
+ if (std::strcmp(p, "t_"))
+ return MAKE_FUNCTOR(report_t::option_amount_);
+ else if (std::strcmp(p, "total_") == 0)
+ return MAKE_FUNCTOR(report_t::option_total_);
+ break;
+
+ case 'T':
+ if (std::strcmp(p, "T_"))
+ return MAKE_FUNCTOR(report_t::option_total_);
+ break;
+ }
+ }
+ break;
+ }
+
+ return session.lookup(name);
+}
+
+} // namespace ledger
diff --git a/src/report.h b/src/report.h
new file mode 100644
index 00000000..98b60ce1
--- /dev/null
+++ b/src/report.h
@@ -0,0 +1,757 @@
+/*
+ * Copyright (c) 2003-2008, 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 _REPORT_H
+#define _REPORT_H
+
+#include "session.h"
+#include "handler.h"
+
+namespace ledger {
+
+// These are the elements of any report:
+//
+// 1. Formatting string used for outputting the underlying ReportedType.
+//
+// 2. Handler object for the ReportedType. This is constructed using #1, or
+// else #1 is ignored completely. This handler object is also constructed
+// with the output stream that will be used during formatting.
+//
+// --- The details of #1 and #2 together represent the ItemHandler.
+//
+// 3. Mode of the report. Currently there are four modes:
+//
+// a. Transaction or commodity iteration. In this mode, all the journal's
+// entries, the transactions of a specific entry, or all the journal's
+// commodities are walked. In the first two cases, it's the underlying
+// transactions which are passed to #2; in the second case, each
+// commodity is passed to #2.
+//
+// b. Account iteration. This employs step 'a', but add a prologue and
+// epilogue to it. In the prologue it "sums" all account totals and
+// subtotals; in the epilogue it calls yet another handler whose job is
+// reporting (the handler used in 'a' is only for calculation).
+//
+// There is one variation on 'b' in which a "totals" line is also
+// displayed.
+//
+// c. Write journal. In this mode, a single function is called that output
+// the journal object as a textual file. #2 is used to print out each
+// transaction in the journal.
+//
+// d. Dump binary file. This is just like 'c', except that it dumps out a
+// binary file and #2 is completely ignored.
+//
+// 4. For 'a' and 'b' in #3, there is a different iteration function called,
+// depending on whether we're iterating:
+//
+// a. The transactions of an entry: walk_transactions.
+// b. The entries of a journal: walk_entries.
+// c. The commodities of a journal: walk_commodities.
+//
+// 5. Finally, for the 'a' and 'b' reporting modes, there is a variant which
+// says that the formatter should be "flushed" after the entities are
+// iterated. This does not happen for the commodities iteration, however.
+
+class report_t : public noncopyable, public scope_t
+{
+ report_t();
+
+public:
+ optional<path> output_file;
+ std::ostream * output_stream;
+
+ string format_string;
+ string date_output_format;
+ string predicate;
+ string secondary_predicate;
+ string display_predicate;
+ string report_period;
+ string report_period_sort;
+ string sort_string;
+ string descend_expr;
+ string forecast_limit;
+ string reconcile_balance;
+ string reconcile_date;
+
+ expr_t amount_expr;
+ expr_t total_expr;
+
+ unsigned long budget_flags;
+
+ int head_entries;
+ int tail_entries;
+
+ bool show_collapsed;
+ bool show_subtotal;
+ bool show_totals;
+ bool show_related;
+ bool show_all_related;
+ bool show_inverted;
+ bool show_empty;
+ bool days_of_the_week;
+ bool by_payee;
+ bool comm_as_payee;
+ bool code_as_payee;
+ bool show_revalued;
+ bool show_revalued_only;
+ bool keep_price;
+ bool keep_date;
+ bool keep_tag;
+ bool entry_sort;
+ bool sort_all;
+
+ string account;
+ optional<path> pager;
+
+ bool raw_mode;
+
+ session_t& session;
+
+ explicit report_t(session_t& _session)
+ : amount_expr("amount"),
+ total_expr("total"),
+
+ head_entries(0),
+ tail_entries(0),
+
+ show_collapsed(false),
+ show_subtotal(false),
+ show_totals(false),
+ show_related(false),
+ show_all_related(false),
+ show_inverted(false),
+ show_empty(false),
+ days_of_the_week(false),
+ by_payee(false),
+ comm_as_payee(false),
+ code_as_payee(false),
+ show_revalued(false),
+ show_revalued_only(false),
+ keep_price(false),
+ keep_date(false),
+ keep_tag(false),
+ entry_sort(false),
+ sort_all(false),
+
+ raw_mode(false),
+
+ session(_session)
+ {
+ TRACE_CTOR(report_t, "session_t&");
+ }
+
+ virtual ~report_t() {
+ TRACE_DTOR(report_t);
+ }
+
+ //
+ // Actual report generation; this is why we're here...
+ //
+
+ void xacts_report(xact_handler_ptr handler);
+ void entry_report(xact_handler_ptr handler, entry_t& entry);
+ void sum_all_accounts();
+ void accounts_report(acct_handler_ptr handler);
+ void commodities_report(const string& format);
+
+ xact_handler_ptr
+ chain_xact_handlers(xact_handler_ptr handler,
+ const bool handle_individual_transactions = true);
+
+#if 0
+ //////////////////////////////////////////////////////////////////////
+ //
+ // Basic options
+
+ value_t option_full_help(call_scope_t& args) { // H
+ option_full_help(std::cout);
+ throw 0;
+ }
+
+ value_t option_help(call_scope_t& args) { // h
+ option_help(std::cout);
+ throw 0;
+ }
+
+ value_t option_help_calc(call_scope_t& args) {
+ option_calc_help(std::cout);
+ throw 0;
+ }
+
+ value_t option_help_disp(call_scope_t& args) {
+ option_disp_help(std::cout);
+ throw 0;
+ }
+
+ value_t option_help_comm(call_scope_t& args) {
+ option_comm_help(std::cout);
+ throw 0;
+ }
+
+ value_t option_version(call_scope_t& args) { // v
+ show_version(std::cout);
+ throw 0;
+ }
+
+ value_t option_init_file(call_scope_t& args) { // i:
+ std::string path = resolve_path(optarg);
+ if (access(path.c_str(), R_OK) != -1)
+ config->init_file = path;
+ else
+ throw_(std::invalid_argument,
+ "The init file '" << path << "' does not exist or is not readable");
+ }
+
+ value_t option_file(call_scope_t& args) { // f:
+ if (std::string(optarg) == "-") {
+ config->data_file = optarg;
+ } else {
+ std::string path = resolve_path(optarg);
+ if (access(path.c_str(), R_OK) != -1)
+ config->data_file = path;
+ else
+ throw_(std::invalid_argument,
+ "The ledger file '" << path << "' does not exist or is not readable");
+ }
+ }
+
+ value_t option_cache(call_scope_t& args) { // :
+ config->cache_file = resolve_path(optarg);
+ }
+
+ value_t option_no_cache(call_scope_t& args) {
+ config->cache_file = "<none>";
+ }
+
+ value_t option_output(call_scope_t& args) { // o:
+ if (std::string(optarg) != "-") {
+ std::string path = resolve_path(optarg);
+ report->output_file = path;
+ }
+ }
+
+ value_t option_account(call_scope_t& args) { // a:
+ config->account = optarg;
+ }
+
+ value_t option_debug(call_scope_t& args) { // :
+ config->debug_mode = true;
+ ::setenv("DEBUG_CLASS", optarg, 1);
+ }
+
+ value_t option_verbose(call_scope_t& args) {
+ config->verbose_mode = true;
+ }
+
+ value_t option_trace(call_scope_t& args) {
+ config->trace_mode = true;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ //
+ // Report filtering
+
+ value_t option_effective(call_scope_t& args) {
+ xact_t::use_effective_date = true;
+ }
+
+ value_t option_begin(call_scope_t& args) { // b:
+ char buf[128];
+ interval_t interval(optarg);
+ if (! interval.begin)
+ throw_(std::invalid_argument,
+ "Could not determine beginning of period '" << optarg << "'");
+
+ if (! report->predicate.empty())
+ report->predicate += "&";
+ report->predicate += "d>=[";
+ report->predicate += interval.begin.to_string();
+ report->predicate += "]";
+ }
+
+ value_t option_end(call_scope_t& args) { // e:
+ char buf[128];
+ interval_t interval(optarg);
+ if (! interval.begin)
+ throw_(std::invalid_argument,
+ "Could not determine end of period '" << optarg << "'");
+
+ if (! report->predicate.empty())
+ report->predicate += "&";
+ report->predicate += "d<[";
+ report->predicate += interval.begin.to_string();
+ report->predicate += "]";
+
+ terminus = interval.begin;
+ }
+
+ value_t option_current(call_scope_t& args) { // c
+ if (! report->predicate.empty())
+ report->predicate += "&";
+ report->predicate += "d<=m";
+ }
+
+ value_t option_cleared(call_scope_t& args) { // C
+ if (! report->predicate.empty())
+ report->predicate += "&";
+ report->predicate += "X";
+ }
+
+ value_t option_uncleared(call_scope_t& args) { // U
+ if (! report->predicate.empty())
+ report->predicate += "&";
+ report->predicate += "!X";
+ }
+
+ value_t option_real(call_scope_t& args) { // R
+ if (! report->predicate.empty())
+ report->predicate += "&";
+ report->predicate += "R";
+ }
+
+ value_t option_actual(call_scope_t& args) { // L
+ if (! report->predicate.empty())
+ report->predicate += "&";
+ report->predicate += "L";
+ }
+
+ value_t option_lots(call_scope_t& args) {
+ report->keep_price =
+ report->keep_date =
+ report->keep_tag = true;
+ }
+
+ value_t option_lot_prices(call_scope_t& args) {
+ report->keep_price = true;
+ }
+
+ value_t option_lot_dates(call_scope_t& args) {
+ report->keep_date = true;
+ }
+
+ value_t option_lot_tags(call_scope_t& args) {
+ report->keep_tag = true;
+ }
+#endif
+
+ //////////////////////////////////////////////////////////////////////
+ //
+ // Output customization
+
+ value_t option_format_(call_scope_t& args) { // F:
+ format_string = args[0].as_string();
+ return true;
+ }
+
+#if 0
+ value_t option_date_format(call_scope_t& args) { // y:
+ report->date_output_format = optarg;
+ }
+
+ value_t option_input_date_format(call_scope_t& args) { // :
+ config->date_input_format = optarg;
+ }
+
+ value_t option_balance_format(call_scope_t& args) { // :
+ config->balance_format = optarg;
+ }
+
+ value_t option_register_format(call_scope_t& args) { // :
+ config->register_format = optarg;
+ }
+
+ value_t option_wide_register_format(call_scope_t& args) { // :
+ config->wide_register_format = optarg;
+ }
+
+ value_t option_plot_amount_format(call_scope_t& args) { // :
+ config->plot_amount_format = optarg;
+ }
+
+ value_t option_plot_total_format(call_scope_t& args) { // :
+ config->plot_total_format = optarg;
+ }
+
+ value_t option_print_format(call_scope_t& args) { // :
+ config->print_format = optarg;
+ }
+
+ value_t option_write_hdr_format(call_scope_t& args) { // :
+ config->write_hdr_format = optarg;
+ }
+
+ value_t option_write_xact_format(call_scope_t& args) { // :
+ config->write_xact_format = optarg;
+ }
+
+ value_t option_equity_format(call_scope_t& args) { // :
+ config->equity_format = optarg;
+ }
+
+ value_t option_prices_format(call_scope_t& args) { // :
+ config->prices_format = optarg;
+ }
+
+ value_t option_wide(call_scope_t& args) { // w
+ config->register_format = config->wide_register_format;
+ }
+
+ value_t option_head(call_scope_t& args) { // :
+ report->head_entries = std::atoi(optarg);
+ }
+
+ value_t option_tail(call_scope_t& args) { // :
+ report->tail_entries = std::atoi(optarg);
+ }
+
+ value_t option_pager(call_scope_t& args) { // :
+ config->pager = optarg;
+ }
+
+ value_t option_truncate(call_scope_t& args) { // :
+ std::string style(optarg);
+ if (style == "leading")
+ format_t::elision_style = format_t::TRUNCATE_LEADING;
+ else if (style == "middle")
+ format_t::elision_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;
+ }
+
+ value_t option_abbrev_len(call_scope_t& args) { // :
+ format_t::abbrev_length = std::atoi(optarg);
+ }
+
+ value_t option_empty(call_scope_t& args) { // E
+ report->show_empty = true;
+ }
+
+ value_t option_collapse(call_scope_t& args) { // n
+ report->show_collapsed = true;
+ }
+
+ value_t option_subtotal(call_scope_t& args) { // s
+ report->show_subtotal = true;
+ }
+
+ value_t option_totals(call_scope_t& args) {
+ report->show_totals = true;
+ }
+
+ value_t option_sort(call_scope_t& args) { // S:
+ report->sort_string = optarg;
+ }
+
+ value_t option_sort_entries(call_scope_t& args) {
+ report->sort_string = optarg;
+ report->entry_sort = true;
+ }
+
+ value_t option_sort_all(call_scope_t& args) {
+ report->sort_string = optarg;
+ report->entry_sort = false;
+ report->sort_all = true;
+ }
+
+ value_t option_period_sort(call_scope_t& args) { // :
+ report->sort_string = optarg;
+ report->entry_sort = true;
+ }
+
+ value_t option_related(call_scope_t& args) { // r
+ report->show_related = true;
+ }
+
+ value_t option_descend(call_scope_t& args) {
+ std::string arg(optarg);
+ std::string::size_type beg = 0;
+ report->descend_expr = "";
+ for (std::string::size_type pos = arg.find(';');
+ pos != std::string::npos;
+ beg = pos + 1, pos = arg.find(';', beg))
+ report->descend_expr += (std::string("t=={") +
+ std::string(arg, beg, pos - beg) + "};");
+ report->descend_expr += (std::string("t=={") +
+ std::string(arg, beg) + "}");
+ }
+
+ value_t option_descend_if(call_scope_t& args) {
+ report->descend_expr = optarg;
+ }
+
+ value_t option_period(call_scope_t& args) { // p:
+ if (report->report_period.empty()) {
+ report->report_period = optarg;
+ } else {
+ report->report_period += " ";
+ report->report_period += optarg;
+ }
+
+ // If the period gives a beginning and/or ending date, make sure to
+ // modify the calculation predicate (via the --begin and --end
+ // options) to take this into account.
+
+ interval_t interval(report->report_period);
+
+ if (interval.begin) {
+ if (! report->predicate.empty())
+ report->predicate += "&";
+ report->predicate += "d>=[";
+ report->predicate += interval.begin.to_string();
+ report->predicate += "]";
+ }
+
+ if (interval.end) {
+ if (! report->predicate.empty())
+ report->predicate += "&";
+ report->predicate += "d<[";
+ report->predicate += interval.end.to_string();
+ report->predicate += "]";
+
+ terminus = interval.end;
+ }
+ }
+
+ value_t option_daily(call_scope_t& args) {
+ if (report->report_period.empty())
+ report->report_period = "daily";
+ else
+ report->report_period = std::string("daily ") + report->report_period;
+ }
+
+ value_t option_weekly(call_scope_t& args) { // W
+ if (report->report_period.empty())
+ report->report_period = "weekly";
+ else
+ report->report_period = std::string("weekly ") + report->report_period;
+ }
+
+ value_t option_monthly(call_scope_t& args) { // M
+ if (report->report_period.empty())
+ report->report_period = "monthly";
+ else
+ report->report_period = std::string("monthly ") + report->report_period;
+ }
+
+ value_t option_quarterly(call_scope_t& args) {
+ if (report->report_period.empty())
+ report->report_period = "quarterly";
+ else
+ report->report_period = std::string("quarterly ") + report->report_period;
+ }
+
+ value_t option_yearly(call_scope_t& args) { // Y
+ if (report->report_period.empty())
+ report->report_period = "yearly";
+ else
+ report->report_period = std::string("yearly ") + report->report_period;
+ }
+
+ value_t option_dow(call_scope_t& args) {
+ report->days_of_the_week = true;
+ }
+
+ value_t option_by_payee(call_scope_t& args) { // P
+ report->by_payee = true;
+ }
+
+ value_t option_comm_as_payee(call_scope_t& args) { // x
+ report->comm_as_payee = true;
+ }
+
+ value_t option_code_as_payee(call_scope_t& args) {
+ report->code_as_payee = true;
+ }
+
+ value_t option_budget(call_scope_t& args) {
+ report->budget_flags = BUDGET_BUDGETED;
+ }
+
+ value_t option_add_budget(call_scope_t& args) {
+ report->budget_flags = BUDGET_BUDGETED | BUDGET_UNBUDGETED;
+ }
+
+ value_t option_unbudgeted(call_scope_t& args) {
+ report->budget_flags = BUDGET_UNBUDGETED;
+ }
+
+ value_t option_forecast(call_scope_t& args) { // :
+ report->forecast_limit = optarg;
+ }
+
+ value_t option_reconcile(call_scope_t& args) { // :
+ report->reconcile_balance = optarg;
+ }
+
+ value_t option_reconcile_date(call_scope_t& args) { // :
+ report->reconcile_date = optarg;
+ }
+#endif
+
+ value_t option_limit_(call_scope_t& args) { // l:
+ if (! predicate.empty())
+ predicate += "&";
+ predicate += args[0].as_string();
+ return true;
+ }
+
+#if 0
+ value_t option_only(call_scope_t& args) { // :
+ if (! report->secondary_predicate.empty())
+ report->secondary_predicate += "&";
+ report->secondary_predicate += "(";
+ report->secondary_predicate += optarg;
+ report->secondary_predicate += ")";
+ }
+
+ value_t option_display(call_scope_t& args) { // d:
+ if (! report->display_predicate.empty())
+ report->display_predicate += "&";
+ report->display_predicate += "(";
+ report->display_predicate += optarg;
+ report->display_predicate += ")";
+ }
+#endif
+
+ value_t option_amount_(call_scope_t& args) { // t:
+ amount_expr = args[0].as_string();
+ return true;
+ }
+
+ value_t option_total_(call_scope_t& args) { // T:
+ total_expr = args[0].as_string();
+ return true;
+ }
+
+ value_t get_amount_expr(call_scope_t& scope);
+ value_t get_total_expr(call_scope_t& scope);
+
+ value_t option_amount_data(call_scope_t&) { // j
+ format_string = session.plot_amount_format;
+ return true;
+ }
+
+ value_t option_total_data(call_scope_t&) { // J
+ format_string = session.plot_total_format;
+ return true;
+ }
+
+#if 0
+ value_t option_ansi(call_scope_t& args) {
+ format_t::ansi_codes = true;
+ format_t::ansi_invert = false;
+ }
+
+ value_t option_ansi_invert(call_scope_t& args) {
+ format_t::ansi_codes =
+ format_t::ansi_invert = true;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ //
+ // Commodity reporting
+
+ value_t option_base(call_scope_t& args) { // :
+ amount_t::keep_base = true;
+ }
+
+ value_t option_price_db(call_scope_t& args) { // :
+ config->price_db = optarg;
+ }
+
+ value_t option_price_exp(call_scope_t& args) { // Z:
+ config->pricing_leeway = std::atol(optarg) * 60;
+ }
+
+ value_t option_download(call_scope_t& args) { // Q
+ config->download_quotes = true;
+ }
+
+ value_t option_quantity(call_scope_t& args) { // O
+ ledger::amount_expr = "amount";
+ ledger::total_expr = "total";
+ }
+
+ value_t option_basis(call_scope_t& args) { // B
+ ledger::amount_expr = "b";
+ ledger::total_expr = "B";
+ }
+
+ value_t option_price(call_scope_t& args) { // I
+ ledger::amount_expr = "i";
+ ledger::total_expr = "I";
+ }
+
+ value_t option_market(call_scope_t& args) { // V
+ report->show_revalued = true;
+
+ ledger::amount_expr = "v";
+ ledger::total_expr = "V";
+ }
+
+#if 0
+ namespace {
+ void parse_price_setting(const char * optarg)
+ {
+ char * equals = std::strchr(optarg, '=');
+ if (! equals)
+ return;
+
+ while (std::isspace(*optarg))
+ 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 = commodity_t::find_or_create(symbol)) {
+ commodity->add_price(datetime_t::now, price);
+ commodity->history()->bogus_time = datetime_t::now;
+ }
+ }
+ }
+#endif
+#endif
+
+ //
+ // Scope members
+ //
+
+ virtual expr_t::ptr_op_t lookup(const string& name);
+};
+
+} // namespace ledger
+
+#endif // _REPORT_H
diff --git a/src/scope.cc b/src/scope.cc
new file mode 100644
index 00000000..7a349949
--- /dev/null
+++ b/src/scope.cc
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2003-2008, 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 "scope.h"
+
+namespace ledger {
+
+void symbol_scope_t::define(const string& name, expr_t::ptr_op_t def)
+{
+ DEBUG("ledger.xpath.syms", "Defining '" << name << "' = " << def);
+
+ std::pair<symbol_map::iterator, bool> result
+ = symbols.insert(symbol_map::value_type(name, def));
+ if (! result.second) {
+ symbol_map::iterator i = symbols.find(name);
+ assert(i != symbols.end());
+ symbols.erase(i);
+
+ std::pair<symbol_map::iterator, bool> result2
+ = symbols.insert(symbol_map::value_type(name, def));
+ if (! result2.second)
+ throw_(compile_error,
+ "Redefinition of '" << name << "' in same scope");
+ }
+}
+
+expr_t::ptr_op_t symbol_scope_t::lookup(const string& name)
+{
+ symbol_map::const_iterator i = symbols.find(name);
+ if (i != symbols.end())
+ return (*i).second;
+
+ return child_scope_t::lookup(name);
+}
+
+#if 0
+namespace {
+ int count_leaves(expr_t::ptr_op_t expr)
+ {
+ int count = 0;
+ if (expr->kind != expr_t::op_t::O_COMMA) {
+ count = 1;
+ } else {
+ count += count_leaves(expr->left());
+ count += count_leaves(expr->right());
+ }
+ return count;
+ }
+
+ expr_t::ptr_op_t reduce_leaves(expr_t::ptr_op_t expr,
+ expr_t::ptr_op_t context)
+ {
+ if (! expr)
+ return NULL;
+
+ expr_t::ptr_op_t temp;
+
+ if (expr->kind != expr_t::op_t::O_COMMA) {
+ if (expr->kind < expr_t::op_t::TERMINALS) {
+ temp.reset(expr);
+ } else {
+ temp.reset(new op_t(expr_t::op_t::VALUE));
+ temp->set_value(NULL_VALUE);
+ expr->compute(temp->as_value_lval(), context);
+ }
+ } else {
+ temp.reset(new op_t(expr_t::op_t::O_COMMA));
+ temp->set_left(reduce_leaves(expr->left(), context));
+ temp->set_right(reduce_leaves(expr->right(), context));
+ }
+ return temp.release();
+ }
+
+ expr_t::ptr_op_t find_leaf(expr_t::ptr_op_t context, int goal, long& found)
+ {
+ if (! context)
+ return NULL;
+
+ if (context->kind != expr_t::op_t::O_COMMA) {
+ if (goal == found++)
+ return context;
+ } else {
+ expr_t::ptr_op_t expr = find_leaf(context->left(), goal, found);
+ if (expr)
+ return expr;
+ expr = find_leaf(context->right(), goal, found);
+ if (expr)
+ return expr;
+ }
+ return NULL;
+ }
+}
+#endif
+
+} // namespace ledger
diff --git a/src/scope.h b/src/scope.h
new file mode 100644
index 00000000..88b45d84
--- /dev/null
+++ b/src/scope.h
@@ -0,0 +1,280 @@
+/*
+ * Copyright (c) 2003-2008, 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 _SCOPE_H
+#define _SCOPE_H
+
+#include "expr.h"
+#include "op.h"
+
+namespace ledger {
+
+class scope_t
+{
+public:
+ explicit scope_t() {
+ TRACE_CTOR(scope_t, "");
+ }
+ virtual ~scope_t() {
+ TRACE_DTOR(scope_t);
+ }
+
+ virtual expr_t::ptr_op_t lookup(const string& name) = 0;
+
+ value_t resolve(const string& name) {
+ expr_t::ptr_op_t definition = lookup(name);
+ if (definition)
+ return definition->calc(*this);
+ else
+ return NULL_VALUE;
+ }
+
+ virtual optional<scope_t&> find_scope(const std::type_info&, bool = true) {
+ return none;
+ }
+};
+
+template <typename T>
+inline T& find_scope(scope_t& scope, bool skip_this = true) {
+ optional<scope_t&> found = scope.find_scope(typeid(T), skip_this);
+ assert(found);
+ return static_cast<T&>(*found);
+}
+
+template <typename T>
+inline optional<T&> maybe_find_scope(scope_t& scope, bool skip_this = true) {
+ optional<scope_t&> found = scope.find_scope(typeid(T), skip_this);
+ if (found)
+ return optional<T&>(static_cast<T&>(*found));
+ else
+ return none;
+}
+
+class child_scope_t : public noncopyable, public scope_t
+{
+public:
+ scope_t * parent;
+
+ explicit child_scope_t() : parent(NULL) {
+ TRACE_CTOR(child_scope_t, "");
+ }
+ explicit child_scope_t(scope_t& _parent)
+ : parent(&_parent) {
+ TRACE_CTOR(child_scope_t, "scope_t&");
+ }
+ virtual ~child_scope_t() {
+ TRACE_DTOR(child_scope_t);
+ }
+
+ virtual expr_t::ptr_op_t lookup(const string& name) {
+ if (parent)
+ return parent->lookup(name);
+ return expr_t::ptr_op_t();
+ }
+
+ virtual optional<scope_t&> find_scope(const std::type_info& type,
+ bool skip_this = true) {
+ for (scope_t * ptr = (skip_this ? parent : this); ptr; ) {
+ if (typeid(*ptr) == type)
+ return *ptr;
+ if (child_scope_t * scope = dynamic_cast<child_scope_t *>(ptr))
+ ptr = scope->parent;
+ else
+ ptr = NULL;
+ }
+ return none;
+ }
+};
+
+class symbol_scope_t : public child_scope_t
+{
+ typedef std::map<const string, expr_t::ptr_op_t> symbol_map;
+
+ symbol_map symbols;
+
+public:
+ explicit symbol_scope_t() {
+ TRACE_CTOR(symbol_scope_t, "");
+ }
+ explicit symbol_scope_t(scope_t& _parent) : child_scope_t(_parent) {
+ TRACE_CTOR(symbol_scope_t, "scope_t&");
+ }
+ virtual ~symbol_scope_t() {
+ TRACE_DTOR(symbol_scope_t);
+ }
+
+ void define(const string& name, const value_t& val) {
+ define(name, expr_t::op_t::wrap_value(val));
+ }
+ void define(const string& name, const function_t& func) {
+ define(name, expr_t::op_t::wrap_functor(func));
+ }
+ virtual void define(const string& name, expr_t::ptr_op_t def);
+
+ virtual expr_t::ptr_op_t lookup(const string& name);
+};
+
+class call_scope_t : public child_scope_t
+{
+ value_t args;
+
+ call_scope_t();
+
+public:
+ explicit call_scope_t(scope_t& _parent) : child_scope_t(_parent) {
+ TRACE_CTOR(call_scope_t, "scope_t&");
+ }
+ virtual ~call_scope_t() {
+ TRACE_DTOR(call_scope_t);
+ }
+
+ void set_args(const value_t& _args) {
+ args = _args;
+ }
+ value_t& value() {
+ return args;
+ }
+
+ value_t& operator[](const unsigned int index) {
+ // jww (2008-07-21): exception here if it's out of bounds
+ return args[index];
+ }
+ const value_t& operator[](const unsigned int index) const {
+ // jww (2008-07-21): exception here if it's out of bounds
+ return args[index];
+ }
+
+ void push_back(const value_t& val) {
+ args.push_back(val);
+ }
+ void pop_back() {
+ args.pop_back();
+ }
+
+ const std::size_t size() const {
+ return args.size();
+ }
+};
+
+template <typename T>
+class ptr_t : public noncopyable
+{
+ T * value;
+
+ ptr_t();
+
+public:
+ ptr_t(scope_t& scope, const string& name)
+ : value(scope.resolve(name).template as_pointer<T>()) {
+ TRACE_CTOR(ptr_t, "scope_t&, const string&");
+ }
+ ptr_t(call_scope_t& scope, const unsigned int idx)
+ : value(scope[idx].template as_pointer<T>()) {
+ TRACE_CTOR(ptr_t, "call_scope_t&, const unsigned int");
+ }
+ ~ptr_t() throw() {
+ TRACE_DTOR(ptr_t);
+ }
+
+ T& operator *() { return *value; }
+ T * operator->() { return value; }
+};
+
+template <typename T>
+class var_t : public noncopyable
+{
+ optional<value_t> value;
+
+ var_t();
+
+public:
+ var_t(scope_t& scope, const string& name)
+ {
+ TRACE_CTOR(var_t, "scope_t&, const string&");
+
+ try {
+ value = scope.resolve(name);
+ }
+ catch (...) {
+ DEBUG("scope.var_t", "Failed lookup var_t(\"" << name << "\")");
+ value = none;
+ }
+ }
+
+ var_t(call_scope_t& scope, const unsigned int idx)
+ {
+ TRACE_CTOR(var_t, "call_scope_t&, const unsigned int");
+
+ if (idx < scope.size())
+ value = scope[idx];
+ else
+ value = none;
+ }
+
+ ~var_t() throw() {
+ TRACE_DTOR(var_t);
+ }
+
+ operator bool() { return value; }
+
+ T& operator *();
+ const T& operator *() const;
+
+ T * operator->() {
+ return &**this;
+ }
+ const T * operator->() const {
+ return &**this;
+ }
+};
+
+template <>
+inline long& var_t<long>::operator *() {
+ return value->as_long_lval();
+}
+template <>
+inline const long& var_t<long>::operator *() const {
+ return value->as_long();
+}
+
+template <>
+inline string& var_t<string>::operator *() {
+ return value->as_string_lval();
+}
+template <>
+inline const string& var_t<string>::operator *() const {
+ return value->as_string();
+}
+
+} // namespace ledger
+
+#endif // _SCOPE_H
+
diff --git a/src/session.cc b/src/session.cc
new file mode 100644
index 00000000..06288444
--- /dev/null
+++ b/src/session.cc
@@ -0,0 +1,391 @@
+/*
+ * Copyright (c) 2003-2008, 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 "session.h"
+#include "report.h"
+#include "handler.h"
+#include "iterators.h"
+#include "filters.h"
+
+namespace ledger {
+
+session_t * session_t::current = NULL;
+
+#if 0
+boost::mutex session_t::session_mutex;
+#endif
+
+void set_session_context(session_t * session)
+{
+#if 0
+ session_t::session_mutex.lock();
+#endif
+
+ if (session && ! session_t::current) {
+ session_t::initialize();
+ }
+ else if (! session && session_t::current) {
+ session_t::shutdown();
+#if 0
+ session_t::session_mutex.unlock();
+#endif
+ }
+
+ session_t::current = session;
+}
+
+void release_session_context()
+{
+#if 0
+ session_t::session_mutex.unlock();
+#endif
+}
+
+session_t::session_t()
+ : register_format
+ ("%-.10D %-.20P %-.22A %12.67t %!12.80T\n%/"
+ "%32|%-.22A %12.67t %!12.80T\n"),
+ wide_register_format
+ ("%-.10D %-.35P %-.38A %22.108t %!22.132T\n%/"
+ "%48|%-.38A %22.108t %!22.132T\n"),
+ print_format
+ ("\n%d %Y%C%P\n %-34W %12o%n\n%/ %-34W %12o%n\n"),
+ balance_format
+ ("%20T %2_%-a\n"),
+ equity_format
+ ("\n%D %Y%C%P\n%/ %-34W %12t\n"),
+ plot_amount_format
+ ("%D %(S(t))\n"),
+ plot_total_format
+ ("%D %(S(T))\n"),
+ write_hdr_format
+ ("%d %Y%C%P\n"),
+ write_xact_format
+ (" %-34W %12o%n\n"),
+ prices_format
+ ("%[%Y/%m/%d %H:%M:%S %Z] %-10A %12t %12T\n"),
+ pricesdb_format
+ ("P %[%Y/%m/%d %H:%M:%S] %A %t\n"),
+
+ pricing_leeway(24 * 3600),
+
+ download_quotes(false),
+ use_cache(false),
+ cache_dirty(false),
+
+ now(now),
+
+#if 0
+ elision_style(ABBREVIATE),
+#endif
+ abbrev_length(2),
+
+ ansi_codes(false),
+ ansi_invert(false),
+
+ master(new account_t(NULL, ""))
+{
+ TRACE_CTOR(session_t, "");
+}
+
+session_t::~session_t()
+{
+ TRACE_DTOR(session_t);
+}
+
+std::size_t session_t::read_journal(journal_t& journal,
+ std::istream& in,
+ const path& pathname,
+ account_t * master)
+{
+ if (! master)
+ master = journal.master;
+
+ foreach (journal_t::parser_t& parser, parsers)
+ if (parser.test(in))
+ return parser.parse(in, *this, journal, master, &pathname);
+
+ return 0;
+}
+
+std::size_t session_t::read_journal(journal_t& journal,
+ const path& pathname,
+ account_t * master)
+{
+ journal.sources.push_back(pathname);
+
+ if (! exists(pathname))
+ throw_(std::logic_error, "Cannot read file" << pathname);
+
+ ifstream stream(pathname);
+ return read_journal(journal, stream, pathname, master);
+}
+
+void session_t::read_init()
+{
+ if (! init_file)
+ return;
+
+ if (! exists(*init_file))
+ throw_(std::logic_error, "Cannot read init file" << *init_file);
+
+ ifstream init(*init_file);
+
+ // jww (2006-09-15): Read initialization options here!
+}
+
+std::size_t session_t::read_data(journal_t& journal,
+ const string& master_account)
+{
+ if (data_file.empty())
+ throw_(parse_error, "No journal file was specified (please use -f)");
+
+ TRACE_START(parser, 1, "Parsing journal file");
+
+ std::size_t entry_count = 0;
+
+ DEBUG("ledger.cache", "3. use_cache = " << use_cache);
+
+ if (use_cache && cache_file) {
+ DEBUG("ledger.cache", "using_cache " << cache_file->string());
+ cache_dirty = true;
+ if (exists(*cache_file)) {
+ push_variable<optional<path> >
+ save_price_db(journal.price_db, price_db);
+
+ entry_count += read_journal(journal, *cache_file);
+ if (entry_count > 0)
+ cache_dirty = false;
+ }
+ }
+
+ if (entry_count == 0) {
+ account_t * acct = NULL;
+ if (! master_account.empty())
+ acct = journal.find_account(master_account);
+
+ journal.price_db = price_db;
+ if (journal.price_db && exists(*journal.price_db)) {
+ if (read_journal(journal, *journal.price_db)) {
+ throw_(parse_error, "Entries not allowed in price history file");
+ } else {
+ DEBUG("ledger.cache",
+ "read price database " << journal.price_db->string());
+ journal.sources.pop_back();
+ }
+ }
+
+ DEBUG("ledger.cache", "rejected cache, parsing " << data_file.string());
+ if (data_file == "-") {
+ use_cache = false;
+ journal.sources.push_back("/dev/stdin");
+ entry_count += read_journal(journal, std::cin, "/dev/stdin", acct);
+ }
+ else if (exists(data_file)) {
+ entry_count += read_journal(journal, data_file, acct);
+ if (journal.price_db)
+ journal.sources.push_back(*journal.price_db);
+ clean_accounts();
+ }
+ }
+
+ VERIFY(journal.valid());
+
+ TRACE_STOP(parser, 1);
+
+ return entry_count;
+}
+
+namespace {
+ account_t * find_account_re_(account_t * account, const mask_t& regexp)
+ {
+ if (regexp.match(account->fullname()))
+ return account;
+
+ foreach (accounts_map::value_type& pair, account->accounts)
+ if (account_t * a = find_account_re_(pair.second, regexp))
+ return a;
+
+ return NULL;
+ }
+}
+
+account_t * session_t::find_account_re(const string& regexp)
+{
+ return find_account_re_(master.get(), mask_t(regexp));
+}
+
+void session_t::clean_xacts()
+{
+ session_xacts_iterator walker(*this);
+ pass_down_xacts
+ (xact_handler_ptr(new clear_xact_xdata), walker);
+}
+
+void session_t::clean_xacts(entry_t& entry)
+{
+ entry_xacts_iterator walker(entry);
+ pass_down_xacts(xact_handler_ptr(new clear_xact_xdata), walker);
+}
+
+void session_t::clean_accounts()
+{
+ basic_accounts_iterator acct_walker(*master);
+ pass_down_accounts(acct_handler_ptr(new clear_account_xdata),
+ acct_walker);
+}
+
+#if 0
+value_t session_t::resolve(const string& name, expr_t::scope_t& locals)
+{
+ const char * p = name.c_str();
+ switch (*p) {
+ case 'd':
+#if 0
+ if (name == "date_format") {
+ // jww (2007-04-18): What to do here?
+ return string_value(moment_t::output_format);
+ }
+#endif
+ break;
+
+ case 'n':
+ switch (*++p) {
+ case 'o':
+ if (name == "now")
+ return value_t(now);
+ break;
+ }
+ break;
+
+ case 'r':
+ if (name == "register_format")
+ return string_value(register_format);
+ break;
+ }
+ return expr_t::scope_t::resolve(name, locals);
+}
+#endif
+
+expr_t::ptr_op_t session_t::lookup(const string& name)
+{
+ const char * p = name.c_str();
+ switch (*p) {
+ case 'b':
+ if (std::strcmp(p, "balance_format") == 0)
+ return expr_t::op_t::wrap_value(string_value(balance_format));
+ break;
+
+ case 'e':
+ if (std::strcmp(p, "equity_format") == 0)
+ return expr_t::op_t::wrap_value(string_value(equity_format));
+ break;
+
+ case 'p':
+ if (std::strcmp(p, "plot_amount_format") == 0)
+ return expr_t::op_t::wrap_value(string_value(plot_amount_format));
+ else if (std::strcmp(p, "plot_total_format") == 0)
+ return expr_t::op_t::wrap_value(string_value(plot_total_format));
+ else if (std::strcmp(p, "prices_format") == 0)
+ return expr_t::op_t::wrap_value(string_value(prices_format));
+ else if (std::strcmp(p, "pricesdb_format") == 0)
+ return expr_t::op_t::wrap_value(string_value(pricesdb_format));
+ else if (std::strcmp(p, "print_format") == 0)
+ return expr_t::op_t::wrap_value(string_value(print_format));
+ break;
+
+ case 'r':
+ if (std::strcmp(p, "register_format") == 0)
+ return expr_t::op_t::wrap_value(string_value(register_format));
+ break;
+
+ case 'w':
+ if (std::strcmp(p, "wide_register_format") == 0)
+ return expr_t::op_t::wrap_value(string_value(wide_register_format));
+ else if (std::strcmp(p, "write_hdr_format") == 0)
+ return expr_t::op_t::wrap_value(string_value(write_hdr_format));
+ else if (std::strcmp(p, "write_xact_format") == 0)
+ return expr_t::op_t::wrap_value(string_value(write_xact_format));
+ break;
+
+ case 'o':
+ if (std::strncmp(p, "opt_", 4) == 0) {
+ p = p + 4;
+ switch (*p) {
+ case 'd':
+ if (std::strcmp(p, "debug_") == 0)
+ return MAKE_FUNCTOR(session_t::option_debug_);
+ break;
+
+ case 'f':
+ if ((*(p + 1) == '_' && ! *(p + 2)) ||
+ std::strcmp(p, "file_") == 0)
+ return MAKE_FUNCTOR(session_t::option_file_);
+ break;
+
+ case 't':
+ if (std::strcmp(p, "trace_") == 0)
+ return MAKE_FUNCTOR(session_t::option_trace_);
+ break;
+
+ case 'v':
+ if (! *(p + 1) || std::strcmp(p, "verbose") == 0)
+ return MAKE_FUNCTOR(session_t::option_verbose);
+ else if (std::strcmp(p, "version") == 0)
+ return MAKE_FUNCTOR(session_t::option_version);
+ else if (std::strcmp(p, "verify") == 0)
+ return MAKE_FUNCTOR(session_t::option_verify);
+ break;
+ }
+ }
+ break;
+ }
+
+ return expr_t::ptr_op_t();
+}
+
+// jww (2007-04-26): All of Ledger should be accessed through a
+// session_t object
+void session_t::initialize()
+{
+ amount_t::initialize();
+ value_t::initialize();
+ expr_t::initialize();
+}
+
+void session_t::shutdown()
+{
+ expr_t::shutdown();
+ value_t::shutdown();
+ amount_t::shutdown();
+}
+
+} // namespace ledger
diff --git a/src/session.h b/src/session.h
new file mode 100644
index 00000000..dc78d6f5
--- /dev/null
+++ b/src/session.h
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2003-2008, 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 _SESSION_H
+#define _SESSION_H
+
+#include "scope.h"
+#include "journal.h"
+#include "account.h"
+#include "format.h"
+
+namespace ledger {
+
+class report_t;
+
+class session_t : public noncopyable, public scope_t
+{
+ static void initialize();
+ static void shutdown();
+
+ friend void set_session_context(session_t * session);
+ friend void release_session_context();
+
+public:
+ static session_t * current;
+
+ scoped_ptr<report_t> current_report;
+
+ path data_file;
+ optional<path> init_file;
+ optional<path> cache_file;
+ optional<path> price_db;
+
+ string register_format;
+ string wide_register_format;
+ string print_format;
+ string balance_format;
+ string equity_format;
+ string plot_amount_format;
+ string plot_total_format;
+ string write_hdr_format;
+ string write_xact_format;
+ string prices_format;
+ string pricesdb_format;
+
+ unsigned long pricing_leeway;
+
+ bool download_quotes;
+ bool use_cache;
+ bool cache_dirty;
+
+ datetime_t now;
+ date_t today;
+
+ format_t::elision_style_t elision_style;
+ int abbrev_length;
+
+ bool ansi_codes;
+ bool ansi_invert;
+
+ ptr_list<journal_t> journals;
+ ptr_list<journal_t::parser_t> parsers;
+ scoped_ptr<commodity_pool_t> commdity_pool;
+ scoped_ptr<account_t> master;
+ mutable accounts_map accounts_cache;
+
+ session_t();
+ virtual ~session_t();
+
+ journal_t * create_journal() {
+ journal_t * journal = new journal_t(this);
+ journals.push_back(journal);
+ return journal;
+ }
+ void close_journal(journal_t * journal) {
+ for (ptr_list<journal_t>::iterator i = journals.begin();
+ i != journals.end();
+ i++)
+ if (&*i == journal) {
+ journals.erase(i);
+ return;
+ }
+ assert(false);
+ checked_delete(journal);
+ }
+
+ std::size_t read_journal(journal_t& journal,
+ std::istream& in,
+ const path& pathname,
+ account_t * master = NULL);
+ std::size_t read_journal(journal_t& journal,
+ const path& pathname,
+ account_t * master = NULL);
+
+ void read_init();
+
+ std::size_t read_data(journal_t& journal,
+ const string& master_account = "");
+
+ void register_parser(journal_t::parser_t * parser) {
+ parsers.push_back(parser);
+ }
+ void unregister_parser(journal_t::parser_t * parser) {
+ for (ptr_list<journal_t::parser_t>::iterator i = parsers.begin();
+ i != parsers.end();
+ i++)
+ if (&*i == parser) {
+ parsers.erase(i);
+ return;
+ }
+ assert(false);
+ checked_delete(parser);
+ }
+
+ //
+ // Dealing with accounts
+ //
+
+ void add_account(account_t * acct) {
+ master->add_account(acct);
+ }
+ bool remove_account(account_t * acct) {
+ return master->remove_account(acct);
+ }
+
+ account_t * find_account(const string& name, bool auto_create = true) {
+ accounts_map::iterator c = accounts_cache.find(name);
+ if (c != accounts_cache.end())
+ return (*c).second;
+
+ account_t * account = master->find_account(name, auto_create);
+ accounts_cache.insert(accounts_map::value_type(name, account));
+ return account;
+ }
+ account_t * find_account_re(const string& regexp);
+
+ void clean_accounts();
+
+ void clean_xacts();
+ void clean_xacts(entry_t& entry);
+
+ //
+ // Scope members
+ //
+
+ virtual expr_t::ptr_op_t lookup(const string& name);
+
+ //
+ // Help options
+ //
+
+ value_t option_version(scope_t&) {
+ std::cout << "Ledger " << ledger::version << ", the command-line accounting tool";
+ std::cout << "\n\nCopyright (c) 2003-2008, John Wiegley. All rights reserved.\n\n\
+This program is made available under the terms of the BSD Public License.\n\
+See LICENSE file included with the distribution for details and disclaimer.\n";
+ std::cout << "\n(modules: gmp, pcre";
+#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
+ std::cout << ", xml";
+#endif
+#ifdef HAVE_LIBOFX
+ std::cout << ", ofx";
+#endif
+ std::cout << ")\n";
+ return NULL_VALUE;
+ }
+
+ //
+ // Debug options
+ //
+
+ value_t option_trace_(scope_t&) {
+ return NULL_VALUE;
+ }
+ value_t option_debug_(scope_t&) {
+ return NULL_VALUE;
+ }
+ value_t option_verify(scope_t&) {
+ return NULL_VALUE;
+ }
+
+ value_t option_verbose(scope_t&) {
+#if defined(LOGGING_ON)
+ if (_log_level < LOG_INFO)
+ _log_level = LOG_INFO;
+#endif
+ return NULL_VALUE;
+ }
+
+ //
+ // Option handlers
+ //
+
+ value_t option_file_(call_scope_t& args) {
+ assert(args.size() == 1);
+ data_file = args[0].as_string();
+ return NULL_VALUE;
+ }
+
+#if 0
+#if defined(USE_BOOST_PYTHON)
+ value_t option_import_(call_scope_t& args) {
+ python_import(optarg);
+ return NULL_VALUE;
+ }
+ value_t option_import_stdin(call_scope_t& args) {
+ python_eval(std::cin, PY_EVAL_MULTI);
+ return NULL_VALUE;
+ }
+#endif
+#endif
+};
+
+/**
+ * This sets the current session context, transferring all static
+ * globals to point at the data structures related to this session.
+ * Although Ledger itself is not thread-safe, by locking, switching
+ * session context, then unlocking after the operation is done,
+ * multiple threads can sequentially make use of the library. Thus, a
+ * session_t maintains all of the information relating to a single
+ * usage of the Ledger library.
+ */
+void set_session_context(session_t * session = NULL);
+
+} // namespace ledger
+
+#endif // _SESSION_H
diff --git a/src/system.hh b/src/system.hh
new file mode 100644
index 00000000..221b81ef
--- /dev/null
+++ b/src/system.hh
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2003-2008, 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 _SYSTEM_HH
+#define _SYSTEM_HH
+
+/**
+ * @file system.hh
+ * @author John Wiegley
+ * @date Mon Apr 23 03:43:05 2007
+ *
+ * @brief All system headers needed by Ledger.
+ *
+ * These are collected here so that a pre-compiled header can be made.
+ * None of these header files (with the exception of acconf.h, when
+ * configure is re-run) are expected to change.
+ */
+
+#include "acconf.h"
+
+#if defined(__GNUG__) && __GNUG__ < 3
+#define _XOPEN_SOURCE
+#endif
+
+#include <algorithm>
+#include <exception>
+#include <typeinfo>
+#include <stdexcept>
+#include <iostream>
+#include <streambuf>
+#include <iomanip>
+#include <fstream>
+#include <sstream>
+#include <iterator>
+#include <list>
+#include <map>
+#include <memory>
+#include <new>
+#include <stack>
+#include <string>
+#include <vector>
+
+#if defined(__GNUG__) && __GNUG__ < 3
+
+namespace std {
+ inline ostream & right (ostream & i) {
+ i.setf(i.right, i.adjustfield);
+ return i;
+ }
+ inline ostream & left (ostream & i) {
+ i.setf(i.left, i.adjustfield);
+ return i;
+ }
+}
+
+typedef unsigned long istream_pos_type;
+typedef unsigned long ostream_pos_type;
+
+#else // ! (defined(__GNUG__) && __GNUG__ < 3)
+
+typedef std::istream::pos_type istream_pos_type;
+typedef std::ostream::pos_type ostream_pos_type;
+
+#endif
+
+#include <cassert>
+#include <cctype>
+#include <cstdarg>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <ctime>
+
+#if defined __FreeBSD__ && __FreeBSD__ <= 4
+// FreeBSD has a broken isspace macro, so don't use it
+#undef isspace(c)
+#endif
+
+#include <sys/stat.h>
+
+#ifdef WIN32
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
+
+#if defined(HAVE_GETPWUID) || defined(HAVE_GETPWNAM)
+#include <pwd.h>
+#endif
+
+#if defined(HAVE_NL_LANGINFO)
+#include <langinfo.h>
+#endif
+
+#include <gmp.h>
+
+extern "C" {
+#if defined(HAVE_EXPAT)
+#include <expat.h> // expat XML parser
+#elif defined(HAVE_XMLPARSE)
+#include <xmlparse.h> // expat XML parser
+#endif
+}
+
+#if defined(HAVE_LIBOFX)
+#include <libofx.h>
+#endif
+
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/any.hpp>
+#include <boost/cast.hpp>
+#include <boost/current_function.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/filesystem/convenience.hpp>
+#include <boost/filesystem/exception.hpp>
+#include <boost/filesystem/fstream.hpp>
+#include <boost/filesystem/operations.hpp>
+#include <boost/filesystem/path.hpp>
+#include <boost/foreach.hpp>
+#include <boost/function.hpp>
+#include <boost/intrusive_ptr.hpp>
+#include <boost/lambda/bind.hpp>
+#include <boost/lambda/lambda.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/key_extractors.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/random_access_index.hpp>
+#include <boost/multi_index/sequenced_index.hpp>
+#include <boost/multi_index_container.hpp>
+#include <boost/operators.hpp>
+#include <boost/optional.hpp>
+#include <boost/ptr_container/ptr_list.hpp>
+#include <boost/ptr_container/ptr_vector.hpp>
+#include <boost/regex.hpp>
+#include <boost/variant.hpp>
+
+#endif // _SYSTEM_HH
diff --git a/src/textual.cc b/src/textual.cc
new file mode 100644
index 00000000..e46ae22a
--- /dev/null
+++ b/src/textual.cc
@@ -0,0 +1,1131 @@
+/*
+ * Copyright (c) 2003-2008, 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.
+ */
+
+#if defined(__GNUG__) && __GNUG__ < 3
+#define _XOPEN_SOURCE
+#endif
+
+#include "textual.h"
+#include "expr.h"
+#include "parser.h"
+#include "session.h"
+#include "option.h"
+#include "acconf.h"
+
+#define TIMELOG_SUPPORT 1
+
+namespace ledger {
+
+#define MAX_LINE 1024
+
+static path pathname;
+static unsigned int linenum;
+static unsigned int src_idx;
+static accounts_map account_aliases;
+
+static std::list<std::pair<path, int> > include_stack;
+
+#ifdef TIMELOG_SUPPORT
+struct time_entry_t
+{
+ datetime_t checkin;
+ account_t * account;
+ string desc;
+
+ time_entry_t() : account(NULL) {
+ TRACE_CTOR(time_entry_t, "");
+ }
+ time_entry_t(const datetime_t& _checkin,
+ account_t * _account = NULL,
+ const string& _desc = "")
+ : checkin(_checkin), account(_account), desc(_desc) {
+ TRACE_CTOR(time_entry_t, "const datetime_t&, account_t *, const string&");
+ }
+ time_entry_t(const time_entry_t& entry)
+ : checkin(entry.checkin), account(entry.account),
+ desc(entry.desc) {
+ TRACE_CTOR(time_entry_t, "copy");
+ }
+ ~time_entry_t() throw() {
+ TRACE_DTOR(time_entry_t);
+ }
+};
+#endif
+
+namespace {
+ optional<expr_t> parse_amount_expr(std::istream& in,
+ amount_t& amount,
+ xact_t * xact,
+ unsigned short flags = 0)
+ {
+ expr_t expr(in, flags | EXPR_PARSE_PARTIAL);
+
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed an amount expression");
+
+#ifdef DEBUG_ENABLED
+ DEBUG_IF("ledger.textual.parse") {
+ if (_debug_stream) {
+ ledger::dump_value_expr(*_debug_stream, expr);
+ *_debug_stream << std::endl;
+ }
+ }
+#endif
+
+ if (expr) {
+ amount = expr.calc(*xact).as_amount();
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "The transaction amount is " << amount);
+ return expr;
+ }
+ return none;
+ }
+}
+
+xact_t * parse_xact(char * line, account_t * account, entry_t * entry = NULL)
+{
+ std::istringstream in(line);
+
+ string err_desc;
+ try {
+
+ // The account will be determined later...
+ std::auto_ptr<xact_t> xact(new xact_t(NULL));
+ if (entry)
+ xact->entry = entry;
+
+ // Parse the state flag
+
+ char p = peek_next_nonws(in);
+ switch (p) {
+ case '*':
+ xact->state = xact_t::CLEARED;
+ in.get(p);
+ p = peek_next_nonws(in);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed the CLEARED flag");
+ break;
+ case '!':
+ xact->state = xact_t::PENDING;
+ in.get(p);
+ p = peek_next_nonws(in);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed the PENDING flag");
+ break;
+ }
+
+ // Parse the account name
+
+ unsigned long account_beg = static_cast<unsigned long>(in.tellg());
+ unsigned long account_end = account_beg;
+ while (! in.eof()) {
+ in.get(p);
+ if (in.eof() || (std::isspace(p) &&
+ (p == '\t' || in.peek() == EOF ||
+ std::isspace(in.peek()))))
+ break;
+ account_end++;
+ }
+
+ if (account_beg == account_end)
+ throw parse_error("No account was specified");
+
+ char * b = &line[account_beg];
+ char * e = &line[account_end];
+ if ((*b == '[' && *(e - 1) == ']') ||
+ (*b == '(' && *(e - 1) == ')')) {
+ xact->add_flags(XACT_VIRTUAL);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed a virtual account name");
+ if (*b == '[') {
+ xact->add_flags(XACT_BALANCE);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed a balanced virtual account name");
+ }
+ b++; e--;
+ }
+
+ string name(b, e - b);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed account name " << name);
+ if (account_aliases.size() > 0) {
+ accounts_map::const_iterator i = account_aliases.find(name);
+ if (i != account_aliases.end())
+ xact->account = (*i).second;
+ }
+ if (! xact->account)
+ xact->account = account->find_account(name);
+
+ // Parse the optional amount
+
+ bool saw_amount = false;
+
+ if (in.good() && ! in.eof()) {
+ p = peek_next_nonws(in);
+ if (in.eof())
+ goto finished;
+ if (p == ';')
+ goto parse_note;
+ if (p == '=' && entry)
+ goto parse_assign;
+
+ try {
+ unsigned long beg = static_cast<unsigned long>(in.tellg());
+
+ xact->amount_expr =
+ parse_amount_expr(in, xact->amount, xact.get(),
+ EXPR_PARSE_NO_REDUCE | EXPR_PARSE_NO_ASSIGN);
+ saw_amount = true;
+
+ if (! xact->amount.is_null()) {
+ xact->amount.reduce();
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Reduced amount is " << xact->amount);
+ }
+
+ // We don't need to store the actual expression that resulted in the
+ // amount if it's constant
+ if (xact->amount_expr) {
+ if (xact->amount_expr->is_constant())
+ xact->amount_expr = expr_t();
+
+ unsigned long end = static_cast<unsigned long>(in.tellg());
+ xact->amount_expr->set_text(string(line, beg, end - beg));
+ }
+ }
+ catch (const std::exception& err) {
+ add_error_context("While parsing transaction amount:\n");
+ throw err;
+ }
+ }
+
+ // Parse the optional cost (@ PER-UNIT-COST, @@ TOTAL-COST)
+
+ if (in.good() && ! in.eof()) {
+ p = peek_next_nonws(in);
+ if (p == '@') {
+ if (! saw_amount)
+ throw parse_error
+ ("Transaction cannot have a cost expression with an amount");
+
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Found a price indicator");
+ bool per_unit = true;
+ in.get(p);
+ if (in.peek() == '@') {
+ in.get(p);
+ per_unit = false;
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "And it's for a total price");
+ }
+
+ if (in.good() && ! in.eof()) {
+ xact->cost = amount_t();
+
+ try {
+ unsigned long beg = static_cast<unsigned long>(in.tellg());
+
+ xact->cost_expr =
+ parse_amount_expr(in, *xact->cost, xact.get(),
+ EXPR_PARSE_NO_MIGRATE |
+ EXPR_PARSE_NO_ASSIGN);
+
+ if (xact->cost_expr) {
+ unsigned long end = static_cast<unsigned long>(in.tellg());
+ if (per_unit)
+ xact->cost_expr->set_text(string("@") +
+ string(line, beg, end - beg));
+ else
+ xact->cost_expr->set_text(string("@@") +
+ string(line, beg, end - beg));
+ }
+ }
+ catch (const std::exception& err) {
+ add_error_context("While parsing transaction cost:\n");
+ throw err;
+ }
+
+ if (xact->cost->sign() < 0)
+ throw parse_error("A transaction's cost may not be negative");
+
+ amount_t per_unit_cost(*xact->cost);
+ if (per_unit)
+ *xact->cost *= xact->amount;
+ else
+ per_unit_cost /= xact->amount;
+
+ if (xact->amount.commodity() &&
+ ! xact->amount.commodity().annotated) {
+ if (xact->entry)
+ xact->amount.annotate(annotation_t(per_unit_cost,
+ xact->entry->actual_date(),
+ xact->entry->code));
+ else
+ xact->amount.annotate(annotation_t(per_unit_cost));
+ }
+
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Total cost is " << *xact->cost);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Per-unit cost is " << per_unit_cost);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Annotated amount is " << xact->amount);
+ }
+ }
+ }
+
+ parse_assign:
+ if (entry != NULL) {
+ // Add this amount to the related account now
+
+ account_t::xdata_t& xdata(xact->account->xdata());
+
+ if (! xact->amount.is_null()) {
+ if (xdata.value.is_null())
+ xdata.value = xact->amount;
+ else
+ xdata.value += xact->amount;
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "XACT assign: account total = " << xdata.value);
+ }
+
+ // Parse the optional assigned (= AMOUNT)
+
+ if (in.good() && ! in.eof()) {
+ p = peek_next_nonws(in);
+ if (p == '=') {
+ in.get(p);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Found a balance assignment indicator");
+ if (in.good() && ! in.eof()) {
+ amount_t amt;
+
+ try {
+ unsigned long beg = static_cast<unsigned long>(in.tellg());
+
+ optional<expr_t> total_expr =
+ parse_amount_expr(in, amt, xact.get(), EXPR_PARSE_NO_MIGRATE);
+
+ if (amt.is_null())
+ throw parse_error
+ ("An assigned balance must evaluate to a constant value");
+
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "XACT assign: parsed amt = " << amt);
+
+ if (total_expr) {
+ unsigned long end = static_cast<unsigned long>(in.tellg());
+ total_expr->set_text(string("=") +
+ string(line, beg, end - beg));
+ }
+
+ // jww (2008-08-02): Save total_expr somewhere!
+
+ amount_t diff;
+ if (xdata.value.is_amount()) {
+ diff = amt - xdata.value.as_amount();
+ }
+ else if (xdata.value.is_balance()) {
+ optional<amount_t> comm_bal =
+ xdata.value.as_balance().commodity_amount(amt.commodity());
+ diff = amt - (comm_bal ? *comm_bal : amount_t(0L));
+ }
+ else if (xdata.value.is_balance_pair()) {
+ optional<amount_t> comm_bal =
+ xdata.value.as_balance_pair().commodity_amount(amt.commodity());
+ diff = amt - (comm_bal ? *comm_bal : amount_t(0L));
+ }
+ else {
+ diff = amt;
+ }
+
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "XACT assign: diff = " << diff);
+
+ if (! diff.is_realzero()) {
+ if (! xact->amount.is_null()) {
+ xact_t * temp =
+ new xact_t(xact->account, diff,
+ XACT_GENERATED | XACT_CALCULATED);
+ entry->add_xact(temp);
+
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Created balancing transaction");
+ } else {
+ xact->amount = diff;
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Overwrite null transaction");
+ }
+ xdata.value = amt;
+ }
+ }
+ catch (const std::exception& err) {
+ add_error_context("While parsing assigned balance:\n");
+ throw err;
+ }
+ }
+ }
+ }
+ }
+
+ // Parse the optional note
+
+ parse_note:
+ if (in.good() && ! in.eof()) {
+ p = peek_next_nonws(in);
+ if (p == ';') {
+ in.get(p);
+ p = peek_next_nonws(in);
+ xact->note = &line[static_cast<unsigned long>(in.tellg())];
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed a note '" << *xact->note << "'");
+
+ if (char * b = std::strchr(xact->note->c_str(), '['))
+ if (char * e = std::strchr(xact->note->c_str(), ']')) {
+ char buf[256];
+ std::strncpy(buf, b + 1, e - b - 1);
+ buf[e - b - 1] = '\0';
+
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed a transaction date " << buf);
+
+ if (char * p = std::strchr(buf, '=')) {
+ *p++ = '\0';
+ xact->_date_eff = parse_date(p);
+ }
+ if (buf[0])
+ xact->_date = parse_date(buf);
+ }
+ }
+ }
+
+ finished:
+ return xact.release();
+
+ }
+ catch (const std::exception& err) {
+ add_error_context("While parsing transaction:\n");
+ add_error_context(line_context(line, static_cast<unsigned long>(in.tellg()) - 1));
+ throw err;
+ }
+}
+
+bool parse_xacts(std::istream& in,
+ account_t * account,
+ entry_base_t& entry,
+ const string& kind,
+ unsigned long beg_pos)
+{
+ TRACE_START(entry_xacts, 1, "Time spent parsing transactions:");
+
+ static char line[MAX_LINE + 1];
+ bool added = false;
+
+ while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) {
+ in.getline(line, MAX_LINE);
+ if (in.eof())
+ break;
+
+ int len = std::strlen(line);
+ if (line[len - 1] == '\r')
+ line[--len] = '\0';
+
+ beg_pos += len + 1;
+ linenum++;
+
+ if (line[0] == ' ' || line[0] == '\t') {
+ char * p = skip_ws(line);
+ if (! *p)
+ break;
+ }
+ if (xact_t * xact = parse_xact(line, account)) {
+ entry.add_xact(xact);
+ added = true;
+ }
+ }
+
+ TRACE_STOP(entry_xacts, 1);
+
+ return added;
+}
+
+entry_t * parse_entry(std::istream& in, char * line, account_t * master,
+ textual_parser_t& parser, unsigned long& pos)
+{
+ TRACE_START(entry_text, 1, "Time spent preparing entry text:");
+
+ std::auto_ptr<entry_t> curr(new entry_t);
+
+ // Parse the date
+
+ char * next = next_element(line);
+
+ if (char * p = std::strchr(line, '=')) {
+ *p++ = '\0';
+ curr->_date_eff = parse_date(p);
+ }
+ curr->_date = parse_date(line);
+
+ // Parse the optional cleared flag: *
+
+ xact_t::state_t state = xact_t::UNCLEARED;
+ if (next) {
+ switch (*next) {
+ case '*':
+ state = xact_t::CLEARED;
+ next = skip_ws(++next);
+ break;
+ case '!':
+ state = xact_t::PENDING;
+ next = skip_ws(++next);
+ break;
+ }
+ }
+
+ // Parse the optional code: (TEXT)
+
+ if (next && *next == '(') {
+ if (char * p = std::strchr(next++, ')')) {
+ *p++ = '\0';
+ curr->code = next;
+ next = skip_ws(p);
+ }
+ }
+
+ // Parse the description text
+
+ curr->payee = next ? next : "<Unspecified payee>";
+
+ TRACE_STOP(entry_text, 1);
+
+ // Parse all of the xacts associated with this entry
+
+ TRACE_START(entry_details, 1, "Time spent parsing entry details:");
+
+ unsigned long end_pos;
+ unsigned long beg_line = linenum;
+
+ while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) {
+ unsigned long beg_pos = static_cast<unsigned long>(in.tellg());
+
+ line[0] = '\0';
+ in.getline(line, MAX_LINE);
+ if (in.eof() && line[0] == '\0')
+ break;
+
+ int len = std::strlen(line);
+ if (line[len - 1] == '\r')
+ line[--len] = '\0';
+
+ end_pos = beg_pos + len + 1;
+ linenum++;
+
+ if (line[0] == ' ' || line[0] == '\t') {
+ char * p = skip_ws(line);
+ if (! *p)
+ break;
+ }
+
+ if (xact_t * xact = parse_xact(line, master, curr.get())) {
+ if (state != xact_t::UNCLEARED &&
+ xact->state == xact_t::UNCLEARED)
+ xact->state = state;
+
+ xact->beg_pos = beg_pos;
+ xact->beg_line = beg_line;
+ xact->end_pos = end_pos;
+ xact->end_line = linenum;
+ pos = end_pos;
+
+ curr->add_xact(xact);
+ }
+
+ if (in.eof())
+ break;
+ }
+
+ TRACE_STOP(entry_details, 1);
+
+ return curr.release();
+}
+
+static inline 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");
+}
+
+bool textual_parser_t::test(std::istream& in) const
+{
+ char buf[5];
+
+ in.read(buf, 5);
+ if (std::strncmp(buf, "<?xml", 5) == 0) {
+#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
+ throw parse_error("Ledger file contains XML data, but format was not recognized");
+#else
+ throw parse_error("Ledger file contains XML data, but no XML support present");
+#endif
+ }
+
+ in.clear();
+ in.seekg(0, std::ios::beg);
+ assert(in.good());
+ return true;
+}
+
+static void clock_out_from_timelog(std::list<time_entry_t>& time_entries,
+ const datetime_t& when,
+ account_t * account,
+ const char * desc,
+ journal_t& journal)
+{
+ time_entry_t event;
+
+ if (time_entries.size() == 1) {
+ event = time_entries.back();
+ time_entries.clear();
+ }
+ else if (time_entries.empty()) {
+ throw parse_error("Timelog check-out event without a check-in");
+ }
+ else if (! account) {
+ throw parse_error
+ ("When multiple check-ins are active, checking out requires an account");
+ }
+ else {
+ bool found = false;
+
+ for (std::list<time_entry_t>::iterator i = time_entries.begin();
+ i != time_entries.end();
+ i++)
+ if (account == (*i).account) {
+ event = *i;
+ found = true;
+ time_entries.erase(i);
+ break;
+ }
+
+ if (! found)
+ throw parse_error
+ ("Timelog check-out event does not match any current check-ins");
+ }
+
+ if (desc && event.desc.empty()) {
+ event.desc = desc;
+ desc = NULL;
+ }
+
+ std::auto_ptr<entry_t> curr(new entry_t);
+ curr->_date = when.date();
+ curr->code = desc ? desc : "";
+ curr->payee = event.desc;
+
+ if (when < event.checkin)
+ throw parse_error
+ ("Timelog check-out date less than corresponding check-in");
+
+ char buf[32];
+ std::sprintf(buf, "%lds", long((when - event.checkin).seconds()));
+ amount_t amt;
+ amt.parse(buf);
+ assert(amt.valid());
+
+ xact_t * xact
+ = new xact_t(event.account, amt, XACT_VIRTUAL);
+ xact->state = xact_t::CLEARED;
+ curr->add_xact(xact);
+
+ if (! journal.add_entry(curr.get()))
+ throw parse_error("Failed to record 'out' timelog entry");
+ else
+ curr.release();
+}
+
+unsigned int textual_parser_t::parse(std::istream& in,
+ session_t& session,
+ journal_t& journal,
+ account_t * master,
+ const path * original_file)
+{
+ TRACE_START(parsing_total, 1, "Total time spent parsing text:");
+
+ static bool added_auto_entry_hook = false;
+ static char line[MAX_LINE + 1];
+ unsigned int count = 0;
+ unsigned int errors = 0;
+
+ std::list<account_t *> account_stack;
+ auto_entry_finalizer_t auto_entry_finalizer(&journal);
+ std::list<time_entry_t> time_entries;
+
+ if (! master)
+ master = journal.master;
+
+ account_stack.push_front(master);
+
+ pathname = journal.sources.back();
+ src_idx = journal.sources.size() - 1;
+ linenum = 1;
+
+ INFO("Parsing file '" << pathname.string() << "'");
+
+ unsigned long beg_pos = static_cast<unsigned long>(in.tellg());
+ unsigned long end_pos;
+ unsigned long beg_line = linenum;
+
+ while (in.good() && ! in.eof()) {
+ try {
+ in.getline(line, MAX_LINE);
+ if (in.eof())
+ break;
+
+ int len = std::strlen(line);
+ if (line[len - 1] == '\r')
+ line[--len] = '\0';
+
+ end_pos = beg_pos + len + 1;
+ linenum++;
+
+ switch (line[0]) {
+ case '\0':
+ break;
+
+ case ' ':
+ case '\t': {
+ char * p = skip_ws(line);
+ if (*p)
+ throw parse_error("Line begins with whitespace");
+ break;
+ }
+
+#ifdef TIMELOG_SUPPORT
+ case 'i':
+ case 'I': {
+ string date(line, 2, 19);
+
+ char * p = skip_ws(line + 22);
+ char * n = next_element(p, true);
+
+ time_entry_t event(parse_datetime(date),
+ account_stack.front()->find_account(p), n ? n : "");
+
+ if (! time_entries.empty())
+ foreach (time_entry_t& time_entry, time_entries)
+ if (event.account == time_entry.account)
+ throw parse_error("Cannot double check-in to the same account");
+
+ time_entries.push_back(event);
+ break;
+ }
+
+ case 'o':
+ case 'O':
+ if (time_entries.empty()) {
+ throw parse_error("Timelog check-out event without a check-in");
+ } else {
+ string date(line, 2, 19);
+
+ char * p = skip_ws(line + 22);
+ char * n = next_element(p, true);
+
+ clock_out_from_timelog
+ (time_entries, parse_datetime(date),
+ p ? account_stack.front()->find_account(p) : NULL, n, journal);
+ count++;
+ }
+ break;
+#endif // TIMELOG_SUPPORT
+
+ case 'D': { // a default commodity for "entry"
+ amount_t amt(skip_ws(line + 1));
+ assert(amt.valid());
+ amount_t::current_pool->default_commodity = &amt.commodity();
+ break;
+ }
+
+ case 'A': // a default account for unbalanced xacts
+ journal.basket =
+ account_stack.front()->find_account(skip_ws(line + 1));
+ break;
+
+ case 'C': // a set of conversions
+ if (char * p = std::strchr(line + 1, '=')) {
+ *p++ = '\0';
+ // jww (2008-04-22): NYI!
+#if 0
+ parse_conversion(line + 1, p);
+#endif
+ }
+ break;
+
+ case 'P': { // a pricing entry
+ char * date_field_ptr = skip_ws(line + 1);
+ char * time_field_ptr = next_element(date_field_ptr);
+ if (! time_field_ptr) break;
+ 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) break;
+ datetime = parse_datetime(date_field + " " + time_field_ptr);
+ } else {
+ symbol_and_price = time_field_ptr;
+ datetime = parse_datetime(date_field);
+ }
+
+ string symbol;
+ parse_symbol(symbol_and_price, symbol);
+ amount_t price(symbol_and_price);
+ assert(price.valid());
+
+ if (commodity_t * commodity =
+ amount_t::current_pool->find_or_create(symbol))
+ commodity->add_price(datetime, price);
+ break;
+ }
+
+ case 'N': { // don't download prices
+ char * p = skip_ws(line + 1);
+ string symbol;
+ parse_symbol(p, symbol);
+
+ if (commodity_t * commodity =
+ amount_t::current_pool->find_or_create(symbol))
+ commodity->add_flags(COMMODITY_STYLE_NOMARKET);
+ break;
+ }
+
+ case 'Y': // set the current year
+ current_year = std::atoi(skip_ws(line + 1));
+ break;
+
+#ifdef TIMELOG_SUPPORT
+ case 'h':
+ case 'b':
+#endif
+ case '*': // comment line
+ case ';': // comment line
+ break;
+
+ case '-': { // option setting
+ char * p = next_element(line);
+ if (! p) {
+ p = std::strchr(line, '=');
+ if (p)
+ *p++ = '\0';
+ }
+ process_option(line + 2, session, p);
+ break;
+ }
+
+ case '=': { // automated entry
+ if (! added_auto_entry_hook) {
+ journal.add_entry_finalizer(&auto_entry_finalizer);
+ added_auto_entry_hook = true;
+ }
+
+ auto_entry_t * ae = new auto_entry_t(skip_ws(line + 1));
+ if (parse_xacts(in, account_stack.front(), *ae,
+ "automated", end_pos)) {
+ journal.auto_entries.push_back(ae);
+ ae->src_idx = src_idx;
+ ae->beg_pos = beg_pos;
+ ae->beg_line = beg_line;
+ ae->end_pos = end_pos;
+ ae->end_line = linenum;
+ }
+ break;
+ }
+
+ case '~': { // period entry
+ period_entry_t * pe = new period_entry_t(skip_ws(line + 1));
+ if (! pe->period)
+ throw_(parse_error, "Parsing time period '" << line << "'");
+
+ if (parse_xacts(in, account_stack.front(), *pe,
+ "period", end_pos)) {
+ if (pe->finalize()) {
+ extend_entry_base(&journal, *pe, true);
+ journal.period_entries.push_back(pe);
+ pe->src_idx = src_idx;
+ pe->beg_pos = beg_pos;
+ pe->beg_line = beg_line;
+ pe->end_pos = end_pos;
+ pe->end_line = linenum;
+ } else {
+ throw new parse_error("Period entry failed to balance");
+ }
+ }
+ break;
+ }
+
+ case '@':
+ case '!': { // directive
+ char * p = next_element(line);
+ string word(line + 1);
+ if (word == "include") {
+ push_variable<path> save_pathname(pathname);
+ push_variable<unsigned int> save_src_idx(src_idx);
+ push_variable<unsigned long> save_beg_pos(beg_pos);
+ push_variable<unsigned long> save_end_pos(end_pos);
+ push_variable<unsigned int> save_linenum(linenum);
+
+ pathname = p;
+#if 0
+ if (pathname[0] != '/' && pathname[0] != '\\' && pathname[0] != '~') {
+ string::size_type pos = save_pathname.prev.rfind('/');
+ if (pos == string::npos)
+ pos = save_pathname.prev.rfind('\\');
+ if (pos != string::npos)
+ pathname = string(save_pathname.prev, 0, pos + 1) + pathname;
+ }
+ pathname = resolve_path(pathname);
+
+ DEBUG("ledger.textual.include", "line " << linenum << ": " <<
+ "Including path '" << pathname << "'");
+
+ include_stack.push_back(std::pair<path, int>
+ (journal.sources.back(), linenum - 1));
+ count += parse_journal_file(pathname, config, journal,
+ account_stack.front());
+ include_stack.pop_back();
+#endif
+ }
+ else if (word == "account") {
+ account_t * acct;
+ acct = account_stack.front()->find_account(p);
+ account_stack.push_front(acct);
+ }
+ else if (word == "end") {
+ account_stack.pop_front();
+ }
+ else if (word == "alias") {
+ char * b = p;
+ if (char * e = std::strchr(b, '=')) {
+ char * z = e - 1;
+ while (std::isspace(*z))
+ *z-- = '\0';
+ *e++ = '\0';
+ e = skip_ws(e);
+
+ // Once we have an alias name (b) and the target account
+ // name (e), add a reference to the account in the
+ // `account_aliases' map, which is used by the xact
+ // parser to resolve alias references.
+ account_t * acct = account_stack.front()->find_account(e);
+ std::pair<accounts_map::iterator, bool> result
+ = account_aliases.insert(accounts_map::value_type(b, acct));
+ assert(result.second);
+ }
+ }
+ else if (word == "def") {
+ expr_t def(p);
+ def.compile(session); // causes definitions to be established
+ }
+ break;
+ }
+
+ default: {
+ unsigned long pos = beg_pos;
+ TRACE_START(entries, 1, "Time spent handling entries:");
+ if (entry_t * entry =
+ parse_entry(in, line, account_stack.front(), *this, pos)) {
+ if (journal.add_entry(entry)) {
+ entry->src_idx = src_idx;
+ entry->beg_pos = beg_pos;
+ entry->beg_line = beg_line;
+ entry->end_pos = pos;
+ entry->end_line = linenum;
+ count++;
+ } else {
+ checked_delete(entry);
+ throw parse_error("Entry does not balance");
+ }
+ } else {
+ throw parse_error("Failed to parse entry");
+ }
+ end_pos = pos;
+ TRACE_STOP(entries, 1);
+ break;
+ }
+ }
+ }
+ catch (const std::exception& err) {
+ for (std::list<std::pair<path, int> >::reverse_iterator i =
+ include_stack.rbegin();
+ i != include_stack.rend();
+ i++) {
+ add_error_context("In file included from ");
+#if 0
+ add_error_context(include_context((*i).first, (*i).second));
+#endif
+ }
+ add_error_context(file_context(pathname, linenum - 1));
+
+ std::cout.flush();
+ std::cerr << "Error: " << error_context() << err.what()
+ << std::endl;
+ errors++;
+ }
+ beg_pos = end_pos;
+ }
+
+ if (! time_entries.empty()) {
+ std::list<account_t *> accounts;
+
+ foreach (time_entry_t& time_entry, time_entries)
+ accounts.push_back(time_entry.account);
+
+ foreach (account_t * account, accounts)
+ clock_out_from_timelog(time_entries, current_time, account, NULL,
+ journal);
+
+ assert(time_entries.empty());
+ }
+
+ if (added_auto_entry_hook)
+ journal.remove_entry_finalizer(&auto_entry_finalizer);
+
+ if (errors > 0)
+ throw static_cast<int>(errors);
+
+ TRACE_STOP(parsing_total, 1);
+
+ return count;
+}
+
+void write_textual_journal(journal_t& journal,
+ const path& pathname,
+ xact_handler_ptr formatter,
+ const string& write_hdr_format,
+ std::ostream& out)
+{
+ unsigned long index = 0;
+ path found;
+
+ if (pathname.empty()) {
+ if (! journal.sources.empty())
+ found = *journal.sources.begin();
+ } else {
+#ifdef HAVE_REALPATH
+ char buf1[PATH_MAX];
+ char buf2[PATH_MAX];
+
+ ::realpath(pathname.string().c_str(), buf1);
+
+ foreach (const path& path, journal.sources) {
+ ::realpath(path.string().c_str(), buf2);
+ if (std::strcmp(buf1, buf2) == 0) {
+ found = path;
+ break;
+ }
+ index++;
+ }
+#else
+ foreach (const path& path, journal.sources) {
+ if (pathname == path) {
+ found = path;
+ break;
+ }
+ index++;
+ }
+#endif
+ }
+
+ if (found.empty())
+ throw_(std::runtime_error,
+ "Journal does not refer to file '" << pathname << "'");
+
+ entries_list::iterator el = journal.entries.begin();
+ auto_entries_list::iterator al = journal.auto_entries.begin();
+ period_entries_list::iterator pl = journal.period_entries.begin();
+
+ unsigned long pos = 0;
+
+ format_t hdr_fmt(write_hdr_format);
+ boost::filesystem::ifstream in(found);
+
+ while (! in.eof()) {
+ entry_base_t * base = NULL;
+ if (el != journal.entries.end() && pos == (*el)->beg_pos) {
+ hdr_fmt.format(out, **el);
+ base = *el++;
+ }
+ else if (al != journal.auto_entries.end() && pos == (*al)->beg_pos) {
+ out << "= " << (*al)->predicate.predicate.text() << '\n';
+ base = *al++;
+ }
+ else if (pl != journal.period_entries.end() && pos == (*pl)->beg_pos) {
+ out << "~ " << (*pl)->period_string << '\n';
+ base = *pl++;
+ }
+
+ char c;
+ if (base) {
+ foreach (xact_t * xact, base->xacts) {
+ if (! xact->has_flags(XACT_AUTO)) {
+ xact->xdata().add_flags(XACT_EXT_TO_DISPLAY);
+ (*formatter)(*xact);
+ }
+ }
+ formatter->flush();
+
+ while (pos < base->end_pos) {
+ in.get(c);
+ pos = static_cast<unsigned long>(in.tellg()); // pos++;
+ }
+ } else {
+ in.get(c);
+ pos = static_cast<unsigned long>(in.tellg()); // pos++;
+ out.put(c);
+ }
+ }
+}
+
+} // namespace ledger
diff --git a/src/textual.h b/src/textual.h
new file mode 100644
index 00000000..8064d0db
--- /dev/null
+++ b/src/textual.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2003-2008, 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 _TEXTUAL_H
+#define _TEXTUAL_H
+
+#include "journal.h"
+#include "handler.h"
+
+namespace ledger {
+
+class textual_parser_t : public journal_t::parser_t
+{
+public:
+ virtual bool test(std::istream& in) const;
+
+ virtual unsigned int parse(std::istream& in,
+ session_t& session,
+ journal_t& journal,
+ account_t * master = NULL,
+ const path * original_file = NULL);
+};
+
+xact_t * parse_xact_text(char * line, account_t * account);
+xact_t * parse_xact(std::istream& in, account_t * account);
+
+void write_textual_journal(journal_t& journal,
+ const path& pathname,
+ xact_handler_ptr& formatter,
+ const string& write_hdr_format,
+ std::ostream& out);
+
+#if 0
+class include_context : public file_context
+{
+ public:
+ include_context(const path& file, unsigned long line,
+ const string& desc = "") throw()
+ : file_context(file, line, desc) {}
+ virtual ~include_context() throw() {}
+
+ virtual void describe(std::ostream& out) const throw() {
+ if (! desc.empty())
+ out << desc << ": ";
+ out << "\"" << file.string() << "\", line " << line << ":"
+ << std::endl;
+ }
+};
+#endif
+
+} // namespace ledger
+
+#endif // _TEXTUAL_H
diff --git a/src/times.cc b/src/times.cc
new file mode 100644
index 00000000..97c0c242
--- /dev/null
+++ b/src/times.cc
@@ -0,0 +1,350 @@
+/*
+ * Copyright (c) 2003-2008, 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 "utils.h" // this brings in times.h
+
+namespace ledger {
+
+namespace {
+#ifdef BOOST_DATE_TIME_HAS_HIGH_PRECISION_CLOCK
+ const ptime time_now = boost::posix_time::microsec_clock::universal_time();
+#else
+ const ptime time_now = boost::posix_time::second_clock::universal_time();
+#endif
+ const date date_now = boost::gregorian::day_clock::universal_day();
+}
+
+const datetime_t& current_time(time_now);
+const date_t& current_date(date_now);
+ int current_year(current_date.year());
+
+namespace {
+ const char * formats[] = {
+ "%y/%m/%d",
+ "%Y/%m/%d",
+ "%m/%d",
+ "%y.%m.%d",
+ "%Y.%m.%d",
+ "%m.%d",
+ "%y-%m-%d",
+ "%Y-%m-%d",
+ "%m-%d",
+ "%a",
+ "%A",
+ "%b",
+ "%B",
+ "%Y",
+ NULL
+ };
+}
+
+optional<string> input_date_format;
+string output_date_format = "%Y/%m/%d";
+
+namespace {
+ bool parse_date_mask(const char * date_str, std::tm& result)
+ {
+ if (input_date_format) {
+ std::memset(&result, -1, sizeof(std::tm));
+ if (strptime(date_str, input_date_format->c_str(), &result))
+ return true;
+ }
+ for (const char ** f = formats; *f; f++) {
+ std::memset(&result, -1, sizeof(std::tm));
+ if (strptime(date_str, *f, &result))
+ return true;
+ }
+ return false;
+ }
+
+ bool parse_date(const char * date_str, std::tm& result, const int year)
+ {
+ if (! parse_date_mask(date_str, result))
+ return false;
+
+ result.tm_hour = 0;
+ result.tm_min = 0;
+ result.tm_sec = 0;
+
+ if (result.tm_year == -1)
+ result.tm_year = ((year == -1) ? current_year : year) - 1900;
+
+ if (result.tm_mon == -1)
+ result.tm_mon = 0;
+
+ if (result.tm_mday == -1)
+ result.tm_mday = 1;
+
+ return true;
+ }
+
+ bool quick_parse_date(const char * date_str, std::tm& result)
+ {
+ return parse_date(date_str, result, current_year);
+ }
+}
+
+datetime_t parse_datetime(const char * str)
+{
+ std::tm when;
+ // jww (2008-08-01): This needs to look for HH:MM:SS as well.
+ quick_parse_date(str, when);
+ return posix_time::ptime_from_tm(when);
+}
+
+date_t parse_date(const char * str)
+{
+ std::tm when;
+ quick_parse_date(str, when);
+ return gregorian::date_from_tm(when);
+}
+
+date_t interval_t::first(const optional<date_t>& moment) const
+{
+ if (! is_valid(begin))
+ throw_(date_error,
+ "Use of interval_t::first() with specifying a range start");
+
+ date_t quant(begin);
+
+ if (! advanced)
+ advanced = true;
+
+ if (moment && *moment > quant) {
+ // Find an efficient starting point for the upcoming while loop.
+ // We want a date early enough that the range will be correct, but
+ // late enough that we don't spend hundreds of thousands of loops
+ // skipping through time.
+
+ date_t quant(moment->year(), gregorian::Jan, 1);
+ date_t temp;
+ while (*moment >= (temp = increment(quant))) {
+ if (quant == temp)
+ break;
+ quant = temp;
+ }
+ }
+ return quant;
+}
+
+date_t interval_t::increment(const date_t& moment) const
+{
+ date_t future(moment);
+
+ if (years) future += gregorian::years(years);
+ if (months) future += gregorian::months(months);
+ if (days) future += gregorian::days(days);
+
+ return future;
+}
+
+namespace {
+ void parse_inclusion_specifier(const string& word,
+ date_t * begin, date_t * end)
+ {
+ struct std::tm when;
+
+ if (! parse_date_mask(word.c_str(), when))
+ throw_(date_error, "Could not parse date mask: " << word);
+
+ when.tm_hour = 0;
+ when.tm_min = 0;
+ when.tm_sec = 0;
+ when.tm_isdst = -1;
+
+ bool saw_year = true;
+ bool saw_mon = true;
+ bool saw_day = true;
+
+ if (when.tm_year == -1) {
+ when.tm_year = current_year - 1900;
+ saw_year = false;
+ }
+ if (when.tm_mon == -1) {
+ when.tm_mon = 0;
+ saw_mon = false;
+ } else {
+ saw_year = false; // don't increment by year if month used
+ }
+ if (when.tm_mday == -1) {
+ when.tm_mday = 1;
+ saw_day = false;
+ } else {
+ saw_mon = false; // don't increment by month if day used
+ saw_year = false; // don't increment by year if day used
+ }
+
+ if (begin) {
+ *begin = gregorian::date_from_tm(when);
+ if (end)
+ *end = interval_t(saw_day ? 1 : 0, saw_mon ? 1 : 0,
+ saw_year ? 1 : 0).increment(*begin);
+ }
+ else if (end) {
+ *end = gregorian::date_from_tm(when);
+ }
+ }
+
+ inline void read_lower_word(std::istream& in, string& word) {
+ in >> word;
+ for (int i = 0, l = word.length(); i < l; i++)
+ word[i] = std::tolower(word[i]);
+ }
+
+ void parse_date_words(std::istream& in, string& word,
+ date_t * begin, date_t * end)
+ {
+ string type;
+
+ bool mon_spec = false;
+ char buf[32];
+
+ if (word == "this" || word == "last" || word == "next") {
+ type = word;
+ if (! in.eof())
+ read_lower_word(in, word);
+ else
+ word = "month";
+ } else {
+ type = "this";
+ }
+
+ if (word == "month") {
+ time_t now = to_time_t(current_time);
+ std::strftime(buf, 31, "%B", localtime(&now));
+ word = buf;
+ mon_spec = true;
+ }
+ else if (word == "year") {
+ std::sprintf(buf, "%04d", current_year);
+ word = buf;
+ }
+
+ parse_inclusion_specifier(word, begin, end);
+
+ if (type == "last") {
+ if (mon_spec) {
+ if (begin)
+ *begin = interval_t(0, -1, 0).increment(*begin);
+ if (end)
+ *end = interval_t(0, -1, 0).increment(*end);
+ } else {
+ if (begin)
+ *begin = interval_t(0, 0, -1).increment(*begin);
+ if (end)
+ *end = interval_t(0, 0, -1).increment(*end);
+ }
+ }
+ else if (type == "next") {
+ if (mon_spec) {
+ if (begin)
+ *begin = interval_t(0, 1, 0).increment(*begin);
+ if (end)
+ *end = interval_t(0, 1, 0).increment(*end);
+ } else {
+ if (begin)
+ *begin = interval_t(0, 0, 1).increment(*begin);
+ if (end)
+ *end = interval_t(0, 0, 1).increment(*end);
+ }
+ }
+ }
+}
+
+void interval_t::parse(std::istream& in)
+{
+ string word;
+
+ while (! in.eof()) {
+ read_lower_word(in, word);
+ if (word == "every") {
+ read_lower_word(in, word);
+ if (std::isdigit(word[0])) {
+ int quantity = std::atol(word.c_str());
+ read_lower_word(in, word);
+ if (word == "days")
+ days = quantity;
+ else if (word == "weeks")
+ days = 7 * quantity;
+ else if (word == "months")
+ months = quantity;
+ else if (word == "quarters")
+ months = 3 * quantity;
+ else if (word == "years")
+ years = quantity;
+ }
+ else if (word == "day")
+ days = 1;
+ else if (word == "week")
+ days = 7;
+ else if (word == "month")
+ months = 1;
+ else if (word == "quarter")
+ months = 3;
+ else if (word == "year")
+ years = 1;
+ }
+ else if (word == "daily")
+ days = 1;
+ else if (word == "weekly")
+ days = 7;
+ else if (word == "biweekly")
+ days = 14;
+ else if (word == "monthly")
+ months = 1;
+ else if (word == "bimonthly")
+ months = 2;
+ else if (word == "quarterly")
+ months = 3;
+ else if (word == "yearly")
+ years = 1;
+ else if (word == "this" || word == "last" || word == "next") {
+ parse_date_words(in, word, &begin, &end);
+ }
+ else if (word == "in") {
+ read_lower_word(in, word);
+ parse_date_words(in, word, &begin, &end);
+ }
+ else if (word == "from" || word == "since") {
+ read_lower_word(in, word);
+ parse_date_words(in, word, &begin, NULL);
+ }
+ else if (word == "to" || word == "until") {
+ read_lower_word(in, word);
+ parse_date_words(in, word, NULL, &end);
+ }
+ else {
+ parse_inclusion_specifier(word, &begin, &end);
+ }
+ }
+}
+
+} // namespace ledger
diff --git a/src/times.h b/src/times.h
new file mode 100644
index 00000000..306477f5
--- /dev/null
+++ b/src/times.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2003-2008, 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 _TIMES_H
+#define _TIMES_H
+
+namespace ledger {
+
+DECLARE_EXCEPTION(datetime_error, std::runtime_error);
+DECLARE_EXCEPTION(date_error, std::runtime_error);
+
+typedef boost::posix_time::ptime datetime_t;
+typedef datetime_t::time_duration_type time_duration_t;
+
+inline bool is_valid(const datetime_t& moment) {
+ return ! moment.is_not_a_date_time();
+}
+
+typedef boost::gregorian::date date_t;
+typedef boost::gregorian::date_duration date_duration_t;
+
+inline bool is_valid(const date_t& moment) {
+ return ! moment.is_not_a_date();
+}
+
+extern const datetime_t& current_time;
+extern const date_t& current_date;
+extern int current_year;
+extern optional<string> input_date_format;
+extern string output_date_format;
+
+inline datetime_t parse_datetime(const string& str) {
+ return parse_datetime(str.c_str());
+}
+datetime_t parse_datetime(const char * str);
+
+inline date_t parse_date(const string& str) {
+ return parse_date(str.c_str());
+}
+date_t parse_date(const char * str);
+
+inline std::time_t to_time_t(const ptime& t)
+{
+ if( t == posix_time::neg_infin )
+ return 0;
+ else if( t == posix_time::pos_infin )
+ return LONG_MAX;
+ ptime start(date(1970,1,1));
+ return (t-start).total_seconds();
+}
+
+inline string format_datetime(const datetime_t& when)
+{
+ char buf[256];
+ time_t moment = to_time_t(when);
+ std::strftime(buf, 255, (output_date_format + " %H:%M:%S").c_str(),
+ std::localtime(&moment));
+ return buf;
+}
+
+inline string format_date(const date_t& when,
+ const optional<string>& format = none)
+{
+ if (format) {
+ char buf[256];
+ std::tm moment = gregorian::to_tm(when);
+ std::strftime(buf, 255, format->c_str(), &moment);
+ return buf;
+ } else {
+ return to_iso_extended_string(when);
+ }
+}
+
+struct interval_t
+{
+ int years;
+ int months;
+ int days;
+ date_t begin;
+ date_t end;
+
+ mutable bool advanced;
+
+ interval_t(int _days = 0, int _months = 0, int _years = 0,
+ const date_t& _begin = date_t(),
+ const date_t& _end = date_t())
+ : years(_years), months(_months), days(_days),
+ begin(_begin), end(_end), advanced(false) {
+ TRACE_CTOR(interval_t, "int, int, int, const date_t&, const date_t&");
+ }
+ interval_t(const interval_t& other)
+ : years(other.years),
+ months(other.months),
+ days(other.days),
+ begin(other.begin),
+ end(other.end),
+ advanced(other.advanced) {
+ TRACE_CTOR(interval_t, "copy");
+ }
+ interval_t(const string& desc)
+ : years(0), months(0), days(0), begin(), end(), advanced(false) {
+ TRACE_CTOR(interval_t, "const string&");
+ std::istringstream stream(desc);
+ parse(stream);
+ }
+
+ ~interval_t() throw() {
+ TRACE_DTOR(interval_t);
+ }
+
+ operator bool() const {
+ return years != 0 || months != 0 || days != 0;
+ }
+
+ void start(const date_t& moment) {
+ begin = first(moment);
+ }
+ date_t first(const optional<date_t>& moment = none) const;
+ date_t increment(const date_t&) const;
+
+ void parse(std::istream& in);
+};
+
+} // namespace ledger
+
+#endif // _TIMES_H
diff --git a/src/token.cc b/src/token.cc
new file mode 100644
index 00000000..d1d70a38
--- /dev/null
+++ b/src/token.cc
@@ -0,0 +1,378 @@
+/*
+ * Copyright (c) 2003-2008, 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 "token.h"
+#include "parser.h"
+
+namespace ledger {
+
+void expr_t::token_t::parse_ident(std::istream& in)
+{
+ if (in.eof()) {
+ kind = TOK_EOF;
+ return;
+ }
+ assert(in.good());
+
+ char c = peek_next_nonws(in);
+
+ if (in.eof()) {
+ kind = TOK_EOF;
+ return;
+ }
+ assert(in.good());
+
+ kind = IDENT;
+ length = 0;
+
+ char buf[256];
+ READ_INTO_(in, buf, 255, c, length,
+ std::isalnum(c) || c == '_' || c == '.' || c == '-');
+
+ switch (buf[0]) {
+#if 0
+ case 'a':
+ if (std::strcmp(buf, "and") == 0)
+ kind = KW_AND;
+ break;
+ case 'd':
+ if (std::strcmp(buf, "div") == 0)
+ kind = KW_DIV;
+ break;
+ case 'e':
+ if (std::strcmp(buf, "eq") == 0)
+ kind = EQUAL;
+ break;
+#endif
+ case 'f':
+ if (std::strcmp(buf, "false") == 0) {
+ kind = VALUE;
+ value = false;
+ }
+ break;
+#if 0
+ case 'g':
+ if (std::strcmp(buf, "gt") == 0)
+ kind = GREATER;
+ else if (std::strcmp(buf, "ge") == 0)
+ kind = GREATEREQ;
+ break;
+ case 'i':
+ if (std::strcmp(buf, "is") == 0)
+ kind = EQUAL;
+ break;
+ case 'l':
+ if (std::strcmp(buf, "lt") == 0)
+ kind = LESS;
+ else if (std::strcmp(buf, "le") == 0)
+ kind = LESSEQ;
+ break;
+ case 'm':
+ if (std::strcmp(buf, "mod") == 0)
+ kind = KW_MOD;
+ break;
+ case 'n':
+ if (std::strcmp(buf, "ne") == 0)
+ kind = NEQUAL;
+ break;
+ case 'o':
+ if (std::strcmp(buf, "or") == 0)
+ kind = KW_OR;
+ break;
+#endif
+ case 't':
+ if (std::strcmp(buf, "true") == 0) {
+ kind = VALUE;
+ value = true;
+ }
+ break;
+ }
+
+ if (kind == IDENT)
+ value.set_string(buf);
+}
+
+void expr_t::token_t::next(std::istream& in, const uint_least8_t pflags)
+{
+ if (in.eof()) {
+ kind = TOK_EOF;
+ return;
+ }
+ assert(in.good());
+
+ char c = peek_next_nonws(in);
+
+ if (in.eof()) {
+ kind = TOK_EOF;
+ return;
+ }
+ assert(in.good());
+
+ symbol[0] = c;
+ symbol[1] = '\0';
+
+ length = 1;
+
+ switch (c) {
+ case '&':
+ in.get(c);
+ kind = KW_AND;
+ break;
+ case '|':
+ in.get(c);
+ kind = KW_OR;
+ break;
+
+ case '(':
+ in.get(c);
+ kind = LPAREN;
+ break;
+ case ')':
+ in.get(c);
+ kind = RPAREN;
+ break;
+
+ case '[': {
+ in.get(c);
+
+ char buf[256];
+ READ_INTO_(in, buf, 255, c, length, c != ']');
+ if (c != ']')
+ expected(']', c);
+
+ in.get(c);
+ length++;
+
+ interval_t timespan(buf);
+ kind = VALUE;
+ value = timespan.first();
+ break;
+ }
+
+ case '\'':
+ case '"': {
+ char delim;
+ in.get(delim);
+ char buf[4096];
+ READ_INTO_(in, buf, 4095, c, length, c != delim);
+ if (c != delim)
+ expected(delim, c);
+ in.get(c);
+ length++;
+ kind = VALUE;
+ value.set_string(buf);
+ break;
+ }
+
+ case '{': {
+ in.get(c);
+ amount_t temp;
+ temp.parse(in, AMOUNT_PARSE_NO_MIGRATE);
+ in.get(c);
+ if (c != '}')
+ expected('}', c);
+ length++;
+ kind = VALUE;
+ value = temp;
+ break;
+ }
+
+ case '!':
+ in.get(c);
+ c = in.peek();
+ if (c == '=') {
+ in.get(c);
+ symbol[1] = c;
+ symbol[2] = '\0';
+ kind = NEQUAL;
+ length = 2;
+ break;
+ }
+ kind = EXCLAM;
+ break;
+
+ case '-':
+ in.get(c);
+ kind = MINUS;
+ break;
+ case '+':
+ in.get(c);
+ kind = PLUS;
+ break;
+
+ case '*':
+ in.get(c);
+ kind = STAR;
+ break;
+
+ case '?':
+ in.get(c);
+ kind = QUERY;
+ break;
+
+ case ':':
+ in.get(c);
+ kind = COLON;
+ break;
+
+ case '/': {
+ in.get(c);
+
+ // Read in the regexp
+ char buf[256];
+ READ_INTO_(in, buf, 255, c, length, c != '/');
+ if (c != '/')
+ expected('/', c);
+ in.get(c);
+ length++;
+
+ kind = MASK;
+ value.set_string(buf);
+ break;
+ }
+
+ case '=':
+ in.get(c);
+ c = in.peek();
+ if (c == '~') {
+ in.get(c);
+ symbol[1] = c;
+ symbol[2] = '\0';
+ kind = MATCH;
+ length = 2;
+ break;
+ }
+ kind = EQUAL;
+ break;
+
+ case '<':
+ in.get(c);
+ if (in.peek() == '=') {
+ in.get(c);
+ symbol[1] = c;
+ symbol[2] = '\0';
+ kind = LESSEQ;
+ length = 2;
+ break;
+ }
+ kind = LESS;
+ break;
+
+ case '>':
+ in.get(c);
+ if (in.peek() == '=') {
+ in.get(c);
+ symbol[1] = c;
+ symbol[2] = '\0';
+ kind = GREATEREQ;
+ length = 2;
+ break;
+ }
+ kind = GREATER;
+ break;
+
+ case ',':
+ in.get(c);
+ kind = COMMA;
+ break;
+
+ default: {
+ amount_t temp;
+ unsigned long pos = 0;
+
+ // When in relaxed parsing mode, we want to migrate commodity
+ // flags so that any precision specified by the user updates the
+ // current maximum displayed precision.
+ pos = static_cast<unsigned long>(in.tellg());
+
+ amount_t::flags_t parse_flags = 0;
+ if (pflags & EXPR_PARSE_NO_MIGRATE)
+ parse_flags |= AMOUNT_PARSE_NO_MIGRATE;
+ if (pflags & EXPR_PARSE_NO_REDUCE)
+ parse_flags |= AMOUNT_PARSE_NO_REDUCE;
+
+ if (! temp.parse(in, parse_flags | AMOUNT_PARSE_SOFT_FAIL)) {
+ // If the amount had no commodity, it must be an unambiguous
+ // variable reference
+
+ in.clear();
+ in.seekg(pos, std::ios::beg);
+
+ c = in.peek();
+ assert(! (std::isdigit(c) || c == '.'));
+ parse_ident(in);
+ } else {
+ kind = VALUE;
+ value = temp;
+ }
+ break;
+ }
+ }
+}
+
+void expr_t::token_t::rewind(std::istream& in)
+{
+ for (unsigned int i = 0; i < length; i++)
+ in.unget();
+}
+
+
+void expr_t::token_t::unexpected()
+{
+ switch (kind) {
+ case TOK_EOF:
+ throw_(parse_error, "Unexpected end of expression");
+ case IDENT:
+ throw_(parse_error, "Unexpected symbol '" << value << "'");
+ case VALUE:
+ throw_(parse_error, "Unexpected value '" << value << "'");
+ default:
+ throw_(parse_error, "Unexpected operator '" << symbol << "'");
+ }
+}
+
+void expr_t::token_t::expected(char wanted, char c)
+{
+ if (c == '\0') {
+ if (wanted)
+ throw_(parse_error, "Missing '" << wanted << "'");
+ else
+ throw_(parse_error, "Unexpected end");
+ } else {
+ if (wanted)
+ throw_(parse_error, "Invalid char '" << c
+ << "' (wanted '" << wanted << "')");
+ else
+ throw_(parse_error, "Invalid char '" << c << "'");
+ }
+}
+
+} // namespace ledger
diff --git a/src/token.h b/src/token.h
new file mode 100644
index 00000000..04ca39c1
--- /dev/null
+++ b/src/token.h
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2003-2008, 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 _TOKEN_H
+#define _TOKEN_H
+
+#include "expr.h"
+
+namespace ledger {
+
+struct expr_t::token_t : public noncopyable
+{
+ enum kind_t {
+ VALUE, // any kind of literal value
+ IDENT, // [A-Za-z_][-A-Za-z0-9_:]*
+ MASK, // /regexp/
+
+ LPAREN, // (
+ RPAREN, // )
+
+ EQUAL, // ==
+ NEQUAL, // !=
+ LESS, // <
+ LESSEQ, // <=
+ GREATER, // >
+ GREATEREQ, // >=
+
+ ASSIGN, // =
+ MATCH, // =~
+ MINUS, // -
+ PLUS, // +
+ STAR, // *
+ KW_DIV, // /
+
+ EXCLAM, // !
+ KW_AND, // &
+ KW_OR, // |
+ KW_MOD, // %
+
+ QUERY, // ?
+ COLON, // :
+
+ COMMA, // ,
+
+ TOK_EOF,
+ UNKNOWN
+
+ } kind;
+
+ char symbol[3];
+ value_t value;
+ std::size_t length;
+
+ explicit token_t() : kind(UNKNOWN), length(0) {
+ TRACE_CTOR(token_t, "");
+ }
+ ~token_t() throw() {
+ TRACE_DTOR(token_t);
+ }
+
+ token_t& operator=(const token_t& other) {
+ if (&other == this)
+ return *this;
+ assert(false);
+ return *this;
+ }
+
+ void clear() {
+ kind = UNKNOWN;
+ length = 0;
+ value = NULL_VALUE;
+
+ symbol[0] = '\0';
+ symbol[1] = '\0';
+ symbol[2] = '\0';
+ }
+
+ void parse_ident(std::istream& in);
+ void next(std::istream& in, const uint_least8_t flags);
+ void rewind(std::istream& in);
+ void unexpected();
+
+ static void expected(char wanted, char c = '\0');
+};
+
+} // namespace ledger
+
+#endif // _TOKEN_H
diff --git a/src/utils.cc b/src/utils.cc
new file mode 100644
index 00000000..5ac5cef0
--- /dev/null
+++ b/src/utils.cc
@@ -0,0 +1,716 @@
+/*
+ * Copyright (c) 2003-2008, 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 "utils.h"
+
+/**********************************************************************
+ *
+ * Assertions
+ */
+
+#if defined(ASSERTS_ON)
+
+namespace ledger {
+
+DECLARE_EXCEPTION(assertion_failed, std::logic_error);
+
+void debug_assert(const string& reason,
+ const string& func,
+ const string& file,
+ unsigned long line)
+{
+ std::ostringstream buf;
+ buf << "Assertion failed in \"" << file << "\", line " << line
+ << ": " << func << ": " << reason;
+ throw assertion_failed(buf.str());
+}
+
+} // namespace ledger
+
+#endif
+
+/**********************************************************************
+ *
+ * Verification (basically, very slow asserts)
+ */
+
+#if defined(VERIFY_ON)
+
+namespace ledger {
+
+bool verify_enabled = false;
+
+typedef std::pair<std::string, std::size_t> allocation_pair;
+typedef std::map<void *, allocation_pair> live_memory_map;
+typedef std::multimap<void *, allocation_pair> live_objects_map;
+
+typedef std::pair<unsigned int, std::size_t> count_size_pair;
+typedef std::map<std::string, count_size_pair> object_count_map;
+
+static live_memory_map * live_memory = NULL;
+static object_count_map * live_memory_count = NULL;
+static object_count_map * total_memory_count = NULL;
+
+static bool memory_tracing_active = false;
+
+static live_objects_map * live_objects = NULL;
+static object_count_map * live_object_count = NULL;
+static object_count_map * total_object_count = NULL;
+static object_count_map * total_ctor_count = NULL;
+
+void initialize_memory_tracing()
+{
+ memory_tracing_active = false;
+
+ live_memory = new live_memory_map;
+ live_memory_count = new object_count_map;
+ total_memory_count = new object_count_map;
+
+ live_objects = new live_objects_map;
+ live_object_count = new object_count_map;
+ total_object_count = new object_count_map;
+ total_ctor_count = new object_count_map;
+
+ memory_tracing_active = true;
+}
+
+void shutdown_memory_tracing()
+{
+ memory_tracing_active = false;
+
+ if (live_objects) {
+ IF_DEBUG("memory.counts")
+ report_memory(std::cerr, true);
+ else IF_DEBUG("memory.counts.live")
+ report_memory(std::cerr);
+ else if (live_objects->size() > 0)
+ report_memory(std::cerr);
+ }
+
+ checked_delete(live_memory); live_memory = NULL;
+ checked_delete(live_memory_count); live_memory_count = NULL;
+ checked_delete(total_memory_count); total_memory_count = NULL;
+
+ checked_delete(live_objects); live_objects = NULL;
+ checked_delete(live_object_count); live_object_count = NULL;
+ checked_delete(total_object_count); total_object_count = NULL;
+ checked_delete(total_ctor_count); total_ctor_count = NULL;
+}
+
+inline void add_to_count_map(object_count_map& the_map,
+ const char * name, std::size_t size)
+{
+ object_count_map::iterator k = the_map.find(name);
+ if (k != the_map.end()) {
+ (*k).second.first++;
+ (*k).second.second += size;
+ } else {
+ std::pair<object_count_map::iterator, bool> result =
+ the_map.insert(object_count_map::value_type(name, count_size_pair(1, size)));
+ VERIFY(result.second);
+ }
+}
+
+std::size_t current_memory_size()
+{
+ std::size_t memory_size = 0;
+
+ foreach (const object_count_map::value_type& pair, *live_memory_count)
+ memory_size += pair.second.second;
+
+ return memory_size;
+}
+
+static void trace_new_func(void * ptr, const char * which, std::size_t size)
+{
+ memory_tracing_active = false;
+
+ if (! live_memory) return;
+
+ live_memory->insert
+ (live_memory_map::value_type(ptr, allocation_pair(which, size)));
+
+ add_to_count_map(*live_memory_count, which, size);
+ add_to_count_map(*total_memory_count, which, size);
+ add_to_count_map(*total_memory_count, "__ALL__", size);
+
+ memory_tracing_active = true;
+}
+
+static void trace_delete_func(void * ptr, const char * which)
+{
+ memory_tracing_active = false;
+
+ if (! live_memory) return;
+
+ // Ignore deletions of memory not tracked, since it's possible that
+ // a user (like boost) allocated a block of memory before memory
+ // tracking began, and then deleted it before memory tracking ended.
+ // If it really is a double-delete, the malloc library on OS/X will
+ // notify me.
+
+ live_memory_map::iterator i = live_memory->find(ptr);
+ if (i == live_memory->end())
+ return;
+
+ std::size_t size = (*i).second.second;
+ VERIFY((*i).second.first == which);
+
+ live_memory->erase(i);
+
+ object_count_map::iterator j = live_memory_count->find(which);
+ VERIFY(j != live_memory_count->end());
+
+ (*j).second.second -= size;
+ if (--(*j).second.first == 0)
+ live_memory_count->erase(j);
+
+ memory_tracing_active = true;
+}
+
+} // namespace ledger
+
+void * operator new(std::size_t size) throw (std::bad_alloc) {
+ void * ptr = std::malloc(size);
+ if (DO_VERIFY() && ledger::memory_tracing_active)
+ ledger::trace_new_func(ptr, "new", size);
+ return ptr;
+}
+void * operator new(std::size_t size, const std::nothrow_t&) throw() {
+ void * ptr = std::malloc(size);
+ if (DO_VERIFY() && ledger::memory_tracing_active)
+ ledger::trace_new_func(ptr, "new", size);
+ return ptr;
+}
+void * operator new[](std::size_t size) throw (std::bad_alloc) {
+ void * ptr = std::malloc(size);
+ if (DO_VERIFY() && ledger::memory_tracing_active)
+ ledger::trace_new_func(ptr, "new[]", size);
+ return ptr;
+}
+void * operator new[](std::size_t size, const std::nothrow_t&) throw() {
+ void * ptr = std::malloc(size);
+ if (DO_VERIFY() && ledger::memory_tracing_active)
+ ledger::trace_new_func(ptr, "new[]", size);
+ return ptr;
+}
+void operator delete(void * ptr) throw() {
+ if (DO_VERIFY() && ledger::memory_tracing_active)
+ ledger::trace_delete_func(ptr, "new");
+ std::free(ptr);
+}
+void operator delete(void * ptr, const std::nothrow_t&) throw() {
+ if (DO_VERIFY() && ledger::memory_tracing_active)
+ ledger::trace_delete_func(ptr, "new");
+ std::free(ptr);
+}
+void operator delete[](void * ptr) throw() {
+ if (DO_VERIFY() && ledger::memory_tracing_active)
+ ledger::trace_delete_func(ptr, "new[]");
+ std::free(ptr);
+}
+void operator delete[](void * ptr, const std::nothrow_t&) throw() {
+ if (DO_VERIFY() && ledger::memory_tracing_active)
+ ledger::trace_delete_func(ptr, "new[]");
+ std::free(ptr);
+}
+
+namespace ledger {
+
+inline void report_count_map(std::ostream& out, object_count_map& the_map)
+{
+ foreach (object_count_map::value_type& pair, the_map)
+ out << " " << std::right << std::setw(12) << pair.second.first
+ << " " << std::right << std::setw(7) << pair.second.second
+ << " " << std::left << pair.first
+ << std::endl;
+}
+
+std::size_t current_objects_size()
+{
+ std::size_t objects_size = 0;
+
+ foreach (const object_count_map::value_type& pair, *live_object_count)
+ objects_size += pair.second.second;
+
+ return objects_size;
+}
+
+void trace_ctor_func(void * ptr, const char * cls_name, const char * args,
+ std::size_t cls_size)
+{
+ memory_tracing_active = false;
+
+ if (! live_objects) return;
+
+ static char name[1024];
+ std::strcpy(name, cls_name);
+ std::strcat(name, "(");
+ std::strcat(name, args);
+ std::strcat(name, ")");
+
+ DEBUG("memory.debug", "TRACE_CTOR " << ptr << " " << name);
+
+ live_objects->insert
+ (live_objects_map::value_type(ptr, allocation_pair(cls_name, cls_size)));
+
+ add_to_count_map(*live_object_count, cls_name, cls_size);
+ add_to_count_map(*total_object_count, cls_name, cls_size);
+ add_to_count_map(*total_object_count, "__ALL__", cls_size);
+ add_to_count_map(*total_ctor_count, name, cls_size);
+
+ memory_tracing_active = true;
+}
+
+void trace_dtor_func(void * ptr, const char * cls_name, std::size_t cls_size)
+{
+ memory_tracing_active = false;
+
+ if (! live_objects) return;
+
+ DEBUG("memory.debug", "TRACE_DTOR " << ptr << " " << cls_name);
+
+ live_objects_map::iterator i = live_objects->find(ptr);
+ if (i == live_objects->end()) {
+ std::cerr << "Attempting to delete " << ptr << " a non-living " << cls_name
+ << std::endl;
+ assert(false);
+ }
+
+ int ptr_count = live_objects->count(ptr);
+ for (int x = 0; x < ptr_count; x++, i++) {
+ if ((*i).second.first == cls_name) {
+ live_objects->erase(i);
+ break;
+ }
+ }
+
+ object_count_map::iterator k = live_object_count->find(cls_name);
+ VERIFY(k != live_object_count->end());
+
+ (*k).second.second -= cls_size;
+ if (--(*k).second.first == 0)
+ live_object_count->erase(k);
+
+ memory_tracing_active = true;
+}
+
+void report_memory(std::ostream& out, bool report_all)
+{
+ if (! live_memory) return;
+
+ if (live_memory_count->size() > 0) {
+ out << "NOTE: There may be memory held by Boost "
+ << "and libstdc++ after ledger::shutdown()" << std::endl;
+ out << "Live memory count:" << std::endl;
+ report_count_map(out, *live_memory_count);
+ }
+
+ if (live_memory->size() > 0) {
+ out << "Live memory:" << std::endl;
+
+ foreach (const live_memory_map::value_type& pair, *live_memory)
+ out << " " << std::right << std::setw(12) << pair.first
+ << " " << std::right << std::setw(7) << pair.second.second
+ << " " << std::left << pair.second.first
+ << std::endl;
+ }
+
+ if (report_all && total_memory_count->size() > 0) {
+ out << "Total memory counts:" << std::endl;
+ report_count_map(out, *total_memory_count);
+ }
+
+ if (live_object_count->size() > 0) {
+ out << "Live object count:" << std::endl;
+ report_count_map(out, *live_object_count);
+ }
+
+ if (live_objects->size() > 0) {
+ out << "Live objects:" << std::endl;
+
+ foreach (const live_objects_map::value_type& pair, *live_objects)
+ out << " " << std::right << std::setw(12) << pair.first
+ << " " << std::right << std::setw(7) << pair.second.second
+ << " " << std::left << pair.second.first
+ << std::endl;
+ }
+
+ if (report_all) {
+ if (total_object_count->size() > 0) {
+ out << "Total object counts:" << std::endl;
+ report_count_map(out, *total_object_count);
+ }
+
+ if (total_ctor_count->size() > 0) {
+ out << "Total constructor counts:" << std::endl;
+ report_count_map(out, *total_ctor_count);
+ }
+ }
+}
+
+
+string::string() : std::string() {
+ TRACE_CTOR(string, "");
+}
+string::string(const string& str) : std::string(str) {
+ TRACE_CTOR(string, "copy");
+}
+string::string(const std::string& str) : std::string(str) {
+ TRACE_CTOR(string, "const std::string&");
+}
+string::string(const int len, char x) : std::string(len, x) {
+ TRACE_CTOR(string, "const int, char");
+}
+string::string(const char * str) : std::string(str) {
+ TRACE_CTOR(string, "const char *");
+}
+string::string(const char * str, const char * end) : std::string(str, end) {
+ TRACE_CTOR(string, "const char *, const char *");
+}
+string::string(const string& str, int x) : std::string(str, x) {
+ TRACE_CTOR(string, "const string&, int");
+}
+string::string(const string& str, int x, int y) : std::string(str, x, y) {
+ TRACE_CTOR(string, "const string&, int, int");
+}
+string::string(const char * str, int x) : std::string(str, x) {
+ TRACE_CTOR(string, "const char *, int");
+}
+string::string(const char * str, int x, int y) : std::string(str, x, y) {
+ TRACE_CTOR(string, "const char *, int, int");
+}
+string::~string() throw() {
+ TRACE_DTOR(string);
+}
+
+} // namespace ledger
+
+#endif // VERIFY_ON
+
+ledger::string empty_string("");
+
+/**********************************************************************
+ *
+ * Logging
+ */
+
+#if defined(LOGGING_ON)
+
+namespace ledger {
+
+log_level_t _log_level = LOG_WARN;
+std::ostream * _log_stream = &std::cerr;
+std::ostringstream _log_buffer;
+
+#if defined(TRACING_ON)
+unsigned int _trace_level;
+#endif
+
+#ifdef BOOST_DATE_TIME_HAS_HIGH_PRECISION_CLOCK
+#define CURRENT_TIME() boost::posix_time::microsec_clock::universal_time()
+#else
+#define CURRENT_TIME() boost::posix_time::second_clock::universal_time()
+#endif
+
+static inline void stream_memory_size(std::ostream& out, std::size_t size)
+{
+ if (size < 1024)
+ out << size << 'b';
+ else if (size < (1024 * 1024))
+ out << (double(size) / 1024.0) << 'K';
+ else if (size < (1024 * 1024 * 1024))
+ out << (double(size) / (1024.0 * 1024.0)) << 'M';
+ else
+ out << (double(size) / (1024.0 * 1024.0 * 1024.0)) << 'G';
+}
+
+static bool logger_has_run = false;
+static ptime logger_start;
+
+bool logger_func(log_level_t level)
+{
+ unsigned long appender = 0;
+
+ if (! logger_has_run) {
+ logger_has_run = true;
+ logger_start = CURRENT_TIME();
+
+ IF_VERIFY()
+ *_log_stream << " TIME OBJSZ MEMSZ" << std::endl;
+
+ appender = (logger_start - current_time).total_milliseconds();
+ }
+
+ *_log_stream << std::right << std::setw(5)
+ << (CURRENT_TIME() - logger_start).total_milliseconds();
+
+ IF_VERIFY() {
+ *_log_stream << std::right << std::setw(6) << std::setprecision(3);
+ stream_memory_size(*_log_stream, current_objects_size());
+ *_log_stream << std::right << std::setw(6) << std::setprecision(3);
+ stream_memory_size(*_log_stream, current_memory_size());
+ }
+
+ *_log_stream << " " << std::left << std::setw(7);
+
+ switch (level) {
+ case LOG_CRIT: *_log_stream << "[CRIT]"; break;
+ case LOG_FATAL: *_log_stream << "[FATAL]"; break;
+ case LOG_ASSERT: *_log_stream << "[ASSRT]"; break;
+ case LOG_ERROR: *_log_stream << "[ERROR]"; break;
+ case LOG_VERIFY: *_log_stream << "[VERFY]"; break;
+ case LOG_WARN: *_log_stream << "[WARN]"; break;
+ case LOG_INFO: *_log_stream << "[INFO]"; break;
+ case LOG_EXCEPT: *_log_stream << "[EXCPT]"; break;
+ case LOG_DEBUG: *_log_stream << "[DEBUG]"; break;
+ case LOG_TRACE: *_log_stream << "[TRACE]"; break;
+
+ case LOG_OFF:
+ case LOG_ALL:
+ assert(false);
+ break;
+ }
+
+ *_log_stream << ' ' << _log_buffer.str();
+
+ if (appender)
+ *_log_stream << " (" << appender << "ms startup)";
+
+ *_log_stream << std::endl;
+
+ _log_buffer.str("");
+
+ return true;
+}
+
+} // namespace ledger
+
+#if defined(DEBUG_ON)
+
+namespace ledger {
+
+optional<std::string> _log_category;
+
+} // namespace ledger
+
+#endif // DEBUG_ON
+#endif // LOGGING_ON
+
+/**********************************************************************
+ *
+ * Timers (allows log entries to specify cumulative time spent)
+ */
+
+#if defined(LOGGING_ON) && defined(TIMERS_ON)
+
+namespace ledger {
+
+struct timer_t
+{
+ log_level_t level;
+ ptime begin;
+ time_duration spent;
+ std::string description;
+ bool active;
+
+ timer_t(log_level_t _level, std::string _description)
+ : level(_level), begin(CURRENT_TIME()),
+ spent(time_duration(0, 0, 0, 0)),
+ description(_description), active(true) {}
+};
+
+typedef std::map<std::string, timer_t> timer_map;
+
+static timer_map timers;
+
+void start_timer(const char * name, log_level_t lvl)
+{
+#if defined(VERIFY_ON)
+ memory_tracing_active = false;
+#endif
+
+ timer_map::iterator i = timers.find(name);
+ if (i == timers.end()) {
+ timers.insert(timer_map::value_type(name, timer_t(lvl, _log_buffer.str())));
+ } else {
+ assert((*i).second.description == _log_buffer.str());
+ (*i).second.begin = CURRENT_TIME();
+ (*i).second.active = true;
+ }
+ _log_buffer.str("");
+
+#if defined(VERIFY_ON)
+ memory_tracing_active = true;
+#endif
+}
+
+void stop_timer(const char * name)
+{
+#if defined(VERIFY_ON)
+ memory_tracing_active = false;
+#endif
+
+ timer_map::iterator i = timers.find(name);
+ assert(i != timers.end());
+
+ (*i).second.spent += CURRENT_TIME() - (*i).second.begin;
+ (*i).second.active = false;
+
+#if defined(VERIFY_ON)
+ memory_tracing_active = true;
+#endif
+}
+
+void finish_timer(const char * name)
+{
+#if defined(VERIFY_ON)
+ memory_tracing_active = false;
+#endif
+
+ timer_map::iterator i = timers.find(name);
+ if (i == timers.end())
+ return;
+
+ time_duration spent = (*i).second.spent;
+ if ((*i).second.active) {
+ spent = CURRENT_TIME() - (*i).second.begin;
+ (*i).second.active = false;
+ }
+
+ _log_buffer << (*i).second.description << ' ';
+
+ bool need_paren =
+ (*i).second.description[(*i).second.description.size() - 1] != ':';
+
+ if (need_paren)
+ _log_buffer << '(';
+
+ _log_buffer << spent.total_milliseconds() << "ms";
+
+ if (need_paren)
+ _log_buffer << ')';
+
+ logger_func((*i).second.level);
+
+ timers.erase(i);
+
+#if defined(VERIFY_ON)
+ memory_tracing_active = true;
+#endif
+}
+
+} // namespace ledger
+
+#endif // LOGGING_ON && TIMERS_ON
+
+/**********************************************************************
+ *
+ * Exception handling
+ */
+
+namespace ledger {
+
+std::ostringstream _desc_buffer;
+std::ostringstream _ctxt_buffer;
+
+} // namespace ledger
+
+/**********************************************************************
+ *
+ * General utility functions
+ */
+
+namespace ledger {
+
+path expand_path(const path& pathname)
+{
+ if (pathname.empty())
+ return pathname;
+
+#if 0
+ // jww (2007-04-30): I need to port this code to use
+ // boost::filesystem::path
+ const char * pfx = NULL;
+ string::size_type pos = pathname.find_first_of('/');
+
+ if (pathname.length() == 1 || pos == 1) {
+ pfx = std::getenv("HOME");
+#ifdef HAVE_GETPWUID
+ if (! pfx) {
+ // Punt. We're trying to expand ~/, but HOME isn't set
+ struct passwd * pw = getpwuid(getuid());
+ if (pw)
+ pfx = pw->pw_dir;
+ }
+#endif
+ }
+#ifdef HAVE_GETPWNAM
+ else {
+ string user(pathname, 1, pos == string::npos ?
+ string::npos : pos - 1);
+ struct passwd * pw = getpwnam(user.c_str());
+ if (pw)
+ pfx = pw->pw_dir;
+ }
+#endif
+
+ // if we failed to find an expansion, return the path unchanged.
+
+ if (! pfx)
+ return pathname;
+
+ string result(pfx);
+
+ if (pos == string::npos)
+ return result;
+
+ if (result.length() == 0 || result[result.length() - 1] != '/')
+ result += '/';
+
+ result += pathname.substr(pos + 1);
+
+ return result;
+#else
+ return pathname;
+#endif
+}
+
+path resolve_path(const path& pathname)
+{
+ path temp = pathname;
+ if (temp.string()[0] == '~')
+ temp = expand_path(temp);
+ temp.normalize();
+ return temp;
+}
+
+} // namespace ledger
diff --git a/src/utils.h b/src/utils.h
new file mode 100644
index 00000000..60d47ec1
--- /dev/null
+++ b/src/utils.h
@@ -0,0 +1,578 @@
+/*
+ * Copyright (c) 2003-2008, 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 utils.h
+ * @author John Wiegley
+ * @date Sun May 6 21:20:00 2007
+ *
+ * @brief This file contains general utility facilities used by Ledger.
+ *
+ * Ledger has need of the following utility code, which this file
+ * provides or includes in:
+ *
+ * - system headers
+ * - asserts
+ * - verification (basically, "heavy asserts")
+ * - tracing code
+ * - debug logging code
+ * - timing code
+ * - current error context
+ * - exception framework
+ * - date/time type
+ * - supports_flags<> for objects that use flags
+ * - push_variable<> for restoring variable values
+ */
+
+#ifndef _UTILS_H
+#define _UTILS_H
+
+#if defined(DEBUG_MODE)
+#define BOOST_MULTI_INDEX_ENABLE_SAFE_MODE 1
+#define BOOST_MULTI_INDEX_ENABLE_INVARIANT_CHECKING 1
+#endif
+
+#include <system.hh>
+
+/**********************************************************************
+ *
+ * Default values
+ */
+
+#if defined(DEBUG_MODE)
+#define VERIFY_ON 1
+#define TRACING_ON 1
+#define DEBUG_ON 1
+#define TIMERS_ON 1
+#elif defined(NDEBUG)
+#define NO_ASSERTS 1
+#define NO_LOGGING 1
+#else
+#define VERIFY_ON 1 // compiled in, use --verify to enable
+#define TRACING_ON 1 // use --trace X to enable
+#define TIMERS_ON 1
+#endif
+
+/**********************************************************************
+ *
+ * Forward declarations
+ */
+
+namespace ledger {
+ using namespace boost;
+
+#if defined(VERIFY_ON)
+ class string;
+#else
+ typedef std::string string;
+#endif
+
+ typedef std::list<string> strings_list;
+
+ typedef posix_time::ptime ptime;
+ typedef ptime::time_duration_type time_duration;
+ typedef gregorian::date date;
+ typedef gregorian::date_duration date_duration;
+ typedef posix_time::seconds seconds;
+
+ typedef boost::filesystem::path path;
+ typedef boost::filesystem::ifstream ifstream;
+ typedef boost::filesystem::ofstream ofstream;
+ typedef boost::filesystem::filesystem_error filesystem_error;
+}
+
+/**********************************************************************
+ *
+ * Assertions
+ */
+
+#ifdef assert
+#undef assert
+#endif
+
+#if ! defined(NO_ASSERTS)
+#define ASSERTS_ON 1
+#endif
+#if defined(ASSERTS_ON)
+
+namespace ledger {
+ void debug_assert(const string& reason, const string& func,
+ const string& file, unsigned long line);
+}
+
+#define assert(x) \
+ ((x) ? ((void)0) : debug_assert(#x, BOOST_CURRENT_FUNCTION, \
+ __FILE__, __LINE__))
+
+#else // ! ASSERTS_ON
+
+#define assert(x)
+
+#endif // ASSERTS_ON
+
+/**********************************************************************
+ *
+ * Verification (basically, very slow asserts)
+ */
+
+#if defined(VERIFY_ON)
+
+namespace ledger {
+
+extern bool verify_enabled;
+
+#define VERIFY(x) (ledger::verify_enabled ? assert(x) : ((void)0))
+#define DO_VERIFY() ledger::verify_enabled
+
+void initialize_memory_tracing();
+void shutdown_memory_tracing();
+
+std::size_t current_memory_size();
+std::size_t current_objects_size();
+
+void trace_ctor_func(void * ptr, const char * cls_name, const char * args,
+ std::size_t cls_size);
+void trace_dtor_func(void * ptr, const char * cls_name, std::size_t cls_size);
+
+#define TRACE_CTOR(cls, args) \
+ (DO_VERIFY() ? \
+ ledger::trace_ctor_func(this, #cls, args, sizeof(cls)) : ((void)0))
+#define TRACE_DTOR(cls) \
+ (DO_VERIFY() ? \
+ ledger::trace_dtor_func(this, #cls, sizeof(cls)) : ((void)0))
+
+void report_memory(std::ostream& out, bool report_all = false);
+
+/**
+ * This string type is a wrapper around std::string that allows us to
+ * trace constructor and destructor calls.
+ */
+class string : public std::string
+{
+public:
+ string();
+ string(const string& str);
+ string(const std::string& str);
+ string(const int len, char x);
+ string(const char * str);
+ string(const char * str, const char * end);
+ string(const string& str, int x);
+ string(const string& str, int x, int y);
+ string(const char * str, int x);
+ string(const char * str, int x, int y);
+ ~string() throw();
+};
+
+inline string operator+(const string& __lhs, const string& __rhs)
+{
+ string __str(__lhs);
+ __str.append(__rhs);
+ return __str;
+}
+
+string operator+(const char* __lhs, const string& __rhs);
+string operator+(char __lhs, const string& __rhs);
+
+inline string operator+(const string& __lhs, const char* __rhs)
+{
+ string __str(__lhs);
+ __str.append(__rhs);
+ return __str;
+}
+
+inline string operator+(const string& __lhs, char __rhs)
+{
+ typedef string __string_type;
+ typedef string::size_type __size_type;
+ __string_type __str(__lhs);
+ __str.append(__size_type(1), __rhs);
+ return __str;
+}
+
+inline bool operator==(const string& __lhs, const string& __rhs)
+{ return __lhs.compare(__rhs) == 0; }
+
+inline bool operator==(const char* __lhs, const string& __rhs)
+{ return __rhs.compare(__lhs) == 0; }
+
+inline bool operator==(const string& __lhs, const char* __rhs)
+{ return __lhs.compare(__rhs) == 0; }
+
+inline bool operator!=(const string& __lhs, const string& __rhs)
+{ return __rhs.compare(__lhs) != 0; }
+
+inline bool operator!=(const char* __lhs, const string& __rhs)
+{ return __rhs.compare(__lhs) != 0; }
+
+inline bool operator!=(const string& __lhs, const char* __rhs)
+{ return __lhs.compare(__rhs) != 0; }
+
+} // namespace ledger
+
+#else // ! VERIFY_ON
+
+#define VERIFY(x)
+#define DO_VERIFY() true
+#define TRACE_CTOR(cls, args)
+#define TRACE_DTOR(cls)
+
+#endif // VERIFY_ON
+
+extern ledger::string empty_string;
+
+#define IF_VERIFY() if (DO_VERIFY())
+
+/**********************************************************************
+ *
+ * Logging
+ */
+
+#if ! defined(NO_LOGGING)
+#define LOGGING_ON 1
+#endif
+#if defined(LOGGING_ON)
+
+namespace ledger {
+
+enum log_level_t {
+ LOG_OFF = 0,
+ LOG_CRIT,
+ LOG_FATAL,
+ LOG_ASSERT,
+ LOG_ERROR,
+ LOG_VERIFY,
+ LOG_WARN,
+ LOG_INFO,
+ LOG_EXCEPT,
+ LOG_DEBUG,
+ LOG_TRACE,
+ LOG_ALL
+};
+
+extern log_level_t _log_level;
+extern std::ostream * _log_stream;
+extern std::ostringstream _log_buffer;
+
+bool logger_func(log_level_t level);
+
+#define LOGGER(cat) \
+ static const char * const _this_category = cat
+
+#if defined(TRACING_ON)
+
+extern unsigned int _trace_level;
+
+#define SHOW_TRACE(lvl) \
+ (ledger::_log_level >= ledger::LOG_TRACE && lvl <= ledger::_trace_level)
+#define TRACE(lvl, msg) \
+ (SHOW_TRACE(lvl) ? \
+ ((ledger::_log_buffer << msg), \
+ ledger::logger_func(ledger::LOG_TRACE)) : false)
+
+#else // TRACING_ON
+
+#define SHOW_TRACE(lvl) false
+#define TRACE(lvl, msg)
+
+#endif // TRACING_ON
+
+#if defined(DEBUG_ON)
+
+extern optional<std::string> _log_category;
+
+inline bool category_matches(const char * cat) {
+ return _log_category && starts_with(cat, *_log_category);
+}
+
+#define SHOW_DEBUG(cat) \
+ (ledger::_log_level >= ledger::LOG_DEBUG && ledger::category_matches(cat))
+#define SHOW_DEBUG_() SHOW_DEBUG(_this_category)
+
+#define DEBUG(cat, msg) \
+ (SHOW_DEBUG(cat) ? \
+ ((ledger::_log_buffer << msg), \
+ ledger::logger_func(ledger::LOG_DEBUG)) : false)
+#define DEBUG_(msg) DEBUG(_this_category, msg)
+
+#else // DEBUG_ON
+
+#define SHOW_DEBUG(cat) false
+#define SHOW_DEBUG_() false
+#define DEBUG(cat, msg)
+#define DEBUG_(msg)
+
+#endif // DEBUG_ON
+
+#define LOG_MACRO(level, msg) \
+ (ledger::_log_level >= level ? \
+ ((ledger::_log_buffer << msg), ledger::logger_func(level)) : false)
+
+#define SHOW_INFO() (ledger::_log_level >= ledger::LOG_INFO)
+#define SHOW_WARN() (ledger::_log_level >= ledger::LOG_WARN)
+#define SHOW_ERROR() (ledger::_log_level >= ledger::LOG_ERROR)
+#define SHOW_FATAL() (ledger::_log_level >= ledger::LOG_FATAL)
+#define SHOW_CRITICAL() (ledger::_log_level >= ledger::LOG_CRIT)
+
+#define INFO(msg) LOG_MACRO(ledger::LOG_INFO, msg)
+#define WARN(msg) LOG_MACRO(ledger::LOG_WARN, msg)
+#define ERROR(msg) LOG_MACRO(ledger::LOG_ERROR, msg)
+#define FATAL(msg) LOG_MACRO(ledger::LOG_FATAL, msg)
+#define CRITICAL(msg) LOG_MACRO(ledger::LOG_CRIT, msg)
+#define EXCEPTION(msg) LOG_MACRO(ledger::LOG_EXCEPT, msg)
+
+} // namespace ledger
+
+#else // ! LOGGING_ON
+
+#define LOGGER(cat)
+
+#define SHOW_TRACE(lvl) false
+#define SHOW_DEBUG(cat) false
+#define SHOW_DEBUG_() false
+#define SHOW_INFO() false
+#define SHOW_WARN() false
+#define SHOW_ERROR() false
+#define SHOW_FATAL() false
+#define SHOW_CRITICAL() false
+
+#define TRACE(lvl, msg)
+#define DEBUG(cat, msg)
+#define DEBUG_(msg)
+#define INFO(msg)
+#define WARN(msg)
+#define ERROR(msg)
+#define FATAL(msg)
+#define CRITICAL(msg)
+
+#endif // LOGGING_ON
+
+#define IF_TRACE(lvl) if (SHOW_TRACE(lvl))
+#define IF_DEBUG(cat) if (SHOW_DEBUG(cat))
+#define IF_DEBUG_() if (SHOW_DEBUG_())
+#define IF_INFO() if (SHOW_INFO())
+#define IF_WARN() if (SHOW_WARN())
+#define IF_ERROR() if (SHOW_ERROR())
+#define IF_FATAL() if (SHOW_FATAL())
+#define IF_CRITICAL() if (SHOW_CRITICAL())
+
+/**********************************************************************
+ *
+ * Timers (allows log entries to specify cumulative time spent)
+ */
+
+#if defined(LOGGING_ON) && defined(TIMERS_ON)
+
+namespace ledger {
+
+void start_timer(const char * name, log_level_t lvl);
+void stop_timer(const char * name);
+void finish_timer(const char * name);
+
+#if defined(TRACING_ON)
+#define TRACE_START(name, lvl, msg) \
+ (SHOW_TRACE(lvl) ? \
+ ((ledger::_log_buffer << msg), \
+ ledger::start_timer(#name, ledger::LOG_TRACE)) : ((void)0))
+#define TRACE_STOP(name, lvl) \
+ (SHOW_TRACE(lvl) ? ledger::stop_timer(#name) : ((void)0))
+#define TRACE_FINISH(name, lvl) \
+ (SHOW_TRACE(lvl) ? ledger::finish_timer(#name) : ((void)0))
+#else
+#define TRACE_START(name, lvl, msg)
+#define TRACE_STOP(name)
+#define TRACE_FINISH(name)
+#endif
+
+#if defined(DEBUG_ON)
+#define DEBUG_START(name, cat, msg) \
+ (SHOW_DEBUG(cat) ? \
+ ((ledger::_log_buffer << msg), \
+ ledger::start_timer(#name, ledger::LOG_DEBUG)) : ((void)0))
+#define DEBUG_START_(name, msg) \
+ DEBUG_START_(name, _this_category, msg)
+#define DEBUG_STOP(name, cat) \
+ (SHOW_DEBUG(cat) ? ledger::stop_timer(#name) : ((void)0))
+#define DEBUG_STOP_(name) \
+ DEBUG_STOP_(name, _this_category)
+#define DEBUG_FINISH(name, cat) \
+ (SHOW_DEBUG(cat) ? ledger::finish_timer(#name) : ((void)0))
+#define DEBUG_FINISH_(name) \
+ DEBUG_FINISH_(name, _this_category)
+#else
+#define DEBUG_START(name, cat, msg)
+#define DEBUG_START_(name, msg)
+#define DEBUG_STOP(name)
+#define DEBUG_FINISH(name)
+#endif
+
+#define INFO_START(name, msg) \
+ (SHOW_INFO() ? \
+ ((ledger::_log_buffer << msg), \
+ ledger::start_timer(#name, ledger::LOG_INFO)) : ((void)0))
+#define INFO_STOP(name) \
+ (SHOW_INFO() ? stop_timer(#name) : ((void)0))
+#define INFO_FINISH(name) \
+ (SHOW_INFO() ? finish_timer(#name) : ((void)0))
+
+} // namespace ledger
+
+#else // ! (LOGGING_ON && TIMERS_ON)
+
+#define TRACE_START(lvl, msg, name)
+#define TRACE_STOP(name)
+#define TRACE_FINISH(name)
+
+#define DEBUG_START(name, msg)
+#define DEBUG_START_(name, cat, msg)
+#define DEBUG_STOP(name)
+#define DEBUG_FINISH(name)
+
+#define INFO_START(name, msg)
+#define INFO_STOP(name)
+#define INFO_FINISH(name)
+
+#endif // TIMERS_ON
+
+/**********************************************************************
+ *
+ * - Exception handling helpers
+ * - Date/time support classes
+ * - General support for objects with "flags"
+ * - Support for scoped execution and variable restoration
+ */
+
+#include "error.h"
+#include "times.h"
+#include "flags.h"
+#include "pushvar.h"
+
+/**********************************************************************
+ *
+ * General utility functions
+ */
+
+#define foreach BOOST_FOREACH
+
+namespace ledger {
+
+template <typename T, typename U>
+inline T& downcast(U& object) {
+ return *polymorphic_downcast<T *>(&object);
+}
+
+path resolve_path(const path& pathname);
+
+#ifdef HAVE_REALPATH
+extern "C" char * realpath(const char *, char resolved_path[]);
+#endif
+
+inline const string& either_or(const string& first,
+ const string& second) {
+ return first.empty() ? second : first;
+}
+
+inline char * skip_ws(char * ptr) {
+ while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n')
+ ptr++;
+ return ptr;
+}
+
+inline char * next_element(char * buf, bool variable = false) {
+ for (char * p = buf; *p; p++) {
+ if (! (*p == ' ' || *p == '\t'))
+ continue;
+
+ if (! variable) {
+ *p = '\0';
+ return skip_ws(p + 1);
+ }
+ else if (*p == '\t') {
+ *p = '\0';
+ return skip_ws(p + 1);
+ }
+ else if (*(p + 1) == ' ') {
+ *p = '\0';
+ return skip_ws(p + 2);
+ }
+ }
+ return NULL;
+}
+
+inline char peek_next_nonws(std::istream& in) {
+ char c = in.peek();
+ while (! in.eof() && std::isspace(c)) {
+ in.get(c);
+ c = in.peek();
+ }
+ return c;
+}
+
+#define READ_INTO(str, targ, size, var, cond) { \
+ char * _p = targ; \
+ var = str.peek(); \
+ while (! str.eof() && var != '\n' && (cond) && _p - targ < size) { \
+ str.get(var); \
+ if (str.eof()) \
+ break; \
+ if (var == '\\') { \
+ str.get(var); \
+ if (in.eof()) \
+ break; \
+ } \
+ *_p++ = var; \
+ var = str.peek(); \
+ } \
+ *_p = '\0'; \
+}
+
+#define READ_INTO_(str, targ, size, var, idx, cond) { \
+ char * _p = targ; \
+ var = str.peek(); \
+ while (! str.eof() && var != '\n' && (cond) && _p - targ < size) { \
+ str.get(var); \
+ if (str.eof()) \
+ break; \
+ idx++; \
+ if (var == '\\') { \
+ str.get(var); \
+ if (in.eof()) \
+ break; \
+ idx++; \
+ } \
+ *_p++ = var; \
+ var = str.peek(); \
+ } \
+ *_p = '\0'; \
+}
+
+} // namespace ledger
+
+#endif // _UTILS_H
diff --git a/src/value.cc b/src/value.cc
new file mode 100644
index 00000000..ea52af1f
--- /dev/null
+++ b/src/value.cc
@@ -0,0 +1,1773 @@
+/*
+ * Copyright (c) 2003-2008, 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 "value.h"
+#include "binary.h"
+
+namespace ledger {
+
+intrusive_ptr<value_t::storage_t> value_t::true_value;
+intrusive_ptr<value_t::storage_t> value_t::false_value;
+
+value_t::storage_t& value_t::storage_t::operator=(const value_t::storage_t& rhs)
+{
+ type = rhs.type;
+
+ switch (type) {
+ case DATETIME:
+ new(reinterpret_cast<datetime_t *>(data))
+ datetime_t(*reinterpret_cast<datetime_t *>
+ (const_cast<char *>(rhs.data)));
+ break;
+
+ case DATE:
+ new(reinterpret_cast<date_t *>(data))
+ date_t(*reinterpret_cast<date_t *>(const_cast<char *>(rhs.data)));
+ break;
+
+ case AMOUNT:
+ new(reinterpret_cast<amount_t *>(data))
+ amount_t(*reinterpret_cast<amount_t *>
+ (const_cast<char *>(rhs.data)));
+ break;
+
+ case BALANCE:
+ *reinterpret_cast<balance_t **>(data) =
+ new balance_t(**reinterpret_cast<balance_t **>
+ (const_cast<char *>(rhs.data)));
+ break;
+
+ case BALANCE_PAIR:
+ *reinterpret_cast<balance_pair_t **>(data) =
+ new balance_pair_t(**reinterpret_cast<balance_pair_t **>
+ (const_cast<char *>(rhs.data)));
+ break;
+
+ case STRING:
+ new(reinterpret_cast<string *>(data))
+ string(*reinterpret_cast<string *>(const_cast<char *>(rhs.data)));
+ break;
+
+ case SEQUENCE:
+ *reinterpret_cast<sequence_t **>(data) =
+ new sequence_t(**reinterpret_cast<sequence_t **>
+ (const_cast<char *>(rhs.data)));
+ break;
+
+ default:
+ // The rest are fundamental types, which can be copied using
+ // std::memcpy
+ std::memcpy(data, rhs.data, sizeof(data));
+ break;
+ }
+
+ return *this;
+}
+
+void value_t::storage_t::destroy()
+{
+ switch (type) {
+ case AMOUNT:
+ reinterpret_cast<amount_t *>(data)->~amount_t();
+ break;
+ case BALANCE:
+ checked_delete(*reinterpret_cast<balance_t **>(data));
+ break;
+ case BALANCE_PAIR:
+ checked_delete(*reinterpret_cast<balance_pair_t **>(data));
+ break;
+ case STRING:
+ reinterpret_cast<string *>(data)->~string();
+ break;
+ case SEQUENCE:
+ checked_delete(*reinterpret_cast<sequence_t **>(data));
+ break;
+ case POINTER:
+ reinterpret_cast<boost::any *>(data)->~any();
+ break;
+
+ default:
+ break;
+ }
+ type = VOID;
+}
+
+void value_t::initialize()
+{
+ LOGGER("value.initialize");
+
+ true_value = new storage_t;
+ true_value->type = BOOLEAN;
+ *reinterpret_cast<bool *>(true_value->data) = true;
+
+ false_value = new storage_t;
+ false_value->type = BOOLEAN;
+ *reinterpret_cast<bool *>(false_value->data) = false;
+
+#if 0
+ BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(bool));
+ BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(datetime_t));
+ BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(date_t));
+ BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(long));
+ BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(amount_t));
+ BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(balance_t *));
+ BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(balance_pair_t *));
+ BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(string));
+ BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(sequence_t *));
+ BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(boost::any));
+#endif
+
+ DEBUG_(std::setw(3) << std::right << sizeof(bool)
+ << " sizeof(bool)");
+ DEBUG_(std::setw(3) << std::right << sizeof(datetime_t)
+ << " sizeof(datetime_t)");
+ DEBUG_(std::setw(3) << std::right << sizeof(date_t)
+ << " sizeof(date_t)");
+ DEBUG_(std::setw(3) << std::right << sizeof(long)
+ << " sizeof(long)");
+ DEBUG_(std::setw(3) << std::right << sizeof(amount_t)
+ << " sizeof(amount_t)");
+ DEBUG_(std::setw(3) << std::right << sizeof(balance_t *)
+ << " sizeof(balance_t *)");
+ DEBUG_(std::setw(3) << std::right << sizeof(balance_pair_t *)
+ << " sizeof(balance_pair_t *)");
+ DEBUG_(std::setw(3) << std::right << sizeof(string)
+ << " sizeof(string)");
+ DEBUG_(std::setw(3) << std::right << sizeof(sequence_t *)
+ << " sizeof(sequence_t *)");
+ DEBUG_(std::setw(3) << std::right << sizeof(boost::any)
+ << " sizeof(boost::any)");
+}
+
+void value_t::shutdown()
+{
+ true_value = intrusive_ptr<storage_t>();
+ false_value = intrusive_ptr<storage_t>();
+}
+
+void value_t::_dup()
+{
+ assert(storage);
+ if (storage->refc > 1)
+ storage = new storage_t(*storage.get());
+}
+
+value_t::operator bool() const
+{
+ switch (type()) {
+ case BOOLEAN:
+ return as_boolean();
+ case DATETIME:
+ return is_valid(as_datetime());
+ case DATE:
+ return is_valid(as_date());
+ case INTEGER:
+ return as_long();
+ case AMOUNT:
+ return as_amount();
+ case BALANCE:
+ return as_balance();
+ case BALANCE_PAIR:
+ return as_balance_pair();
+ case STRING:
+ return ! as_string().empty();
+ case SEQUENCE:
+ return ! as_sequence().empty();
+ case POINTER:
+ return ! as_any_pointer().empty();
+ default:
+ assert(false);
+ break;
+ }
+ assert(false);
+ return 0;
+}
+
+bool value_t::to_boolean() const
+{
+ if (is_boolean()) {
+ return as_boolean();
+ } else {
+ value_t temp(*this);
+ temp.in_place_cast(BOOLEAN);
+ return temp.as_boolean();
+ }
+}
+
+datetime_t value_t::to_datetime() const
+{
+ if (is_datetime()) {
+ return as_datetime();
+ } else {
+ value_t temp(*this);
+ temp.in_place_cast(DATETIME);
+ return temp.as_datetime();
+ }
+}
+
+date_t value_t::to_date() const
+{
+ if (is_date()) {
+ return as_date();
+ } else {
+ value_t temp(*this);
+ temp.in_place_cast(DATE);
+ return temp.as_date();
+ }
+}
+
+long value_t::to_long() const
+{
+ if (is_long()) {
+ return as_long();
+ } else {
+ value_t temp(*this);
+ temp.in_place_cast(INTEGER);
+ return temp.as_long();
+ }
+}
+
+amount_t value_t::to_amount() const
+{
+ if (is_amount()) {
+ return as_amount();
+ } else {
+ value_t temp(*this);
+ temp.in_place_cast(AMOUNT);
+ return temp.as_amount();
+ }
+}
+
+balance_t value_t::to_balance() const
+{
+ if (is_balance()) {
+ return as_balance();
+ } else {
+ value_t temp(*this);
+ temp.in_place_cast(BALANCE);
+ return temp.as_balance();
+ }
+}
+
+balance_pair_t value_t::to_balance_pair() const
+{
+ if (is_balance_pair()) {
+ return as_balance_pair();
+ } else {
+ value_t temp(*this);
+ temp.in_place_cast(BALANCE_PAIR);
+ return temp.as_balance_pair();
+ }
+}
+
+string value_t::to_string() const
+{
+ if (is_string()) {
+ return as_string();
+ } else {
+ value_t temp(*this);
+ temp.in_place_cast(STRING);
+ return temp.as_string();
+ }
+}
+
+value_t::sequence_t value_t::to_sequence() const
+{
+ if (is_sequence()) {
+ return as_sequence();
+ } else {
+ value_t temp(*this);
+ temp.in_place_cast(SEQUENCE);
+ return temp.as_sequence();
+ }
+}
+
+
+void value_t::in_place_simplify()
+{
+ LOGGER("amounts.values.simplify");
+
+ if (is_realzero()) {
+ DEBUG_("Zeroing type " << static_cast<int>(type()));
+ set_long(0L);
+ return;
+ }
+
+ if (is_balance_pair() &&
+ (! as_balance_pair().cost || as_balance_pair().cost->is_realzero())) {
+ DEBUG_("Reducing balance pair to balance");
+ in_place_cast(BALANCE);
+ }
+
+ if (is_balance() && as_balance().amounts.size() == 1) {
+ DEBUG_("Reducing balance to amount");
+ DEBUG("ledger.value.reduce", "as a balance it looks like: " << *this);
+ in_place_cast(AMOUNT);
+ DEBUG("ledger.value.reduce", "as an amount it looks like: " << *this);
+ }
+
+#if 0
+ if (is_amount() && ! as_amount().has_commodity() &&
+ as_amount().fits_in_long()) {
+ DEBUG_("Reducing amount to integer");
+ in_place_cast(INTEGER);
+ }
+#endif
+}
+
+value_t& value_t::operator+=(const value_t& val)
+{
+ if (is_string()) {
+ if (val.is_string())
+ as_string_lval() += val.as_string();
+ else
+ as_string_lval() += val.to_string();
+ return *this;
+ }
+ else if (is_sequence()) {
+ if (val.is_sequence()) {
+ sequence_t& seq(as_sequence_lval());
+ seq.insert(seq.end(), val.as_sequence().begin(),
+ val.as_sequence().end());
+ } else {
+ as_sequence_lval().push_back(val);
+ }
+ return *this;
+ }
+
+ switch (type()) {
+ case DATETIME:
+ switch (val.type()) {
+ case INTEGER:
+ as_datetime_lval() += date_duration(val.as_long());
+ return *this;
+ case AMOUNT:
+ as_datetime_lval() += date_duration(val.as_amount().to_long());
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ case DATE:
+ switch (val.type()) {
+ case INTEGER:
+ as_date_lval() += date_duration_t(val.as_long());
+ return *this;
+ case AMOUNT:
+ as_date_lval() += date_duration_t(val.as_amount().to_long());
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ case INTEGER:
+ switch (val.type()) {
+ case INTEGER:
+ as_long_lval() += val.as_long();
+ return *this;
+ case AMOUNT:
+ in_place_cast(AMOUNT);
+ as_amount_lval() += val.as_amount();
+ return *this;
+ case BALANCE:
+ in_place_cast(BALANCE);
+ as_balance_lval() += val.as_balance();
+ return *this;
+ case BALANCE_PAIR:
+ in_place_cast(BALANCE_PAIR);
+ as_balance_pair_lval() += val.as_balance_pair();
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ case AMOUNT:
+ switch (val.type()) {
+ case INTEGER:
+ if (as_amount().has_commodity()) {
+ in_place_cast(BALANCE);
+ return *this += val;
+ } else {
+ as_amount_lval() += val.as_long();
+ return *this;
+ }
+ break;
+
+ case AMOUNT:
+ if (as_amount().commodity() != val.as_amount().commodity()) {
+ in_place_cast(BALANCE);
+ return *this += val;
+ } else {
+ as_amount_lval() += val.as_amount();
+ return *this;
+ }
+ break;
+
+ case BALANCE:
+ in_place_cast(BALANCE);
+ as_balance_lval() += val.as_balance();
+ return *this;
+
+ case BALANCE_PAIR:
+ in_place_cast(BALANCE_PAIR);
+ as_balance_pair_lval() += val.as_balance_pair();
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ case BALANCE:
+ switch (val.type()) {
+ case INTEGER:
+ as_balance_lval() += val.to_amount();
+ return *this;
+ case AMOUNT:
+ as_balance_lval() += val.as_amount();
+ return *this;
+ case BALANCE:
+ as_balance_lval() += val.as_balance();
+ return *this;
+ case BALANCE_PAIR:
+ in_place_cast(BALANCE_PAIR);
+ as_balance_pair_lval() += val.as_balance_pair();
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ case BALANCE_PAIR:
+ switch (val.type()) {
+ case INTEGER:
+ as_balance_pair_lval() += val.to_amount();
+ return *this;
+ case AMOUNT:
+ as_balance_pair_lval() += val.as_amount();
+ return *this;
+ case BALANCE:
+ as_balance_pair_lval() += val.as_balance();
+ return *this;
+ case BALANCE_PAIR:
+ as_balance_pair_lval() += val.as_balance_pair();
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot add " << val.label() << " to " << label());
+ return *this;
+}
+
+value_t& value_t::operator-=(const value_t& val)
+{
+ if (is_sequence()) {
+ sequence_t& seq(as_sequence_lval());
+
+ if (val.is_sequence()) {
+ foreach (const value_t& v, val.as_sequence()) {
+ sequence_t::iterator j = std::find(seq.begin(), seq.end(), v);
+ if (j != seq.end())
+ seq.erase(j);
+ }
+ } else {
+ sequence_t::iterator i = std::find(seq.begin(), seq.end(), val);
+ if (i != seq.end())
+ seq.erase(i);
+ }
+ return *this;
+ }
+
+ switch (type()) {
+ case DATETIME:
+ switch (val.type()) {
+ case INTEGER:
+ as_datetime_lval() -= date_duration(val.as_long());
+ return *this;
+ case AMOUNT:
+ as_datetime_lval() -= date_duration(val.as_amount().to_long());
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ case DATE:
+ switch (val.type()) {
+ case INTEGER:
+ as_date_lval() -= date_duration_t(val.as_long());
+ return *this;
+ case AMOUNT:
+ as_date_lval() -= date_duration_t(val.as_amount().to_long());
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ case INTEGER:
+ switch (val.type()) {
+ case INTEGER:
+ as_long_lval() -= val.as_long();
+ return *this;
+ case AMOUNT:
+ in_place_cast(AMOUNT);
+ as_amount_lval() -= val.as_amount();
+ in_place_simplify();
+ return *this;
+ case BALANCE:
+ in_place_cast(BALANCE);
+ as_balance_lval() -= val.as_balance();
+ in_place_simplify();
+ return *this;
+ case BALANCE_PAIR:
+ in_place_cast(BALANCE_PAIR);
+ as_balance_pair_lval() -= val.as_balance_pair();
+ in_place_simplify();
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ case AMOUNT:
+ switch (val.type()) {
+ case INTEGER:
+ if (as_amount().has_commodity()) {
+ in_place_cast(BALANCE);
+ *this -= val;
+ in_place_simplify();
+ return *this;
+ } else {
+ as_amount_lval() -= val.as_long();
+ in_place_simplify();
+ return *this;
+ }
+ break;
+
+ case AMOUNT:
+ if (as_amount().commodity() != val.as_amount().commodity()) {
+ in_place_cast(BALANCE);
+ *this -= val;
+ in_place_simplify();
+ return *this;
+ } else {
+ as_amount_lval() -= val.as_amount();
+ in_place_simplify();
+ return *this;
+ }
+ break;
+
+ case BALANCE:
+ in_place_cast(BALANCE);
+ as_balance_lval() -= val.as_balance();
+ in_place_simplify();
+ return *this;
+
+ case BALANCE_PAIR:
+ in_place_cast(BALANCE_PAIR);
+ as_balance_pair_lval() -= val.as_balance_pair();
+ in_place_simplify();
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ case BALANCE:
+ switch (val.type()) {
+ case INTEGER:
+ as_balance_lval() -= val.to_amount();
+ in_place_simplify();
+ return *this;
+ case AMOUNT:
+ as_balance_lval() -= val.as_amount();
+ in_place_simplify();
+ return *this;
+ case BALANCE:
+ as_balance_lval() -= val.as_balance();
+ in_place_simplify();
+ return *this;
+ case BALANCE_PAIR:
+ in_place_cast(BALANCE_PAIR);
+ as_balance_pair_lval() -= val.as_balance_pair();
+ in_place_simplify();
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ case BALANCE_PAIR:
+ switch (val.type()) {
+ case INTEGER:
+ as_balance_pair_lval() -= val.to_amount();
+ in_place_simplify();
+ return *this;
+ case AMOUNT:
+ as_balance_pair_lval() -= val.as_amount();
+ in_place_simplify();
+ return *this;
+ case BALANCE:
+ as_balance_pair_lval() -= val.as_balance();
+ in_place_simplify();
+ return *this;
+ case BALANCE_PAIR:
+ as_balance_pair_lval() -= val.as_balance_pair();
+ in_place_simplify();
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot subtract " << val.label() << " from " << label());
+
+ return *this;
+}
+
+value_t& value_t::operator*=(const value_t& val)
+{
+ if (is_string()) {
+ string temp;
+ long count = val.to_long();
+ for (long i = 0; i < count; i++)
+ temp += as_string();
+ set_string(temp);
+ return *this;
+ }
+ else if (is_sequence()) {
+ value_t temp;
+ long count = val.to_long();
+ for (long i = 0; i < count; i++)
+ temp += as_sequence();
+ return *this = temp;
+ }
+
+ switch (type()) {
+ case INTEGER:
+ switch (val.type()) {
+ case INTEGER:
+ as_long_lval() *= val.as_long();
+ return *this;
+ case AMOUNT:
+ set_amount(val.as_amount() * as_long());
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ case AMOUNT:
+ switch (val.type()) {
+ case INTEGER:
+ as_amount_lval() *= val.as_long();
+ return *this;
+ case AMOUNT:
+ if (as_amount().commodity() == val.as_amount().commodity() ||
+ ! val.as_amount().has_commodity()) {
+ as_amount_lval() *= val.as_amount();
+ return *this;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case BALANCE:
+ switch (val.type()) {
+ case INTEGER:
+ as_balance_lval() *= val.as_long();
+ return *this;
+ case AMOUNT:
+ if (! val.as_amount().has_commodity()) {
+ as_balance_lval() *= val.as_amount();
+ return *this;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case BALANCE_PAIR:
+ switch (val.type()) {
+ case INTEGER:
+ as_balance_pair_lval() *= val.as_long();
+ return *this;
+ case AMOUNT:
+ if (! val.as_amount().has_commodity()) {
+ as_balance_pair_lval() *= val.as_amount();
+ return *this;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot multiply " << label() << " with " << val.label());
+
+ return *this;
+}
+
+value_t& value_t::operator/=(const value_t& val)
+{
+ switch (type()) {
+ case INTEGER:
+ switch (val.type()) {
+ case INTEGER:
+ as_long_lval() /= val.as_long();
+ return *this;
+ case AMOUNT:
+ set_amount(val.as_amount() / as_long());
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ case AMOUNT:
+ switch (val.type()) {
+ case INTEGER:
+ as_amount_lval() /= val.as_long();
+ return *this;
+
+ case AMOUNT:
+ if (as_amount().commodity() == val.as_amount().commodity() ||
+ ! val.as_amount().has_commodity()) {
+ as_amount_lval() /= val.as_amount();
+ return *this;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case BALANCE:
+ switch (val.type()) {
+ case INTEGER:
+ as_balance_lval() /= val.as_long();
+ return *this;
+ case AMOUNT:
+ if (! val.as_amount().has_commodity()) {
+ as_balance_lval() /= val.as_amount();
+ return *this;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case BALANCE_PAIR:
+ switch (val.type()) {
+ case INTEGER:
+ as_balance_pair_lval() /= val.as_long();
+ return *this;
+ case AMOUNT:
+ if (! val.as_amount().has_commodity()) {
+ as_balance_pair_lval() /= val.as_amount();
+ return *this;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot divide " << label() << " by " << val.label());
+
+ return *this;
+}
+
+
+bool value_t::operator==(const value_t& val) const
+{
+ switch (type()) {
+ case VOID:
+ return val.type() == VOID;
+
+ case BOOLEAN:
+ if (val.is_boolean())
+ return as_boolean() == val.as_boolean();
+ break;
+
+ case DATETIME:
+ if (val.is_datetime())
+ return as_datetime() == val.as_datetime();
+ break;
+
+ case DATE:
+ if (val.is_date())
+ return as_date() == val.as_date();
+ break;
+
+ case INTEGER:
+ switch (val.type()) {
+ case INTEGER:
+ return as_long() == val.as_long();
+ case AMOUNT:
+ return val.as_amount() == to_amount();
+ case BALANCE:
+ return val.as_balance() == to_amount();
+ case BALANCE_PAIR:
+ return val.as_balance_pair() == to_amount();
+ default:
+ break;
+ }
+ break;
+
+ case AMOUNT:
+ switch (val.type()) {
+ case INTEGER:
+ return as_amount() == val.as_long();
+ case AMOUNT:
+ return as_amount() == val.as_amount();
+ case BALANCE:
+ return val.as_balance() == as_amount();
+ case BALANCE_PAIR:
+ return val.as_balance_pair() == as_amount();
+ default:
+ break;
+ }
+ break;
+
+ case BALANCE:
+ switch (val.type()) {
+ case INTEGER:
+ return as_balance() == val.to_amount();
+ case AMOUNT:
+ return as_balance() == val.as_amount();
+ case BALANCE:
+ return as_balance() == val.as_balance();
+ case BALANCE_PAIR:
+ return val.as_balance_pair() == as_balance();
+ default:
+ break;
+ }
+ break;
+
+ case BALANCE_PAIR:
+ switch (val.type()) {
+ case INTEGER:
+ return as_balance_pair() == val.to_amount();
+ case AMOUNT:
+ return as_balance_pair() == val.as_amount();
+ case BALANCE:
+ return as_balance_pair() == val.as_balance();
+ case BALANCE_PAIR:
+ return as_balance_pair() == val.as_balance_pair();
+ default:
+ break;
+ }
+ break;
+
+ case STRING:
+ if (val.is_string())
+ return as_string() == val.as_string();
+ break;
+
+ case SEQUENCE:
+ if (val.is_sequence())
+ return as_sequence() == val.as_sequence();
+ break;
+
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot compare " << label() << " to " << val.label());
+
+ return *this;
+}
+
+bool value_t::operator<(const value_t& val) const
+{
+ switch (type()) {
+ case DATETIME:
+ if (val.is_datetime())
+ return as_datetime() < val.as_datetime();
+ break;
+
+ case DATE:
+ if (val.is_date())
+ return as_date() < val.as_date();
+ break;
+
+ case INTEGER:
+ switch (val.type()) {
+ case INTEGER:
+ return as_long() < val.as_long();
+ case AMOUNT:
+ return val.as_amount() < as_long();
+ default:
+ break;
+ }
+ break;
+
+ case AMOUNT:
+ switch (val.type()) {
+ case INTEGER:
+ return as_amount() < val.as_long();
+ case AMOUNT:
+ return as_amount() < val.as_amount();
+ default:
+ break;
+ }
+ break;
+
+ case STRING:
+ if (val.is_string())
+ return as_string() < val.as_string();
+ break;
+
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot compare " << label() << " to " << val.label());
+
+ return *this;
+}
+
+#if 0
+bool value_t::operator>(const value_t& val) const
+{
+ switch (type()) {
+ case DATETIME:
+ if (val.is_datetime())
+ return as_datetime() > val.as_datetime();
+ break;
+
+ case DATE:
+ if (val.is_date())
+ return as_date() > val.as_date();
+ break;
+
+ case INTEGER:
+ switch (val.type()) {
+ case INTEGER:
+ return as_long() > val.as_long();
+ case AMOUNT:
+ return val.as_amount() > as_long();
+ default:
+ break;
+ }
+ break;
+
+ case AMOUNT:
+ switch (val.type()) {
+ case INTEGER:
+ return as_amount() > val.as_long();
+ case AMOUNT:
+ return as_amount() > val.as_amount();
+ default:
+ break;
+ }
+ break;
+
+ case STRING:
+ if (val.is_string())
+ return as_string() > val.as_string();
+ break;
+
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot compare " << label() << " to " << val.label());
+
+ return *this;
+}
+#endif
+
+void value_t::in_place_cast(type_t cast_type)
+{
+ if (type() == cast_type)
+ return;
+
+ if (cast_type == BOOLEAN) {
+ set_boolean(bool(*this));
+ return;
+ }
+ else if (cast_type == SEQUENCE) {
+ sequence_t temp;
+ if (! is_null())
+ temp.push_back(*this);
+ set_sequence(temp);
+ return;
+ }
+
+ switch (type()) {
+ case BOOLEAN:
+ switch (cast_type) {
+ case STRING:
+ set_string(as_boolean() ? "true" : "false");
+ return;
+ default:
+ break;
+ }
+ break;
+
+ case INTEGER:
+ switch (cast_type) {
+ case AMOUNT:
+ set_amount(as_long());
+ return;
+ case BALANCE:
+ set_balance(to_amount());
+ return;
+ case BALANCE_PAIR:
+ set_balance_pair(to_amount());
+ return;
+ case STRING:
+ set_string(lexical_cast<string>(as_long()));
+ return;
+ default:
+ break;
+ }
+ break;
+
+ case AMOUNT: {
+ const amount_t& amt(as_amount());
+ switch (cast_type) {
+ case INTEGER:
+ if (amt.is_null())
+ set_long(0L);
+ else
+ set_long(as_amount().to_long());
+ return;
+ case BALANCE:
+ if (amt.is_null())
+ set_balance(balance_t());
+ else
+ set_balance(as_amount()); // creates temporary
+ return;
+ case BALANCE_PAIR:
+ if (amt.is_null())
+ set_balance_pair(balance_pair_t());
+ else
+ set_balance_pair(as_amount()); // creates temporary
+ return;
+ case STRING:
+ if (amt.is_null())
+ set_string("");
+ else
+ set_string(as_amount().to_string());
+ return;
+ default:
+ break;
+ }
+ break;
+ }
+
+ case BALANCE:
+ switch (cast_type) {
+ case AMOUNT: {
+ const balance_t& temp(as_balance());
+ if (temp.amounts.size() == 1) {
+ // Because we are changing the current balance value to an amount
+ // value, and because set_amount takes a reference (and that memory is
+ // about to be repurposed), we must pass in a copy.
+ set_amount(amount_t((*temp.amounts.begin()).second));
+ return;
+ }
+ else if (temp.amounts.size() == 0) {
+ set_amount(0L);
+ return;
+ }
+ else {
+ throw_(value_error, "Cannot convert " << label() <<
+ " with multiple commodities to " << label(cast_type));
+ }
+ break;
+ }
+ case BALANCE_PAIR:
+ set_balance_pair(as_balance());
+ return;
+ default:
+ break;
+ }
+ break;
+
+ case BALANCE_PAIR:
+ switch (cast_type) {
+ case AMOUNT: {
+ const balance_t& temp(as_balance_pair().quantity());
+ if (temp.amounts.size() == 1) {
+ set_amount(amount_t((*temp.amounts.begin()).second));
+ return;
+ }
+ else if (temp.amounts.size() == 0) {
+ set_amount(0L);
+ return;
+ }
+ else {
+ throw_(value_error, "Cannot convert " << label() <<
+ " with multiple commodities to " << label(cast_type));
+ }
+ break;
+ }
+ case BALANCE:
+ // A temporary is required, becaues set_balance is going to wipe us out
+ // before assigned the value passed in.
+ set_balance(balance_t(as_balance_pair().quantity()));
+ return;
+ default:
+ break;
+ }
+ break;
+
+ case STRING:
+ switch (cast_type) {
+ case INTEGER: {
+ if (all(as_string(), is_digit())) {
+ set_long(lexical_cast<long>(as_string()));
+ return;
+ } else {
+ throw_(value_error,
+ "Cannot convert string '" << *this << "' to an integer");
+ }
+ break;
+ }
+ case AMOUNT:
+ set_amount(amount_t(as_string()));
+ return;
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ throw_(value_error,
+ "Cannot convert " << label() << " to " << label(cast_type));
+}
+
+void value_t::in_place_negate()
+{
+ switch (type()) {
+ case BOOLEAN:
+ set_boolean(! as_boolean());
+ return;
+ case INTEGER:
+ case DATETIME:
+ set_long(- as_long());
+ return;
+ case DATE:
+ set_long(- as_long());
+ return;
+ case AMOUNT:
+ as_amount_lval().in_place_negate();
+ return;
+ case BALANCE:
+ as_balance_lval().in_place_negate();
+ return;
+ case BALANCE_PAIR:
+ as_balance_pair_lval().in_place_negate();
+ return;
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot negate " << label());
+}
+
+bool value_t::is_realzero() const
+{
+ switch (type()) {
+ case BOOLEAN:
+ return ! as_boolean();
+ case INTEGER:
+ return as_long() == 0;
+ case DATETIME:
+ return ! is_valid(as_datetime());
+ case DATE:
+ return ! is_valid(as_date());
+ case AMOUNT:
+ return as_amount().is_realzero();
+ case BALANCE:
+ return as_balance().is_realzero();
+ case BALANCE_PAIR:
+ return as_balance_pair().is_realzero();
+ case STRING:
+ return as_string().empty();
+ case SEQUENCE:
+ return as_sequence().empty();
+
+ case POINTER:
+ return as_any_pointer().empty();
+
+ default:
+ assert(false);
+ break;
+ }
+ assert(false);
+ return true;
+}
+
+bool value_t::is_zero() const
+{
+ switch (type()) {
+ case BOOLEAN:
+ return ! as_boolean();
+ case INTEGER:
+ return as_long() == 0;
+ case DATETIME:
+ return ! is_valid(as_datetime());
+ case DATE:
+ return ! is_valid(as_date());
+ case AMOUNT:
+ return as_amount().is_zero();
+ case BALANCE:
+ return as_balance().is_zero();
+ case BALANCE_PAIR:
+ return as_balance_pair().is_zero();
+ case STRING:
+ return as_string().empty();
+ case SEQUENCE:
+ return as_sequence().empty();
+
+ case POINTER:
+ return as_any_pointer().empty();
+
+ default:
+ assert(false);
+ break;
+ }
+ assert(false);
+ return true;
+}
+
+value_t value_t::value(const optional<datetime_t>& moment) const
+{
+ switch (type()) {
+ case INTEGER:
+ return *this;
+
+ case AMOUNT: {
+ if (optional<amount_t> val = as_amount().value(moment))
+ return *val;
+ return false;
+ }
+ case BALANCE: {
+ if (optional<balance_t> bal = as_balance().value(moment))
+ return *bal;
+ return false;
+ }
+ case BALANCE_PAIR: {
+ if (optional<balance_t> bal_pair =
+ as_balance_pair().quantity().value(moment))
+ return *bal_pair;
+ return false;
+ }
+
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot find the value of " << label());
+ return NULL_VALUE;
+}
+
+void value_t::in_place_reduce()
+{
+ switch (type()) {
+ case INTEGER:
+ return;
+ case AMOUNT:
+ as_amount_lval().in_place_reduce();
+ return;
+ case BALANCE:
+ as_balance_lval().in_place_reduce();
+ return;
+ case BALANCE_PAIR:
+ as_balance_pair_lval().in_place_reduce();
+ return;
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot reduce " << label());
+}
+
+value_t value_t::abs() const
+{
+ switch (type()) {
+ case INTEGER: {
+ long val = as_long();
+ if (val < 0)
+ return - val;
+ return val;
+ }
+ case AMOUNT:
+ return as_amount().abs();
+ case BALANCE:
+ return as_balance().abs();
+ case BALANCE_PAIR:
+ return as_balance_pair().abs();
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot abs " << label());
+ return NULL_VALUE;
+}
+
+value_t value_t::round() const
+{
+ switch (type()) {
+ case INTEGER:
+ return *this;
+ case AMOUNT:
+ return as_amount().round();
+ case BALANCE:
+ return as_balance().round();
+ case BALANCE_PAIR:
+ return as_balance_pair().round();
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot round " << label());
+ return NULL_VALUE;
+}
+
+value_t value_t::unround() const
+{
+ switch (type()) {
+ case INTEGER:
+ return *this;
+ case AMOUNT:
+ return as_amount().unround();
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot unround " << label());
+ return NULL_VALUE;
+}
+
+#if 0
+value_t value_t::annotated_price() const
+{
+ switch (type()) {
+ case AMOUNT: {
+ optional<amount_t> temp = as_amount().annotation_details().price;
+ if (! temp)
+ return false;
+ return *temp;
+ }
+
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot find the annotated price of " << label());
+ return NULL_VALUE;
+}
+
+value_t value_t::annotated_date() const
+{
+ switch (type()) {
+ case DATETIME:
+ return *this;
+ case DATE:
+ return *this;
+
+ case AMOUNT: {
+ optional<datetime_t> temp = as_amount().annotation_details().date;
+ if (! temp)
+ return false;
+ return *temp;
+ }
+
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot find the annotated date of " << label());
+ return NULL_VALUE;
+}
+
+value_t value_t::annotated_tag() const
+{
+ switch (type()) {
+ case AMOUNT: {
+ optional<string> temp = as_amount().annotation_details().tag;
+ if (! temp)
+ return false;
+ return string_value(*temp);
+ }
+
+ case STRING:
+ return *this;
+
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot find the annotated tag of " << label());
+ return NULL_VALUE;
+}
+#endif
+
+value_t value_t::strip_annotations(const bool keep_price,
+ const bool keep_date,
+ const bool keep_tag) const
+{
+ switch (type()) {
+ case VOID:
+ case BOOLEAN:
+ case INTEGER:
+ case DATETIME:
+ case DATE:
+ case STRING:
+ case POINTER:
+ return *this;
+
+ case SEQUENCE: {
+ sequence_t temp;
+ foreach (const value_t& value, as_sequence())
+ temp.push_back(value.strip_annotations(keep_price, keep_date, keep_tag));
+ return temp;
+ }
+
+ case AMOUNT:
+ return as_amount().strip_annotations(keep_price, keep_date, keep_tag);
+ case BALANCE:
+ return as_balance().strip_annotations(keep_price, keep_date, keep_tag);
+ case BALANCE_PAIR:
+ return as_balance_pair().quantity().strip_annotations(keep_price, keep_date,
+ keep_tag);
+
+ default:
+ assert(false);
+ break;
+ }
+ assert(false);
+ return NULL_VALUE;
+}
+
+value_t value_t::cost() const
+{
+ switch (type()) {
+ case INTEGER:
+ case AMOUNT:
+ case BALANCE:
+ return *this;
+
+ case BALANCE_PAIR:
+ assert(as_balance_pair().cost);
+ if (as_balance_pair().cost)
+ return *(as_balance_pair().cost);
+ else
+ return as_balance_pair().quantity();
+
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot find the cost of " << label());
+ return NULL_VALUE;
+}
+
+value_t& value_t::add(const amount_t& amount, const optional<amount_t>& tcost)
+{
+ switch (type()) {
+ case INTEGER:
+ case AMOUNT:
+ if (tcost) {
+ in_place_cast(BALANCE_PAIR);
+ return add(amount, tcost);
+ }
+ else if ((is_amount() &&
+ as_amount().commodity() != amount.commodity()) ||
+ (! is_amount() && amount.commodity())) {
+ in_place_cast(BALANCE);
+ return add(amount, tcost);
+ }
+ else if (! is_amount()) {
+ in_place_cast(AMOUNT);
+ }
+ return *this += amount;
+
+ case BALANCE:
+ if (tcost) {
+ in_place_cast(BALANCE_PAIR);
+ return add(amount, tcost);
+ }
+ return *this += amount;
+
+ case BALANCE_PAIR:
+ as_balance_pair_lval().add(amount, tcost);
+ return *this;
+
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot add an amount to " << label());
+ return *this;
+}
+
+void value_t::dump(std::ostream& out, const int first_width,
+ const int latter_width) const
+{
+ switch (type()) {
+ case VOID:
+ out << "VOID";
+ break;
+
+ case BOOLEAN:
+ out << as_boolean();
+ break;
+
+ case DATETIME:
+ out << format_datetime(as_datetime());
+ break;
+
+ case DATE:
+ out << format_date(as_date());
+ break;
+
+ case INTEGER:
+ out << as_long();
+ break;
+
+ case AMOUNT:
+ out << as_amount();
+ break;
+
+ case STRING:
+ out << as_string();
+ break;
+
+ case POINTER:
+ out << boost::unsafe_any_cast<const void *>(&as_any_pointer());
+ break;
+
+ case SEQUENCE: {
+ out << '(';
+ bool first = true;
+ foreach (const value_t& value, as_sequence()) {
+ if (first)
+ first = false;
+ else
+ out << ", ";
+
+ value.dump(out, first_width, latter_width);
+ }
+ out << ')';
+ break;
+ }
+
+ case BALANCE:
+ as_balance().print(out, first_width, latter_width);
+ break;
+ case BALANCE_PAIR:
+ as_balance_pair().print(out, first_width, latter_width);
+ break;
+ default:
+ assert(false);
+ break;
+ }
+}
+
+void value_t::print(std::ostream& out, const bool relaxed) const
+{
+ switch (type()) {
+ case VOID:
+ out << "";
+ break;
+
+ case BOOLEAN:
+ if (as_boolean())
+ out << "true";
+ else
+ out << "false";
+ break;
+
+ case INTEGER:
+ out << as_long();
+ break;
+
+ case AMOUNT:
+ if (! relaxed)
+ out << '{';
+ out << as_amount();
+ if (! relaxed)
+ out << '}';
+ break;
+
+ case BALANCE:
+ case BALANCE_PAIR:
+ assert(false);
+ break;
+
+ case DATETIME:
+ assert(false);
+ break;
+ case DATE:
+ out << '[' << format_date(as_date()) << ']';
+ break;
+
+ case STRING:
+ out << '"' << as_string() << '"';
+ break;
+
+ case POINTER:
+ assert(false);
+ break;
+
+ case SEQUENCE: {
+ out << '(';
+ bool first = true;
+ foreach (const value_t& value, as_sequence()) {
+ if (first)
+ first = false;
+ else
+ out << ", ";
+
+ value.print(out, relaxed);
+ }
+ out << ')';
+ break;
+ }
+ }
+}
+
+void value_t::read(const char *& data)
+{
+ switch (static_cast<value_t::type_t>(binary::read_long<int>(data))) {
+ case BOOLEAN:
+ set_boolean(binary::read_bool(data));
+ break;
+ case INTEGER:
+ set_long(binary::read_long<unsigned long>(data));
+ break;
+ case DATETIME:
+#if 0
+ // jww (2008-04-22): I need to record and read a datetime_t directly
+ set_datetime(read_long<unsigned long>(data));
+#endif
+ break;
+ case DATE:
+#if 0
+ // jww (2008-04-22): I need to record and read a date_t directly
+ set_date(read_long<unsigned long>(data));
+#endif
+ break;
+ case AMOUNT: {
+ amount_t temp;
+ temp.read(data);
+ set_amount(temp);
+ break;
+ }
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot read " << label() << " from a stream");
+}
+
+void value_t::write(std::ostream& out) const
+{
+ binary::write_long(out, static_cast<int>(type()));
+
+ switch (type()) {
+ case BOOLEAN:
+ binary::write_bool(out, as_boolean());
+ break;
+ case INTEGER:
+ binary::write_long(out, as_long());
+ break;
+ case DATETIME:
+#if 0
+ binary::write_number(out, as_datetime());
+#endif
+ break;
+ case DATE:
+#if 0
+ binary::write_number(out, as_date());
+#endif
+ break;
+ case AMOUNT:
+ as_amount().write(out);
+ break;
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot read " << label() << " to a stream");
+}
+
+bool value_t::valid() const
+{
+ switch (type()) {
+ case AMOUNT:
+ return as_amount().valid();
+ case BALANCE:
+ return as_balance().valid();
+ case BALANCE_PAIR:
+ return as_balance_pair().valid();
+ default:
+ break;
+ }
+ return true;
+}
+
+} // namespace ledger
diff --git a/src/value.h b/src/value.h
new file mode 100644
index 00000000..4754de61
--- /dev/null
+++ b/src/value.h
@@ -0,0 +1,922 @@
+/*
+ * Copyright (c) 2003-2008, 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 value.h
+ * @author John Wiegley
+ * @date Thu Jun 14 21:54:00 2007
+ *
+ * @brief Abstract dynamic type representing various numeric types.
+ *
+ * A value_t object can be one of many types, and changes its type
+ * dynamically based on how it is used. For example, if you assign
+ * the number 10 to a value object, it's internal type will be
+ * INTEGER.
+ */
+#ifndef _VALUE_H
+#define _VALUE_H
+
+#include "balpair.h" // pulls in balance.h and amount.h
+
+namespace ledger {
+
+DECLARE_EXCEPTION(value_error, std::runtime_error);
+
+/**
+ * @class value_t
+ *
+ * @brief Dynamic type representing various numeric types.
+ *
+ * The following type is a polymorphous value type used solely for
+ * performance reasons. The alternative is to compute value
+ * expressions (valexpr.cc) in terms of the largest data type,
+ * balance_t. This was found to be prohibitively expensive, especially
+ * when large logic chains were involved, since many temporary
+ * allocations would occur for every operator. With value_t, and the
+ * fact that logic chains only need boolean values to continue, no
+ * memory allocations need to take place at all.
+ */
+class value_t
+ : public ordered_field_operators<value_t,
+ equality_comparable<value_t, balance_pair_t,
+ equality_comparable<value_t, balance_t,
+ additive<value_t, balance_pair_t,
+ additive<value_t, balance_t,
+ multiplicative<value_t, balance_pair_t,
+ multiplicative<value_t, balance_t,
+ ordered_field_operators<value_t, amount_t,
+#ifdef HAVE_GDTOA
+ ordered_field_operators<value_t, double,
+#endif
+ ordered_field_operators<value_t, unsigned long,
+ ordered_field_operators<value_t, long> > > > > > > > > >
+#ifdef HAVE_GDTOA
+ >
+#endif
+{
+public:
+ /**
+ * The sequence_t member type abstracts the type used to represent a
+ * resizable "array" of value_t objects.
+ */
+ typedef std::vector<value_t> sequence_t;
+
+ typedef sequence_t::iterator iterator;
+ typedef sequence_t::const_iterator const_iterator;
+ typedef sequence_t::difference_type difference_type;
+
+ /**
+ * type_t gives the type of the data contained or referenced by a
+ * value_t object. Use the type() method to get a value of type
+ * type_t.
+ */
+ enum type_t {
+ VOID, // a null value (i.e., uninitialized)
+ BOOLEAN, // a boolean
+ DATETIME, // a date and time (Boost posix_time)
+ DATE, // a date (Boost gregorian::date)
+ INTEGER, // a signed integer value
+ AMOUNT, // a ledger::amount_t
+ BALANCE, // a ledger::balance_t
+ BALANCE_PAIR, // a ledger::balance_pair_t
+ STRING, // a string object
+ SEQUENCE, // a vector of value_t objects
+ POINTER // an opaque pointer of any type
+ };
+
+private:
+ class storage_t
+ {
+ friend class value_t;
+
+ /**
+ * The `data' member holds the actual bytes relating to whatever
+ * has been stuffed into this storage object. There is a set of
+ * asserts in value.cc to guarantee that the sizeof expression
+ * used here is indeed at least as big as the largest object that
+ * will ever be copied into `data'.
+ *
+ * The `type' member holds the value_t::type_t value representing
+ * the type of the object stored.
+ */
+ char data[sizeof(amount_t)];
+ type_t type;
+
+ /**
+ * `refc' holds the current reference count for each storage_t
+ * object.
+ */
+ mutable int refc;
+
+ /**
+ * Constructor. Since all storage object are assigned to after
+ * construction, the only constructors allowed are explicit, and
+ * copy (see below). The default starting type is VOID, which
+ * should rarely ever be seen in practice, since the first thing
+ * that value_t typically does is to assign a valid value.
+ */
+ explicit storage_t() : type(VOID), refc(0) {
+ TRACE_CTOR(value_t::storage_t, "");
+ }
+
+ public: // so `checked_delete' can access it
+ /**
+ * Destructor. Must only be called when the reference count has
+ * reached zero. The `destroy' method is used to do the actual
+ * cleanup of the data, since it's quite possible for `destroy' to
+ * be called while the object is still active -- to clear the
+ * stored data for subsequent reuse of the storage_t object.
+ */
+ ~storage_t() {
+ TRACE_DTOR(value_t::storage_t);
+ DEBUG("value.storage.refcount", "Destroying " << this);
+ assert(refc == 0);
+ destroy();
+ }
+
+ void destroy();
+
+ private:
+ /**
+ * Assignment and copy operators. These are called when making a
+ * new copy of a storage object in order to modify the copy.
+ */
+ explicit storage_t(const storage_t& rhs)
+ : type(rhs.type), refc(0) {
+ TRACE_CTOR(value_t::storage_t, "copy");
+ *this = rhs;
+ }
+ storage_t& operator=(const storage_t& rhs);
+
+ /**
+ * Reference counting methods. The intrusive_ptr_* methods are
+ * used by boost::intrusive_ptr to manage the calls to acquire and
+ * release.
+ */
+ void acquire() const {
+ DEBUG("value.storage.refcount",
+ "Acquiring " << this << ", refc now " << refc + 1);
+ assert(refc >= 0);
+ refc++;
+ }
+ void release() const {
+ DEBUG("value.storage.refcount",
+ "Releasing " << this << ", refc now " << refc - 1);
+ assert(refc > 0);
+ if (--refc == 0)
+ checked_delete(this);
+ }
+
+ friend inline void intrusive_ptr_add_ref(value_t::storage_t * storage) {
+ storage->acquire();
+ }
+ friend inline void intrusive_ptr_release(value_t::storage_t * storage) {
+ storage->release();
+ }
+ };
+
+ /**
+ * The actual data for each value_t is kept in the `storage' member.
+ * Data is modified using a copy-on-write policy.
+ */
+ intrusive_ptr<storage_t> storage;
+
+ /**
+ * _dup() makes a private copy of the current value (if necessary)
+ * so it can subsequently be modified.
+ *
+ * _clear() removes our pointer to the current value and initializes
+ * a new storage bin for things to be stored in.
+ *
+ * _reset() makes the current object appear as if it were
+ * uninitialized.
+ */
+ void _dup();
+ void _clear() {
+ if (! storage || storage->refc > 1)
+ storage = new storage_t;
+ else
+ storage->destroy();
+ }
+ void _reset() {
+ if (storage)
+ storage = intrusive_ptr<storage_t>();
+ }
+
+ /**
+ * Because boolean "true" and "false" are so common, a pair of
+ * static references are kept to prevent the creation of throwaway
+ * storage_t objects just to represent these two common values.
+ */
+ static intrusive_ptr<storage_t> true_value;
+ static intrusive_ptr<storage_t> false_value;
+
+public:
+ // jww (2007-05-03): Make these private, and make ledger::initialize
+ // a member function of session_t.
+ static void initialize();
+ static void shutdown();
+
+public:
+ /**
+ * Constructors. value_t objects may be constructed from almost any
+ * value type that they can contain, including variations on those
+ * types (such as long, unsigned long, etc). The ordering of the
+ * methods here reflects the ordering of the constants in type_t
+ * above.
+ *
+ * One constructor of special note is that taking a string or
+ * character pointer as an argument. Because value_t("$100") is
+ * interpreted as a commoditized amount, the form value_t("$100",
+ * true) is required to represent the literal string "$100", and not
+ * the amount "one hundred dollars".
+ */
+ value_t() {
+ TRACE_CTOR(value_t, "");
+ }
+
+ value_t(const bool val) {
+ TRACE_CTOR(value_t, "const bool");
+ set_boolean(val);
+ }
+
+ value_t(const datetime_t& val) {
+ TRACE_CTOR(value_t, "const datetime_t&");
+ set_datetime(val);
+ }
+ value_t(const date_t& val) {
+ TRACE_CTOR(value_t, "const date_t&");
+ set_date(val);
+ }
+
+ value_t(const long val) {
+ TRACE_CTOR(value_t, "const long");
+ set_long(val);
+ }
+ value_t(const unsigned long val) {
+ TRACE_CTOR(value_t, "const unsigned long");
+ set_amount(val);
+ }
+#ifdef HAVE_GDTOA
+ value_t(const double val) {
+ TRACE_CTOR(value_t, "const double");
+ set_amount(val);
+ }
+#endif
+ value_t(const amount_t& val) {
+ TRACE_CTOR(value_t, "const amount_t&");
+ set_amount(val);
+ }
+ value_t(const balance_t& val) {
+ TRACE_CTOR(value_t, "const balance_t&");
+ set_balance(val);
+ }
+ value_t(const balance_pair_t& val) {
+ TRACE_CTOR(value_t, "const balance_pair_t&");
+ set_balance_pair(val);
+ }
+
+ explicit value_t(const string& val, bool literal = false) {
+ TRACE_CTOR(value_t, "const string&, bool");
+ if (literal)
+ set_string(val);
+ else
+ set_amount(amount_t(val));
+ }
+ explicit value_t(const char * val, bool literal = false) {
+ TRACE_CTOR(value_t, "const char *");
+ if (literal)
+ set_string(val);
+ else
+ set_amount(amount_t(val));
+ }
+
+ value_t(const sequence_t& val) {
+ TRACE_CTOR(value_t, "const sequence_t&");
+ set_sequence(val);
+ }
+
+ template <typename T>
+ explicit value_t(T * item) {
+ TRACE_CTOR(value_t, "T *");
+ set_pointer(item);
+ }
+
+ /**
+ * Destructor. This does not do anything, because the intrusive_ptr
+ * that refers to our storage object will decrease its reference
+ * count itself upon destruction.
+ */
+ ~value_t() {
+ TRACE_DTOR(value_t);
+ }
+
+ /**
+ * Assignment and copy operators. Values are cheaply copied by
+ * simply creating another reference to the other value's storage
+ * object. A true copy is only ever made prior to modification.
+ */
+ value_t(const value_t& val) {
+ TRACE_CTOR(value_t, "copy");
+ *this = val;
+ }
+ value_t& operator=(const value_t& val) {
+ if (! (this == &val || storage == val.storage))
+ storage = val.storage;
+ return *this;
+ }
+
+ /**
+ * Comparison operators. Values can be compared to other values
+ */
+ bool operator==(const value_t& val) const;
+ bool operator<(const value_t& val) const;
+
+ template <typename T>
+ bool operator==(const T& amt) const {
+ return *this == value_t(amt);
+ }
+ template <typename T>
+ bool operator<(const T& amt) const {
+ return *this < value_t(amt);
+ }
+
+ /**
+ * Binary arithmetic operators.
+ *
+ * add(amount_t, optional<amount_t>) allows for the possibility of
+ * adding both an amount and its cost in a single operation.
+ * Otherwise, there is no way to separately represent the "cost"
+ * part of an amount addition statement.
+ */
+ value_t& operator+=(const value_t& val);
+ value_t& operator-=(const value_t& val);
+ value_t& operator*=(const value_t& val);
+ value_t& operator/=(const value_t& val);
+
+ // This special form of add is use to produce a balance pair by
+ // simultaneously adding both an amount and its cost.
+ value_t& add(const amount_t& amount,
+ const optional<amount_t>& cost = none);
+
+ /**
+ * Unary arithmetic operators.
+ */
+ value_t negate() const {
+ value_t temp = *this;
+ temp.in_place_negate();
+ return temp;
+ }
+ void in_place_negate(); // exists for efficiency's sake
+
+ value_t operator-() const {
+ return negate();
+ }
+
+ value_t abs() const;
+ value_t round() const;
+ value_t unround() const;
+
+ value_t reduce() const {
+ value_t temp(*this);
+ temp.in_place_reduce();
+ return temp;
+ }
+ void in_place_reduce(); // exists for efficiency's sake
+
+ // Return the "market value" of a given value at a specific time.
+ value_t value(const optional<datetime_t>& moment = none) const;
+ value_t cost() const;
+
+
+ /**
+ * Truth tests.
+ */
+ operator bool() const;
+
+ bool is_realzero() const;
+ bool is_zero() const;
+ bool is_null() const {
+ if (! storage) {
+ assert(is_type(VOID));
+ return true;
+ } else {
+ assert(! is_type(VOID));
+ return false;
+ }
+ }
+
+ type_t type() const {
+ type_t result = storage ? storage->type : VOID;
+ assert(result >= VOID && result <= POINTER);
+ return result;
+ }
+ bool is_type(type_t _type) const {
+ return type() == _type;
+ }
+
+private:
+ void set_type(type_t new_type) {
+ assert(new_type >= VOID && new_type <= POINTER);
+ if (new_type == VOID) {
+ _reset();
+ assert(is_null());
+ } else {
+ _clear();
+ storage->type = new_type;
+ assert(is_type(new_type));
+ }
+ }
+
+public:
+ /**
+ * Data manipulation methods. A value object may be truth tested for the
+ * existence of every type it can contain:
+ *
+ * is_boolean()
+ * is_long()
+ * is_datetime()
+ * is_date()
+ * is_amount()
+ * is_balance()
+ * is_balance_pair()
+ * is_string()
+ * is_sequence()
+ * is_pointer()
+ *
+ * There are corresponding as_*() methods that represent a value as a
+ * reference to its underlying type. For example, as_long() returns a
+ * reference to a "const long".
+ *
+ * There are also as_*_lval() methods, which represent the underlying data
+ * as a reference to a non-const type. The difference here is that an
+ * _lval() call causes the underlying data to be fully copied before the
+ * resulting reference is returned.
+ *
+ * Lastly, there are corresponding set_*(data) methods for directly
+ * assigning data of a particular type, rather than using the regular
+ * assignment operator (whose implementation simply calls the various set_
+ * methods).
+ */
+ bool is_boolean() const {
+ return is_type(BOOLEAN);
+ }
+ bool& as_boolean_lval() {
+ assert(is_boolean());
+ _dup();
+ return *reinterpret_cast<bool *>(storage->data);
+ }
+ const bool& as_boolean() const {
+ assert(is_boolean());
+ return *reinterpret_cast<bool *>(storage->data);
+ }
+ void set_boolean(const bool val) {
+ set_type(BOOLEAN);
+ storage = val ? true_value : false_value;
+ }
+
+ bool is_datetime() const {
+ return is_type(DATETIME);
+ }
+ datetime_t& as_datetime_lval() {
+ assert(is_datetime());
+ _dup();
+ return *reinterpret_cast<datetime_t *>(storage->data);
+ }
+ const datetime_t& as_datetime() const {
+ assert(is_datetime());
+ return *reinterpret_cast<datetime_t *>(storage->data);
+ }
+ void set_datetime(const datetime_t& val) {
+ set_type(DATETIME);
+ new(reinterpret_cast<datetime_t *>(storage->data)) datetime_t(val);
+ }
+
+ bool is_date() const {
+ return is_type(DATE);
+ }
+ date_t& as_date_lval() {
+ assert(is_date());
+ _dup();
+ return *reinterpret_cast<date_t *>(storage->data);
+ }
+ const date_t& as_date() const {
+ assert(is_date());
+ return *reinterpret_cast<date_t *>(storage->data);
+ }
+ void set_date(const date_t& val) {
+ set_type(DATE);
+ new(reinterpret_cast<date_t *>(storage->data)) date_t(val);
+ }
+
+ bool is_long() const {
+ return is_type(INTEGER);
+ }
+ long& as_long_lval() {
+ assert(is_long());
+ _dup();
+ return *reinterpret_cast<long *>(storage->data);
+ }
+ const long& as_long() const {
+ assert(is_long());
+ return *reinterpret_cast<long *>(storage->data);
+ }
+ void set_long(const long val) {
+ set_type(INTEGER);
+ *reinterpret_cast<long *>(storage->data) = val;
+ }
+
+ bool is_amount() const {
+ return is_type(AMOUNT);
+ }
+ amount_t& as_amount_lval() {
+ assert(is_amount());
+ _dup();
+ amount_t& amt(*reinterpret_cast<amount_t *>(storage->data));
+ assert(amt.valid());
+ return amt;
+ }
+ const amount_t& as_amount() const {
+ assert(is_amount());
+ amount_t& amt(*reinterpret_cast<amount_t *>(storage->data));
+ assert(amt.valid());
+ return amt;
+ }
+ void set_amount(const amount_t& val) {
+ assert(val.valid());
+ set_type(AMOUNT);
+ new(reinterpret_cast<amount_t *>(storage->data)) amount_t(val);
+ }
+
+ bool is_balance() const {
+ return is_type(BALANCE);
+ }
+ balance_t& as_balance_lval() {
+ assert(is_balance());
+ _dup();
+ balance_t& bal(**reinterpret_cast<balance_t **>(storage->data));
+ assert(bal.valid());
+ return bal;
+ }
+ const balance_t& as_balance() const {
+ assert(is_balance());
+ balance_t& bal(**reinterpret_cast<balance_t **>(storage->data));
+ assert(bal.valid());
+ return bal;
+ }
+ void set_balance(const balance_t& val) {
+ assert(val.valid());
+ set_type(BALANCE);
+ *reinterpret_cast<balance_t **>(storage->data) = new balance_t(val);
+ }
+
+ bool is_balance_pair() const {
+ return is_type(BALANCE_PAIR);
+ }
+ balance_pair_t& as_balance_pair_lval() {
+ assert(is_balance_pair());
+ _dup();
+ balance_pair_t& bal_pair(**reinterpret_cast<balance_pair_t **>(storage->data));
+ assert(bal_pair.valid());
+ return bal_pair;
+ }
+ const balance_pair_t& as_balance_pair() const {
+ assert(is_balance_pair());
+ balance_pair_t& bal_pair(**reinterpret_cast<balance_pair_t **>(storage->data));
+ assert(bal_pair.valid());
+ return bal_pair;
+ }
+ void set_balance_pair(const balance_pair_t& val) {
+ assert(val.valid());
+ set_type(BALANCE_PAIR);
+ *reinterpret_cast<balance_pair_t **>(storage->data) = new balance_pair_t(val);
+ }
+
+ bool is_string() const {
+ return is_type(STRING);
+ }
+ string& as_string_lval() {
+ assert(is_string());
+ _dup();
+ return *reinterpret_cast<string *>(storage->data);
+ }
+ const string& as_string() const {
+ assert(is_string());
+ return *reinterpret_cast<string *>(storage->data);
+ }
+ void set_string(const string& val = "") {
+ set_type(STRING);
+ new(reinterpret_cast<string *>(storage->data)) string(val);
+ }
+ void set_string(const char * val = "") {
+ set_type(STRING);
+ new(reinterpret_cast<string *>(storage->data)) string(val);
+ }
+
+ bool is_sequence() const {
+ return is_type(SEQUENCE);
+ }
+ sequence_t& as_sequence_lval() {
+ assert(is_sequence());
+ _dup();
+ return **reinterpret_cast<sequence_t **>(storage->data);
+ }
+ const sequence_t& as_sequence() const {
+ assert(is_sequence());
+ return **reinterpret_cast<sequence_t **>(storage->data);
+ }
+ void set_sequence(const sequence_t& val) {
+ set_type(SEQUENCE);
+ *reinterpret_cast<sequence_t **>(storage->data) = new sequence_t(val);
+ }
+
+ /**
+ * Dealing with pointers is bit involved because we actually deal
+ * with typed pointers. For example, if you call as_pointer it
+ * returns a boost::any object, but if you use as_pointer<void>,
+ * then it returns a void *. The latter form only succeeds if the
+ * stored pointers was assigned to the value as a void*, otherwise
+ * it throws an exception.
+ */
+ bool is_pointer() const {
+ return is_type(POINTER);
+ }
+ boost::any& as_any_pointer_lval() {
+ assert(is_pointer());
+ _dup();
+ return *reinterpret_cast<boost::any *>(storage->data);
+ }
+ template <typename T>
+ T * as_pointer_lval() {
+ assert(is_pointer());
+ _dup();
+ return any_cast<T *>(*reinterpret_cast<boost::any *>(storage->data));
+ }
+ template <typename T>
+ T& as_ref_lval() {
+ assert(is_pointer());
+ _dup();
+ return *any_cast<T *>(*reinterpret_cast<boost::any *>(storage->data));
+ }
+ const boost::any& as_any_pointer() const {
+ assert(is_pointer());
+ return *reinterpret_cast<boost::any *>(storage->data);
+ }
+ template <typename T>
+ T * as_pointer() const {
+ assert(is_pointer());
+ return any_cast<T *>(*reinterpret_cast<boost::any *>(storage->data));
+ }
+ template <typename T>
+ T& as_ref() const {
+ assert(is_pointer());
+ return *any_cast<T *>(*reinterpret_cast<boost::any *>(storage->data));
+ }
+ void set_any_pointer(const boost::any& val) {
+ set_type(POINTER);
+ new(reinterpret_cast<boost::any *>(storage->data)) boost::any(val);
+ }
+ template <typename T>
+ void set_pointer(T * val) {
+ set_type(POINTER);
+ new(reinterpret_cast<boost::any *>(storage->data)) boost::any(val);
+ }
+
+ /**
+ * Data conversion methods. These methods convert a value object to
+ * its underlying type, where possible. If not possible, an
+ * exception is thrown.
+ */
+ bool to_boolean() const;
+ long to_long() const;
+ datetime_t to_datetime() const;
+ date_t to_date() const;
+ amount_t to_amount() const;
+ balance_t to_balance() const;
+ balance_pair_t to_balance_pair() const;
+ string to_string() const;
+ sequence_t to_sequence() const;
+
+ /**
+ * Dynamic typing conversion methods.
+ *
+ * `cast(type_t)' returns a new value whose type has been cast to
+ * the given type, but whose value is based on the original value.
+ * For example, the uncommoditized AMOUNT "100.00" could be cast to
+ * an INTEGER value. If a cast would lose information or is not
+ * meaningful, an exception is thrown.
+ *
+ * `simplify()' is an automatic cast to the simplest type that can
+ * still represent the original value.
+ *
+ * There are also "in-place" versions of these two methods:
+ * in_place_cast
+ * in_place_simplify
+ */
+ value_t cast(type_t cast_type) const {
+ value_t temp(*this);
+ temp.in_place_cast(cast_type);
+ return temp;
+ }
+ void in_place_cast(type_t cast_type);
+
+ value_t simplify() const {
+ value_t temp = *this;
+ temp.in_place_simplify();
+ return temp;
+ }
+ void in_place_simplify();
+
+ /**
+ * Annotated commodity methods.
+ */
+#if 0
+ // These helper methods only apply to AMOUNT values.
+ value_t annotated_price() const;
+ value_t annotated_date() const;
+ value_t annotated_tag() const;
+#endif
+
+ value_t strip_annotations(const bool keep_price = amount_t::keep_price,
+ const bool keep_date = amount_t::keep_date,
+ const bool keep_tag = amount_t::keep_tag) const;
+
+ /**
+ * Collection-style access methods for SEQUENCE values.
+ */
+ value_t& operator[](const int index) {
+ assert(! is_null());
+ if (is_sequence())
+ return as_sequence_lval()[index];
+ else if (index == 0)
+ return *this;
+
+ assert(false);
+ static value_t null;
+ return null;
+ }
+ const value_t& operator[](const int index) const {
+ assert(! is_null());
+ if (is_sequence())
+ return as_sequence()[index];
+ else if (index == 0)
+ return *this;
+
+ assert(false);
+ static value_t null;
+ return null;
+ }
+
+ void push_back(const value_t& val) {
+ if (! val.is_null()) {
+ if (is_null()) {
+ *this = val;
+ } else {
+ if (! is_sequence())
+ in_place_cast(SEQUENCE);
+
+ if (! val.is_sequence()) {
+ if (! val.is_null())
+ as_sequence_lval().push_back(val);
+ } else {
+ const value_t::sequence_t& val_seq(val.as_sequence());
+ std::copy(val_seq.begin(), val_seq.end(),
+ back_inserter(as_sequence_lval()));
+ }
+ }
+ }
+ }
+
+ void pop_back() {
+ assert(! is_null());
+
+ if (! is_sequence()) {
+ _reset();
+ } else {
+ as_sequence_lval().pop_back();
+
+ const value_t::sequence_t& seq(as_sequence());
+ std::size_t new_size = seq.size();
+ if (new_size == 0)
+ _reset();
+ else if (new_size == 1)
+ *this = seq.front();
+ }
+ }
+
+ const std::size_t size() const {
+ if (is_null())
+ return 0;
+ else if (is_sequence())
+ return as_sequence().size();
+ else
+ return 1;
+ }
+
+ /**
+ * Informational methods.
+ */
+ string label(optional<type_t> the_type = none) const {
+ switch (the_type ? *the_type : type()) {
+ case VOID:
+ return "an uninitialized value";
+ case BOOLEAN:
+ return "a boolean";
+ case DATETIME:
+ return "a date/time";
+ case DATE:
+ return "a date";
+ case INTEGER:
+ return "an integer";
+ case AMOUNT:
+ return "an amount";
+ case BALANCE:
+ return "a balance";
+ case BALANCE_PAIR:
+ return "a balance pair";
+ case STRING:
+ return "a string";
+ case SEQUENCE:
+ return "a sequence";
+ case POINTER:
+ return "a pointer";
+ default:
+ assert(false);
+ break;
+ }
+ assert(false);
+ return "<invalid>";
+ }
+
+ /**
+ * Printing methods.
+ */
+ void dump(std::ostream& out, const int first_width,
+ const int latter_width = -1) const;
+ void print(std::ostream& out, const bool relaxed = true) const;
+
+ /**
+ * Serialization methods. A value may be deserialized from an input
+ * stream or a character pointer, and it may be serialized to an
+ * output stream. The methods used are:
+ */
+ void read(const char *& data);
+ void write(std::ostream& out) const;
+
+ /**
+ * Debugging methods.
+ */
+ bool valid() const;
+};
+
+#define NULL_VALUE (value_t())
+
+inline value_t string_value(const string& str) {
+ return value_t(str, true);
+}
+
+inline std::ostream& operator<<(std::ostream& out, const value_t& val) {
+ val.print(out, 12);
+ return out;
+}
+
+inline string value_context(const value_t& val) {
+ std::ostringstream buf;
+ buf << std::right;
+ buf.width(20);
+ val.print(buf);
+ buf << std::endl;
+ return buf.str();
+}
+
+} // namespace ledger
+
+#endif // _VALUE_H
diff --git a/src/xact.cc b/src/xact.cc
new file mode 100644
index 00000000..ce4da0d7
--- /dev/null
+++ b/src/xact.cc
@@ -0,0 +1,278 @@
+/*
+ * Copyright (c) 2003-2008, 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 "xact.h"
+#include "journal.h"
+#include "account.h"
+#include "format.h"
+
+namespace ledger {
+
+bool xact_t::use_effective_date = false;
+
+xact_t::~xact_t()
+{
+ TRACE_DTOR(xact_t);
+}
+
+date_t xact_t::actual_date() const
+{
+ if (! _date && entry)
+ return entry->actual_date();
+ return *_date;
+}
+
+date_t xact_t::effective_date() const
+{
+ if (! _date_eff && entry)
+ return entry->effective_date();
+ return *_date_eff;
+}
+
+namespace {
+ value_t get_state(xact_t& xact) {
+ return long(xact.state);
+ }
+
+ value_t state_uncleared(call_scope_t&) {
+ return 0L;
+ }
+
+ value_t state_cleared(call_scope_t&) {
+ return 1L;
+ }
+
+ value_t state_pending(call_scope_t&) {
+ return 2L;
+ }
+
+ value_t get_date(xact_t& xact) {
+ return xact.date();
+ }
+
+ value_t get_payee(xact_t& xact) {
+ return string_value(xact.entry->payee);
+ }
+
+ value_t get_amount(xact_t& xact) {
+ return xact.amount;
+ }
+
+ value_t get_total(xact_t& xact) {
+ if (xact.xdata_)
+ return xact.xdata_->total;
+ else
+ return xact.amount;
+ }
+
+ value_t get_cost(xact_t& xact) {
+ return xact.cost ? *xact.cost : xact.amount;
+ }
+
+ value_t get_note(xact_t& xact) {
+ return string_value(xact.note ? *xact.note : ":NOTELESS:");
+ }
+
+ value_t get_account(call_scope_t& scope)
+ {
+ xact_t& xact(downcast<xact_t>(*scope.parent));
+
+ var_t<long> max_width(scope, 0);
+
+ string name = xact.reported_account()->fullname();
+
+ if (max_width && *max_width > 2)
+ name = format_t::truncate(name, *max_width - 2, true);
+
+ if (xact.has_flags(XACT_VIRTUAL)) {
+ if (xact.must_balance())
+ name = string("[") + name + "]";
+ else
+ name = string("(") + name + ")";
+ }
+ return string_value(name);
+ }
+
+ value_t get_account_base(xact_t& xact) {
+ return string_value(xact.reported_account()->name);
+ }
+
+ value_t get_beg_pos(xact_t& xact) {
+ return long(xact.beg_pos);
+ }
+
+ value_t get_beg_line(xact_t& xact) {
+ return long(xact.beg_line);
+ }
+
+ value_t get_end_pos(xact_t& xact) {
+ return long(xact.end_pos);
+ }
+
+ value_t get_end_line(xact_t& xact) {
+ return long(xact.end_line);
+ }
+
+ template <value_t (*Func)(xact_t&)>
+ value_t get_wrapper(call_scope_t& scope) {
+ return (*Func)(find_scope<xact_t>(scope));
+ }
+}
+
+expr_t::ptr_op_t xact_t::lookup(const string& name)
+{
+ switch (name[0]) {
+ case 'a':
+ if (name[1] == '\0' || name == "amount")
+ return WRAP_FUNCTOR(get_wrapper<&get_amount>);
+ else if (name == "account")
+ return WRAP_FUNCTOR(get_account);
+ else if (name == "account_base")
+ return WRAP_FUNCTOR(get_wrapper<&get_account_base>);
+ break;
+
+ case 'c':
+ if (name == "cleared")
+ return expr_t::op_t::wrap_value(0L);
+ break;
+
+ case 'd':
+ if (name[1] == '\0' || name == "date")
+ return WRAP_FUNCTOR(get_wrapper<&get_date>);
+ break;
+
+ case 'f':
+ if (name.find("fmt_") == 0) {
+ switch (name[4]) {
+ case 'A':
+ return WRAP_FUNCTOR(get_account);
+ case 'D':
+ return WRAP_FUNCTOR(get_wrapper<&get_date>);
+ case 'P':
+ return WRAP_FUNCTOR(get_wrapper<&get_payee>);
+ }
+ }
+ break;
+
+ case 'p':
+ if (name == "pending")
+ return expr_t::op_t::wrap_value(2L);
+ else if (name == "payee")
+ return WRAP_FUNCTOR(get_wrapper<&get_payee>);
+ break;
+
+ case 't':
+ if (name[1] == '\0' || name == "total")
+ return WRAP_FUNCTOR(get_wrapper<&get_total>);
+ break;
+
+ case 'u':
+ if (name == "uncleared")
+ return expr_t::op_t::wrap_value(1L);
+ break;
+ }
+ return entry->lookup(name);
+}
+
+bool xact_t::valid() const
+{
+ if (! entry) {
+ DEBUG("ledger.validate", "xact_t: ! entry");
+ return false;
+ }
+
+ if (state != UNCLEARED && state != CLEARED && state != PENDING) {
+ DEBUG("ledger.validate", "xact_t: state is bad");
+ return false;
+ }
+
+ xacts_list::const_iterator i =
+ std::find(entry->xacts.begin(),
+ entry->xacts.end(), this);
+ if (i == entry->xacts.end()) {
+ DEBUG("ledger.validate", "xact_t: ! found");
+ return false;
+ }
+
+ if (! account) {
+ DEBUG("ledger.validate", "xact_t: ! account");
+ return false;
+ }
+
+ if (! amount.valid()) {
+ DEBUG("ledger.validate", "xact_t: ! amount.valid()");
+ return false;
+ }
+
+ if (cost && ! cost->valid()) {
+ DEBUG("ledger.validate", "xact_t: cost && ! cost->valid()");
+ return false;
+ }
+
+ if (flags() & ~0x003f) {
+ DEBUG("ledger.validate", "xact_t: flags are bad");
+ return false;
+ }
+
+ return true;
+}
+
+#if 0
+xact_context::xact_context(const xact_t& _xact, const string& desc) throw()
+ : file_context("", 0, desc), xact(_xact)
+{
+ const paths_list& sources(xact.entry->journal->sources);
+ unsigned int x = 0;
+ foreach (const path& path, sources)
+ if (x == xact.entry->src_idx) {
+ file = path;
+ break;
+ }
+ line = xact.beg_line;
+}
+#endif
+
+void xact_t::add_to_value(value_t& value)
+{
+ if (xdata_ && xdata_->has_flags(XACT_EXT_COMPOUND)) {
+ value += xdata_->value;
+ }
+ else if (cost || (! value.is_null() && ! value.is_realzero())) {
+ if (value.is_null())
+ value = amount_t();
+ value.add(amount, cost);
+ }
+ else {
+ value = amount;
+ }
+}
+
+} // namespace ledger
diff --git a/src/xact.h b/src/xact.h
new file mode 100644
index 00000000..5d4950f9
--- /dev/null
+++ b/src/xact.h
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2003-2008, 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 _XACT_H
+#define _XACT_H
+
+#include "utils.h"
+#include "scope.h"
+
+namespace ledger {
+
+class entry_t;
+class account_t;
+
+class xact_t;
+typedef std::list<xact_t *> xacts_list;
+
+class xact_t : public supports_flags<>, public scope_t
+{
+public:
+#define XACT_NORMAL 0x0000 // no flags at all, a basic transaction
+#define XACT_VIRTUAL 0x0001 // the account was specified with (parens)
+#define XACT_BALANCE 0x0002 // the account was specified with [brackets]
+#define XACT_AUTO 0x0004 // transaction created by automated entry
+#define XACT_IN_CACHE 0x0008 // transaction allocated by the binary cache
+#define XACT_CALCULATED 0x0010 // transaction's amount was auto-calculated
+#define XACT_GENERATED 0x0020 // transaction was not found in a journal
+#define XACT_TEMP 0x0040 // transaction is a temporary object
+
+ enum state_t { UNCLEARED, CLEARED, PENDING };
+
+ entry_t * entry;
+ account_t * account;
+ state_t state;
+
+ optional<date_t> _date;
+ optional<date_t> _date_eff;
+ optional<string> note;
+
+ amount_t amount;
+ optional<expr_t> amount_expr;
+ optional<amount_t> cost;
+ optional<expr_t> cost_expr;
+
+ istream_pos_type beg_pos;
+ unsigned long beg_line;
+ istream_pos_type end_pos;
+ unsigned long end_line;
+
+ static bool use_effective_date;
+
+ xact_t(account_t * _account = NULL,
+ flags_t _flags = XACT_NORMAL)
+ : supports_flags<>(_flags), entry(NULL), account(_account),
+ state(UNCLEARED), beg_pos(0), beg_line(0), end_pos(0), end_line(0)
+ {
+ TRACE_CTOR(xact_t, "account_t *, flags_t");
+ }
+ xact_t(account_t * _account,
+ const amount_t& _amount,
+ flags_t _flags = XACT_NORMAL,
+ const optional<string>& _note = none)
+ : supports_flags<>(_flags), entry(NULL), account(_account),
+ state(UNCLEARED), note(_note), amount(_amount),
+ beg_pos(0), beg_line(0), end_pos(0), end_line(0)
+ {
+ TRACE_CTOR(xact_t,
+ "account_t *, const amount_t&, flags_t, const string&");
+ }
+ xact_t(const xact_t& xact)
+ : supports_flags<>(xact),
+ scope_t(),
+ entry(xact.entry),
+ account(xact.account),
+ state(xact.state),
+ _date(xact._date),
+ _date_eff(xact._date_eff),
+ note(xact.note),
+ amount(xact.amount),
+ cost(xact.cost),
+ beg_pos(xact.beg_pos),
+ beg_line(xact.beg_line),
+ end_pos(xact.end_pos),
+ end_line(xact.end_line),
+ xdata_(xact.xdata_) // jww (2008-07-19): What are the copy semantics?
+ {
+ TRACE_CTOR(xact_t, "copy");
+ }
+ ~xact_t();
+
+ date_t actual_date() const;
+ date_t effective_date() const;
+ date_t date() const {
+ if (use_effective_date)
+ return effective_date();
+ else
+ return actual_date();
+ }
+
+ bool must_balance() const {
+ return ! has_flags(XACT_VIRTUAL) || has_flags(XACT_BALANCE);
+ }
+
+ virtual expr_t::ptr_op_t lookup(const string& name);
+
+ bool valid() const;
+
+ struct xdata_t : public supports_flags<>
+ {
+#define XACT_EXT_RECEIVED 0x01
+#define XACT_EXT_HANDLED 0x02
+#define XACT_EXT_TO_DISPLAY 0x04
+#define XACT_EXT_DISPLAYED 0x08
+#define XACT_EXT_NO_TOTAL 0x10
+#define XACT_EXT_SORT_CALC 0x20
+#define XACT_EXT_COMPOUND 0x40
+#define XACT_EXT_MATCHES 0x80
+
+ value_t total;
+ value_t sort_value;
+ value_t value;
+ unsigned int index;
+ date_t date;
+ account_t * account;
+ void * ptr;
+
+ optional<xacts_list> component_xacts;
+
+ xdata_t() : supports_flags<>(), index(0), account(NULL), ptr(NULL) {
+ TRACE_CTOR(xdata_t, "");
+ }
+ ~xdata_t() throw() {
+ TRACE_DTOR(xdata_t);
+ }
+
+ void remember_xact(xact_t& xact) {
+ if (! component_xacts)
+ component_xacts = xacts_list();
+ component_xacts->push_back(&xact);
+ }
+
+ bool has_component_xacts() const {
+ return component_xacts && ! component_xacts->empty();
+ }
+
+ void copy_component_xacts(xacts_list& xacts) {
+ foreach (xact_t * xact, xacts)
+ remember_xact(*xact);
+ }
+
+#if 0
+ void walk_component_xacts(item_handler<xact_t>& handler) const {
+ foreach (xact_t * xact, *component_xacts)
+ handler(*xact);
+ }
+#endif
+ };
+
+ // This variable holds optional "extended data" which is usually produced
+ // only during reporting, and only for the transaction set being reported.
+ // It's a memory-saving measure to delay allocation until the last possible
+ // moment.
+ mutable optional<xdata_t> xdata_;
+
+ bool has_xdata() const {
+ return xdata_;
+ }
+ void clear_xdata() {
+ xdata_ = none;
+ }
+ xdata_t& xdata() {
+ if (! xdata_)
+ xdata_ = xdata_t();
+ return *xdata_;
+ }
+
+ void add_to_value(value_t& value);
+
+ date_t reported_date() const {
+ if (xdata_ && is_valid(xdata_->date))
+ return xdata_->date;
+ return
+ date();
+ }
+
+ account_t * reported_account() {
+ if (xdata_)
+ if (account_t * acct = xdata_->account)
+ return acct;
+ return account;
+ }
+
+ const account_t * reported_account() const {
+ return const_cast<xact_t *>(this)->reported_account();
+ }
+};
+
+} // namespace ledger
+
+#endif // _XACT_H
diff --git a/src/xml.cc b/src/xml.cc
new file mode 100644
index 00000000..715965cb
--- /dev/null
+++ b/src/xml.cc
@@ -0,0 +1,499 @@
+/*
+ * Copyright (c) 2003-2008, 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 "xml.h"
+#include "journal.h"
+#include "utils.h"
+
+namespace ledger {
+
+#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
+
+static XML_Parser current_parser;
+static unsigned int count;
+
+static journal_t * curr_journal;
+static entry_t * curr_entry;
+static commodity_t * curr_comm;
+static string comm_flags;
+
+static xact_t::state_t curr_state;
+
+static string data;
+static bool ignore;
+static string have_error;
+
+static void startElement(void *userData, const char *name, const char **attrs)
+{
+ if (ignore)
+ return;
+
+ if (std::strcmp(name, "entry") == 0) {
+ assert(! curr_entry);
+ curr_entry = new entry_t;
+ curr_state = xact_t::UNCLEARED;
+ }
+ else if (std::strcmp(name, "xact") == 0) {
+ assert(curr_entry);
+ curr_entry->add_xact(new xact_t);
+ if (curr_state != xact_t::UNCLEARED)
+ curr_entry->xacts.back()->state = curr_state;
+ }
+ else if (std::strcmp(name, "commodity") == 0) {
+ if (string(attrs[0]) == "flags")
+ comm_flags = attrs[1];
+ }
+ else if (std::strcmp(name, "total") == 0) {
+ ignore = true;
+ }
+}
+
+static void endElement(void *userData, const char *name)
+{
+ if (ignore) {
+ if (std::strcmp(name, "total") == 0)
+ ignore = false;
+ return;
+ }
+
+ if (std::strcmp(name, "entry") == 0) {
+ assert(curr_entry);
+ if (curr_journal->add_entry(curr_entry)) {
+ count++;
+ } else {
+ account_t * acct = curr_journal->find_account("<Unknown>");
+ curr_entry->add_xact(new xact_t(acct));
+ if (curr_journal->add_entry(curr_entry)) {
+ count++;
+ } else {
+ checked_delete(curr_entry);
+ have_error = "Entry cannot be balanced";
+ }
+ }
+ curr_entry = NULL;
+ }
+ else if (std::strcmp(name, "en:date") == 0) {
+ curr_entry->_date = parse_date(data);
+ }
+ else if (std::strcmp(name, "en:date_eff") == 0) {
+ curr_entry->_date_eff = parse_date(data);
+ }
+ else if (std::strcmp(name, "en:code") == 0) {
+ curr_entry->code = data;
+ }
+ else if (std::strcmp(name, "en:cleared") == 0) {
+ curr_state = xact_t::CLEARED;
+ }
+ else if (std::strcmp(name, "en:pending") == 0) {
+ curr_state = xact_t::PENDING;
+ }
+ else if (std::strcmp(name, "en:payee") == 0) {
+ curr_entry->payee = data;
+ }
+ else if (std::strcmp(name, "tr:account") == 0) {
+ curr_entry->xacts.back()->account = curr_journal->find_account(data);
+ }
+ else if (std::strcmp(name, "tr:cleared") == 0) {
+ curr_entry->xacts.back()->state = xact_t::CLEARED;
+ }
+ else if (std::strcmp(name, "tr:pending") == 0) {
+ curr_entry->xacts.back()->state = xact_t::PENDING;
+ }
+ else if (std::strcmp(name, "tr:virtual") == 0) {
+ curr_entry->xacts.back()->add_flags(XACT_VIRTUAL);
+ }
+ else if (std::strcmp(name, "tr:generated") == 0) {
+ curr_entry->xacts.back()->add_flags(XACT_AUTO);
+ }
+ else if (std::strcmp(name, "symbol") == 0) {
+ assert(! curr_comm);
+ curr_comm = amount_t::current_pool->find_or_create(data);
+ assert(curr_comm);
+ curr_comm->add_flags(COMMODITY_STYLE_SUFFIXED);
+ if (! comm_flags.empty()) {
+ for (string::size_type i = 0, l = comm_flags.length(); i < l; i++) {
+ switch (comm_flags[i]) {
+ case 'P': curr_comm->drop_flags(COMMODITY_STYLE_SUFFIXED); break;
+ case 'S': curr_comm->add_flags(COMMODITY_STYLE_SEPARATED); break;
+ case 'T': curr_comm->add_flags(COMMODITY_STYLE_THOUSANDS); break;
+ case 'E': curr_comm->add_flags(COMMODITY_STYLE_EUROPEAN); break;
+ }
+ }
+ }
+ }
+#if 0
+ // jww (2006-03-02): !!!
+ else if (std::strcmp(name, "price") == 0) {
+ assert(curr_comm);
+ amount_t * price = new amount_t(data);
+ std::ostringstream symstr;
+ symstr << curr_comm->symbol << " {" << *price << "}";
+ commodity_t * priced_comm =
+ commodity_t::find_commodity(symstr.str(), true);
+ priced_comm->price = price;
+ priced_comm->base = curr_comm;
+ curr_comm = priced_comm;
+ }
+#endif
+ else if (std::strcmp(name, "quantity") == 0) {
+ curr_entry->xacts.back()->amount.parse(data);
+ if (curr_comm) {
+ string::size_type i = data.find('.');
+ if (i != string::npos) {
+ int precision = data.length() - i - 1;
+ if (precision > curr_comm->precision())
+ curr_comm->set_precision(precision);
+ }
+ curr_entry->xacts.back()->amount.set_commodity(*curr_comm);
+ curr_comm = NULL;
+ }
+ }
+ else if (std::strcmp(name, "tr:amount") == 0) {
+ curr_comm = NULL;
+ }
+}
+
+static void dataHandler(void *userData, const char *s, int len)
+{
+ if (! ignore)
+ data = string(s, len);
+}
+
+bool xml_parser_t::test(std::istream& in) const
+{
+ char buf[80];
+
+ in.getline(buf, 79);
+ if (std::strncmp(buf, "<?xml", 5) != 0) {
+ in.clear();
+ in.seekg(0, std::ios::beg);
+ return false;
+ }
+
+ in.getline(buf, 79);
+ if (! std::strstr(buf, "<ledger")) {
+ in.clear();
+ in.seekg(0, std::ios::beg);
+ return false;
+ }
+
+ in.clear();
+ in.seekg(0, std::ios::beg);
+ return true;
+}
+
+unsigned int xml_parser_t::parse(std::istream& in,
+ session_t& session,
+ journal_t& journal,
+ account_t * master,
+ const path * original_file)
+{
+ char buf[BUFSIZ];
+
+ count = 0;
+ curr_journal = &journal;
+ curr_entry = NULL;
+ curr_comm = NULL;
+ ignore = false;
+
+ XML_Parser parser = XML_ParserCreate(NULL);
+ current_parser = parser;
+
+ XML_SetElementHandler(parser, startElement, endElement);
+ XML_SetCharacterDataHandler(parser, dataHandler);
+
+ while (! in.eof()) {
+ in.getline(buf, BUFSIZ - 1);
+ std::strcat(buf, "\n");
+ bool result;
+ try {
+ result = XML_Parse(parser, buf, std::strlen(buf), in.eof());
+ }
+ catch (const std::exception& err) {
+ //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++;
+ XML_ParserFree(parser);
+ throw parse_error(err.what());
+ }
+
+ if (! have_error.empty()) {
+ //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++;
+ parse_error err(have_error);
+ std::cerr << "Error: " << err.what() << std::endl;
+ have_error = "";
+ }
+
+ if (! result) {
+ //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++;
+ const char * err = XML_ErrorString(XML_GetErrorCode(parser));
+ XML_ParserFree(parser);
+ throw parse_error(err);
+ }
+ }
+
+ XML_ParserFree(parser);
+
+ return count;
+}
+
+#endif // defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
+
+void xml_write_amount(std::ostream& out, const amount_t& amount,
+ const int depth = 0)
+{
+ for (int i = 0; i < depth; i++) out << ' ';
+ out << "<amount>\n";
+
+ commodity_t& c = amount.commodity();
+ for (int i = 0; i < depth + 2; i++) out << ' ';
+ out << "<commodity flags=\"";
+ if (! (c.flags() & COMMODITY_STYLE_SUFFIXED)) out << 'P';
+ if (c.flags() & COMMODITY_STYLE_SEPARATED) out << 'S';
+ if (c.flags() & COMMODITY_STYLE_THOUSANDS) out << 'T';
+ if (c.flags() & COMMODITY_STYLE_EUROPEAN) out << 'E';
+ out << "\">\n";
+ for (int i = 0; i < depth + 4; i++) out << ' ';
+#if 0
+ // jww (2006-03-02): !!!
+ if (c.price) {
+ out << "<symbol>" << c.base->symbol << "</symbol>\n";
+ for (int i = 0; i < depth + 4; i++) out << ' ';
+ out << "<price>\n";
+ xml_write_amount(out, *c.price, depth + 6);
+ for (int i = 0; i < depth + 4; i++) out << ' ';
+ out << "</price>\n";
+ } else {
+ out << "<symbol>" << c.symbol << "</symbol>\n";
+ }
+#endif
+ for (int i = 0; i < depth + 2; i++) out << ' ';
+ out << "</commodity>\n";
+
+ for (int i = 0; i < depth + 2; i++) out << ' ';
+ out << "<quantity>";
+ out << amount.quantity_string() << "</quantity>\n";
+
+ for (int i = 0; i < depth; i++) out << ' ';
+ out << "</amount>\n";
+}
+
+void xml_write_value(std::ostream& out, const value_t& value,
+ const int depth = 0)
+{
+ const balance_t * bal = NULL;
+
+ for (int i = 0; i < depth; i++) out << ' ';
+ out << "<value type=\"";
+ switch (value.type()) {
+ case value_t::BOOLEAN: out << "boolean"; break;
+ case value_t::INTEGER: out << "integer"; break;
+ case value_t::AMOUNT: out << "amount"; break;
+ case value_t::BALANCE:
+ case value_t::BALANCE_PAIR: out << "balance"; break;
+ default:
+ assert(false);
+ break;
+ }
+ out << "\">\n";
+
+ switch (value.type()) {
+ case value_t::BOOLEAN:
+ for (int i = 0; i < depth + 2; i++) out << ' ';
+ out << "<boolean>" << value.as_boolean() << "</boolean>\n";
+ break;
+
+ case value_t::INTEGER:
+ for (int i = 0; i < depth + 2; i++) out << ' ';
+ out << "<integer>" << value.as_long() << "</integer>\n";
+ break;
+
+ case value_t::AMOUNT:
+ xml_write_amount(out, value.as_amount(), depth + 2);
+ break;
+
+ case value_t::BALANCE:
+ bal = &(value.as_balance());
+ // fall through...
+
+ case value_t::BALANCE_PAIR:
+ if (! bal)
+ bal = &(value.as_balance_pair().quantity());
+
+ for (int i = 0; i < depth + 2; i++) out << ' ';
+ out << "<balance>\n";
+
+ foreach (const balance_t::amounts_map::value_type& pair, bal->amounts)
+ xml_write_amount(out, pair.second, depth + 4);
+
+ for (int i = 0; i < depth + 2; i++) out << ' ';
+ out << "</balance>\n";
+ break;
+
+ default:
+ assert(false);
+ break;
+ }
+
+ for (int i = 0; i < depth; i++) out << ' ';
+ out << "</value>\n";
+}
+
+void output_xml_string(std::ostream& out, const string& str)
+{
+ for (const char * s = str.c_str(); *s; s++) {
+ switch (*s) {
+ case '<':
+ out << "&lt;";
+ break;
+ case '>':
+ out << "&rt;";
+ break;
+ case '&':
+ out << "&amp;";
+ break;
+ default:
+ out << *s;
+ break;
+ }
+ }
+}
+
+void format_xml_entries::format_last_entry()
+{
+ std::ostream& out(*report.output_stream);
+
+#if 0
+ // jww (2008-05-08): Need to format these dates
+ out << " <entry>\n"
+ << " <en:date>" << last_entry->_date.to_string("%Y/%m/%d")
+ << "</en:date>\n";
+
+ if (is_valid(last_entry->_date_eff))
+ out << " <en:date_eff>"
+ << last_entry->_date_eff.to_string("%Y/%m/%d")
+ << "</en:date_eff>\n";
+#endif
+
+ if (last_entry->code) {
+ out << " <en:code>";
+ output_xml_string(out, *last_entry->code);
+ out << "</en:code>\n";
+ }
+
+ if (! last_entry->payee.empty()) {
+ out << " <en:payee>";
+ output_xml_string(out, last_entry->payee);
+ out << "</en:payee>\n";
+ }
+
+ bool first = true;
+ foreach (xact_t * xact, last_entry->xacts) {
+ if (xact->has_xdata() &&
+ xact->xdata().has_flags(XACT_EXT_TO_DISPLAY)) {
+ if (first) {
+ out << " <en:xacts>\n";
+ first = false;
+ }
+
+ out << " <xact>\n";
+
+#if 0
+ // jww (2008-05-08): Need to format these
+ if (xact->_date)
+ out << " <tr:date>"
+ << xact->_date.to_string("%Y/%m/%d")
+ << "</tr:date>\n";
+
+ if (is_valid(xact->_date_eff))
+ out << " <tr:date_eff>"
+ << xact->_date_eff.to_string("%Y/%m/%d")
+ << "</tr:date_eff>\n";
+#endif
+
+ if (xact->state == xact_t::CLEARED)
+ out << " <tr:cleared/>\n";
+ else if (xact->state == xact_t::PENDING)
+ out << " <tr:pending/>\n";
+
+ if (xact->has_flags(XACT_VIRTUAL))
+ out << " <tr:virtual/>\n";
+ if (xact->has_flags(XACT_AUTO))
+ out << " <tr:generated/>\n";
+
+ if (xact->account) {
+ string name = xact->account->fullname();
+ if (name == "<Total>")
+ name = "[TOTAL]";
+ else if (name == "<Unknown>")
+ name = "[UNKNOWN]";
+
+ out << " <tr:account>";
+ output_xml_string(out, name);
+ out << "</tr:account>\n";
+ }
+
+ out << " <tr:amount>\n";
+ if (xact->xdata().has_flags(XACT_EXT_COMPOUND))
+ xml_write_value(out, xact->xdata().value, 10);
+ else
+ xml_write_value(out, value_t(xact->amount), 10);
+ out << " </tr:amount>\n";
+
+ if (xact->cost) {
+ out << " <tr:cost>\n";
+ xml_write_value(out, value_t(*xact->cost), 10);
+ out << " </tr:cost>\n";
+ }
+
+ if (xact->note) {
+ out << " <tr:note>";
+ output_xml_string(out, *xact->note);
+ out << "</tr:note>\n";
+ }
+
+ if (show_totals) {
+ out << " <total>\n";
+ xml_write_value(out, xact->xdata().total, 10);
+ out << " </total>\n";
+ }
+
+ out << " </xact>\n";
+
+ xact->xdata().add_flags(XACT_EXT_DISPLAYED);
+ }
+ }
+
+ if (! first)
+ out << " </en:xacts>\n";
+
+ out << " </entry>\n";
+}
+
+} // namespace ledger
diff --git a/src/xml.h b/src/xml.h
new file mode 100644
index 00000000..38768cff
--- /dev/null
+++ b/src/xml.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2003-2008, 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 _XML_H
+#define _XML_H
+
+#include "journal.h"
+#include "report.h"
+#include "output.h"
+
+namespace ledger {
+
+#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
+
+class xml_parser_t : public journal_t::parser_t
+{
+ public:
+ virtual bool test(std::istream& in) const;
+
+ virtual unsigned int parse(std::istream& in,
+ session_t& session,
+ journal_t& journal,
+ account_t * master = NULL,
+ const path * original_file = NULL);
+};
+
+#endif
+
+class format_xml_entries : public format_entries
+{
+ bool show_totals;
+
+ format_xml_entries();
+
+public:
+ format_xml_entries(report_t& _report,
+ const bool _show_totals = false)
+ : format_entries(_report, ""), show_totals(_show_totals) {
+ TRACE_CTOR(format_xml_entries, "std::ostream&, const bool");
+ *report.output_stream << "<?xml version=\"1.0\"?>\n"
+ << "<ledger version=\"2.5\">\n";
+ }
+ virtual ~format_xml_entries() throw() {
+ TRACE_DTOR(format_xml_entries);
+ }
+
+ virtual void flush() {
+ format_entries::flush();
+ *report.output_stream << "</ledger>" << std::endl;
+ }
+
+ virtual void format_last_entry();
+};
+
+} // namespace ledger
+
+#endif // _XML_H