summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/account.cc551
-rw-r--r--src/account.h273
-rw-r--r--src/accum.cc76
-rw-r--r--src/accum.h86
-rw-r--r--src/amount.cc1278
-rw-r--r--src/amount.h761
-rw-r--r--src/annotate.cc205
-rw-r--r--src/annotate.h265
-rw-r--r--src/archive.cc295
-rw-r--r--src/archive.h92
-rw-r--r--src/balance.cc310
-rw-r--r--src/balance.h602
-rw-r--r--src/chain.cc241
-rw-r--r--src/chain.h91
-rw-r--r--src/commodity.cc705
-rw-r--r--src/commodity.h431
-rw-r--r--src/compare.cc114
-rw-r--r--src/compare.h98
-rw-r--r--src/draft.cc527
-rw-r--r--src/draft.h113
-rw-r--r--src/emacs.cc108
-rw-r--r--src/emacs.h79
-rw-r--r--src/error.cc118
-rw-r--r--src/error.h104
-rw-r--r--src/expr.cc143
-rw-r--r--src/expr.h147
-rw-r--r--src/exprbase.h253
-rw-r--r--src/filters.cc1055
-rw-r--r--src/filters.h721
-rw-r--r--src/flags.h209
-rw-r--r--src/format.cc488
-rw-r--r--src/format.h161
-rw-r--r--src/generate.cc385
-rw-r--r--src/generate.h129
-rw-r--r--src/global.cc480
-rw-r--r--src/global.h163
-rw-r--r--src/interactive.cc194
-rw-r--r--src/interactive.h151
-rw-r--r--src/item.cc467
-rw-r--r--src/item.h215
-rw-r--r--src/iterators.cc258
-rw-r--r--src/iterators.h230
-rw-r--r--src/journal.cc251
-rw-r--r--src/journal.h197
-rw-r--r--src/main.cc217
-rw-r--r--src/mask.cc55
-rw-r--r--src/mask.h157
-rw-r--r--src/op.cc732
-rw-r--r--src/op.h343
-rw-r--r--src/option.cc249
-rw-r--r--src/option.h294
-rw-r--r--src/output.cc254
-rw-r--r--src/output.h110
-rw-r--r--src/parser.cc517
-rw-r--r--src/parser.h107
-rw-r--r--src/pool.cc364
-rw-r--r--src/pool.h156
-rw-r--r--src/post.cc576
-rw-r--r--src/post.h224
-rw-r--r--src/precmd.cc211
-rw-r--r--src/precmd.h59
-rw-r--r--src/predicate.cc46
-rw-r--r--src/predicate.h106
-rw-r--r--src/pstream.h109
-rw-r--r--src/py_account.cc235
-rw-r--r--src/py_amount.cc314
-rw-r--r--src/py_balance.cc238
-rw-r--r--src/py_commodity.cc451
-rw-r--r--src/py_expr.cc68
-rw-r--r--src/py_format.cc64
-rw-r--r--src/py_item.cc166
-rw-r--r--src/py_journal.cc321
-rw-r--r--src/py_post.cc184
-rw-r--r--src/py_times.cc253
-rw-r--r--src/py_utils.cc254
-rw-r--r--src/py_value.cc380
-rw-r--r--src/py_xact.cc153
-rw-r--r--src/pyfstream.h202
-rw-r--r--src/pyinterp.cc505
-rw-r--r--src/pyinterp.h124
-rw-r--r--src/pyledger.cc52
-rw-r--r--src/pyutils.h189
-rw-r--r--src/query.cc455
-rw-r--r--src/query.h305
-rw-r--r--src/quotes.cc109
-rw-r--r--src/quotes.h53
-rw-r--r--src/report.cc1307
-rw-r--r--src/report.h958
-rw-r--r--src/scope.cc74
-rw-r--r--src/scope.h348
-rw-r--r--src/session.cc248
-rw-r--r--src/session.h151
-rw-r--r--src/stats.cc121
-rw-r--r--src/stats.h55
-rw-r--r--src/stream.cc139
-rw-r--r--src/stream.h142
-rw-r--r--src/system.hh.in256
-rw-r--r--src/temps.cc137
-rw-r--r--src/temps.h76
-rw-r--r--src/textual.cc1396
-rw-r--r--src/timelog.cc158
-rw-r--r--src/timelog.h103
-rw-r--r--src/times.cc1587
-rw-r--r--src/times.h632
-rw-r--r--src/token.cc485
-rw-r--r--src/token.h135
-rw-r--r--src/unistring.h128
-rw-r--r--src/utils.cc821
-rw-r--r--src/utils.h713
-rw-r--r--src/value.cc1864
-rw-r--r--src/value.h998
-rw-r--r--src/xact.cc781
-rw-r--r--src/xact.h232
-rw-r--r--src/xml.cc124
-rw-r--r--src/xml.h90
115 files changed, 38040 insertions, 0 deletions
diff --git a/src/account.cc b/src/account.cc
new file mode 100644
index 00000000..e6c7af56
--- /dev/null
+++ b/src/account.cc
@@ -0,0 +1,551 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "account.h"
+#include "post.h"
+#include "xact.h"
+#include "interactive.h"
+
+namespace ledger {
+
+account_t::~account_t()
+{
+ TRACE_DTOR(account_t);
+
+ foreach (accounts_map::value_type& pair, accounts)
+ if (! pair.second->has_flags(ACCOUNT_TEMP))
+ 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[8192];
+
+ 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;
+}
+
+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 * account_t::find_account_re(const string& regexp)
+{
+ return find_account_re_(this, mask_t(regexp));
+}
+
+bool account_t::remove_post(post_t * post)
+{
+ assert(! posts.empty());
+ posts.remove(post);
+ post->account = NULL;
+ return true;
+}
+
+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;
+ }
+}
+
+string account_t::partial_name(bool flat) const
+{
+ string pname = name;
+
+ for (const account_t * acct = parent;
+ acct && acct->parent;
+ acct = acct->parent) {
+ if (! flat) {
+ std::size_t count = acct->children_with_flags(ACCOUNT_EXT_TO_DISPLAY);
+ assert(count > 0);
+ if (count > 1 || acct->has_xflags(ACCOUNT_EXT_TO_DISPLAY))
+ break;
+ }
+ pname = acct->name + ":" + pname;
+ }
+ return pname;
+}
+
+std::ostream& operator<<(std::ostream& out, const account_t& account)
+{
+ out << account.fullname();
+ return out;
+}
+
+namespace {
+ value_t get_partial_name(call_scope_t& scope)
+ {
+ in_context_t<account_t> env(scope, "&b");
+ return string_value(env->partial_name(env.has(0) ?
+ env.get<bool>(0) : false));
+ }
+
+ value_t get_account(account_t& account) { // this gets the name
+ return string_value(account.fullname());
+ }
+
+ value_t get_account_base(account_t& account) {
+ return string_value(account.name);
+ }
+
+ value_t get_amount(account_t& account) {
+ return SIMPLIFIED_VALUE_OR_ZERO(account.amount());
+ }
+
+ value_t get_total(account_t& account) {
+ return SIMPLIFIED_VALUE_OR_ZERO(account.total());
+ }
+
+ value_t get_subcount(account_t& account) {
+ return long(account.self_details().posts_count);
+ }
+
+ value_t get_count(account_t& account) {
+ return long(account.family_details().posts_count);
+ }
+
+ value_t get_depth(account_t& account) {
+ return long(account.depth);
+ }
+
+ value_t ignore(account_t&) {
+ return false;
+ }
+
+ value_t get_true(account_t&) {
+ return true;
+ }
+
+ value_t get_depth_spacer(account_t& account)
+ {
+ std::size_t depth = 0;
+ for (const account_t * acct = account.parent;
+ acct && acct->parent;
+ acct = acct->parent) {
+ std::size_t count = acct->children_with_flags(ACCOUNT_EXT_TO_DISPLAY);
+ assert(count > 0);
+ if (count > 1 || acct->has_xflags(ACCOUNT_EXT_TO_DISPLAY))
+ depth++;
+ }
+
+ std::ostringstream out;
+ for (std::size_t i = 0; i < depth; i++)
+ out << " ";
+
+ return string_value(out.str());
+ }
+
+ value_t get_latest_cleared(account_t& account)
+ {
+ return account.self_details().latest_cleared_post;
+ }
+
+ template <value_t (*Func)(account_t&)>
+ value_t get_wrapper(call_scope_t& scope) {
+ return (*Func)(find_scope<account_t>(scope));
+ }
+
+ value_t get_parent(account_t& account) {
+ return value_t(static_cast<scope_t *>(account.parent));
+ }
+}
+
+expr_t::ptr_op_t account_t::lookup(const symbol_t::kind_t kind,
+ const string& name)
+{
+ if (kind != symbol_t::FUNCTION)
+ return NULL;
+
+ 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_wrapper<&get_account>);
+ else if (name == "account_base")
+ return WRAP_FUNCTOR(get_wrapper<&get_account_base>);
+ break;
+
+ case 'c':
+ if (name == "count")
+ return WRAP_FUNCTOR(get_wrapper<&get_count>);
+ break;
+
+ case 'd':
+ if (name == "depth")
+ return WRAP_FUNCTOR(get_wrapper<&get_depth>);
+ else if (name == "depth_spacer")
+ return WRAP_FUNCTOR(get_wrapper<&get_depth_spacer>);
+ break;
+
+ case 'i':
+ if (name == "is_account")
+ return WRAP_FUNCTOR(get_wrapper<&get_true>);
+ else if (name == "is_index")
+ return WRAP_FUNCTOR(get_wrapper<&get_subcount>);
+ break;
+
+ case 'l':
+ if (name == "latest_cleared")
+ return WRAP_FUNCTOR(get_wrapper<&get_latest_cleared>);
+ else if (name[1] == '\0')
+ return WRAP_FUNCTOR(get_wrapper<&get_depth>);
+ break;
+
+ case 'n':
+ if (name[1] == '\0')
+ return WRAP_FUNCTOR(get_wrapper<&get_subcount>);
+ break;
+
+ case 'p':
+ if (name == "partial_account")
+ return WRAP_FUNCTOR(get_partial_name);
+ else if (name == "parent")
+ return WRAP_FUNCTOR(get_wrapper<&get_parent>);
+ break;
+
+ case 's':
+ if (name == "subcount")
+ return WRAP_FUNCTOR(get_wrapper<&get_subcount>);
+ break;
+
+ case 't':
+ if (name == "total")
+ return WRAP_FUNCTOR(get_wrapper<&get_total>);
+ break;
+
+ case 'u':
+ if (name == "use_direct_amount")
+ return WRAP_FUNCTOR(get_wrapper<&ignore>);
+ break;
+
+ case 'N':
+ if (name[1] == '\0')
+ return WRAP_FUNCTOR(get_wrapper<&get_count>);
+ break;
+
+ case 'O':
+ if (name[1] == '\0')
+ return WRAP_FUNCTOR(get_wrapper<&get_total>);
+ break;
+ }
+
+ return NULL;
+}
+
+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;
+}
+
+bool account_t::children_with_xdata() const
+{
+ foreach (const accounts_map::value_type& pair, accounts)
+ if (pair.second->has_xdata() ||
+ pair.second->children_with_xdata())
+ return true;
+
+ return false;
+}
+
+std::size_t account_t::children_with_flags(xdata_t::flags_t flags) const
+{
+ std::size_t count = 0;
+ bool grandchildren_visited = false;
+
+ foreach (const accounts_map::value_type& pair, accounts)
+ if (pair.second->has_xflags(flags) ||
+ pair.second->children_with_flags(flags))
+ count++;
+
+ // Although no immediately children were visited, if any progeny at all were
+ // visited, it counts as one.
+ if (count == 0 && grandchildren_visited)
+ count = 1;
+
+ return count;
+}
+
+account_t::xdata_t::details_t&
+account_t::xdata_t::details_t::operator+=(const details_t& other)
+{
+ posts_count += other.posts_count;
+ posts_virtuals_count += other.posts_virtuals_count;
+ posts_cleared_count += other.posts_cleared_count;
+ posts_last_7_count += other.posts_last_7_count;
+ posts_last_30_count += other.posts_last_30_count;
+ posts_this_month_count += other.posts_this_month_count;
+
+ if (! is_valid(earliest_post) ||
+ (is_valid(other.earliest_post) &&
+ other.earliest_post < earliest_post))
+ earliest_post = other.earliest_post;
+ if (! is_valid(earliest_cleared_post) ||
+ (is_valid(other.earliest_cleared_post) &&
+ other.earliest_cleared_post < earliest_cleared_post))
+ earliest_cleared_post = other.earliest_cleared_post;
+
+ if (! is_valid(latest_post) ||
+ (is_valid(other.latest_post) &&
+ other.latest_post > latest_post))
+ latest_post = other.latest_post;
+ if (! is_valid(latest_cleared_post) ||
+ (is_valid(other.latest_cleared_post) &&
+ other.latest_cleared_post > latest_cleared_post))
+ latest_cleared_post = other.latest_cleared_post;
+
+ filenames.insert(other.filenames.begin(), other.filenames.end());
+ accounts_referenced.insert(other.accounts_referenced.begin(),
+ other.accounts_referenced.end());
+ payees_referenced.insert(other.payees_referenced.begin(),
+ other.payees_referenced.end());
+ return *this;
+}
+
+void account_t::clear_xdata()
+{
+ xdata_ = none;
+
+ foreach (accounts_map::value_type& pair, accounts)
+ if (! pair.second->has_flags(ACCOUNT_TEMP))
+ pair.second->clear_xdata();
+}
+
+value_t account_t::amount(const optional<expr_t&>& expr) const
+{
+ if (xdata_ && xdata_->has_flags(ACCOUNT_EXT_VISITED)) {
+ posts_list::const_iterator i;
+ if (xdata_->self_details.last_post)
+ i = *xdata_->self_details.last_post;
+ else
+ i = posts.begin();
+
+ for (; i != posts.end(); i++) {
+ if ((*i)->xdata().has_flags(POST_EXT_VISITED)) {
+ if (! (*i)->xdata().has_flags(POST_EXT_CONSIDERED)) {
+ (*i)->add_to_value(xdata_->self_details.total, expr);
+ (*i)->xdata().add_flags(POST_EXT_CONSIDERED);
+ }
+ }
+ xdata_->self_details.last_post = i;
+ }
+
+ if (xdata_->self_details.last_reported_post)
+ i = *xdata_->self_details.last_reported_post;
+ else
+ i = xdata_->reported_posts.begin();
+
+ for (; i != xdata_->reported_posts.end(); i++) {
+ if ((*i)->xdata().has_flags(POST_EXT_VISITED)) {
+ if (! (*i)->xdata().has_flags(POST_EXT_CONSIDERED)) {
+ (*i)->add_to_value(xdata_->self_details.total, expr);
+ (*i)->xdata().add_flags(POST_EXT_CONSIDERED);
+ }
+ }
+ xdata_->self_details.last_reported_post = i;
+ }
+
+ return xdata_->self_details.total;
+ } else {
+ return NULL_VALUE;
+ }
+}
+
+value_t account_t::total(const optional<expr_t&>& expr) const
+{
+ if (! (xdata_ && xdata_->family_details.calculated)) {
+ const_cast<account_t&>(*this).xdata().family_details.calculated = true;
+
+ value_t temp;
+ foreach (const accounts_map::value_type& pair, accounts) {
+ temp = pair.second->total(expr);
+ if (! temp.is_null())
+ add_or_set_value(xdata_->family_details.total, temp);
+ }
+
+ temp = amount(expr);
+ if (! temp.is_null())
+ add_or_set_value(xdata_->family_details.total, temp);
+ }
+ return xdata_->family_details.total;
+}
+
+const account_t::xdata_t::details_t&
+account_t::self_details(bool gather_all) const
+{
+ if (! (xdata_ && xdata_->self_details.gathered)) {
+ const_cast<account_t&>(*this).xdata().self_details.gathered = true;
+
+ foreach (const post_t * post, posts)
+ xdata_->self_details.update(const_cast<post_t&>(*post), gather_all);
+ }
+ return xdata_->self_details;
+}
+
+const account_t::xdata_t::details_t&
+account_t::family_details(bool gather_all) const
+{
+ if (! (xdata_ && xdata_->family_details.gathered)) {
+ const_cast<account_t&>(*this).xdata().family_details.gathered = true;
+
+ foreach (const accounts_map::value_type& pair, accounts)
+ xdata_->family_details += pair.second->family_details(gather_all);
+
+ xdata_->family_details += self_details(gather_all);
+ }
+ return xdata_->family_details;
+}
+
+void account_t::xdata_t::details_t::update(post_t& post,
+ bool gather_all)
+{
+ posts_count++;
+
+ if (post.has_flags(POST_VIRTUAL))
+ posts_virtuals_count++;
+
+ if (gather_all)
+ filenames.insert(post.pos->pathname);
+
+ date_t date = post.date();
+
+ if (date.year() == CURRENT_DATE().year() &&
+ date.month() == CURRENT_DATE().month())
+ posts_this_month_count++;
+
+ if ((CURRENT_DATE() - date).days() <= 30)
+ posts_last_30_count++;
+ if ((CURRENT_DATE() - date).days() <= 7)
+ posts_last_7_count++;
+
+ if (! is_valid(earliest_post) || post.date() < earliest_post)
+ earliest_post = post.date();
+ if (! is_valid(latest_post) || post.date() > latest_post)
+ latest_post = post.date();
+
+ if (post.state() == item_t::CLEARED) {
+ posts_cleared_count++;
+
+ if (! is_valid(earliest_cleared_post) ||
+ post.date() < earliest_cleared_post)
+ earliest_cleared_post = post.date();
+ if (! is_valid(latest_cleared_post) ||
+ post.date() > latest_cleared_post)
+ latest_cleared_post = post.date();
+ }
+
+ if (gather_all) {
+ accounts_referenced.insert(post.account->fullname());
+ payees_referenced.insert(post.xact->payee);
+ }
+}
+
+} // namespace ledger
diff --git a/src/account.h b/src/account.h
new file mode 100644
index 00000000..73cd35ac
--- /dev/null
+++ b/src/account.h
@@ -0,0 +1,273 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup data
+ */
+
+/**
+ * @file account.h
+ * @author John Wiegley
+ *
+ * @ingroup data
+ */
+#ifndef _ACCOUNT_H
+#define _ACCOUNT_H
+
+#include "scope.h"
+
+namespace ledger {
+
+class account_t;
+class xact_t;
+class post_t;
+
+typedef std::list<post_t *> posts_list;
+typedef std::map<const string, account_t *> accounts_map;
+
+class account_t : public supports_flags<>, public scope_t
+{
+#define ACCOUNT_NORMAL 0x00 // no flags at all, a basic account
+#define ACCOUNT_KNOWN 0x01
+#define ACCOUNT_TEMP 0x02 // account is a temporary object
+#define ACCOUNT_GENERATED 0x04 // account never actually existed
+
+public:
+ account_t * parent;
+ string name;
+ optional<string> note;
+ unsigned short depth;
+ accounts_map accounts;
+ posts_list posts;
+
+ mutable string _fullname;
+
+ account_t(account_t * _parent = NULL,
+ const string& _name = "",
+ const optional<string>& _note = none)
+ : supports_flags<>(), scope_t(), parent(_parent),
+ name(_name), note(_note),
+ depth(static_cast<unsigned short>(parent ? parent->depth + 1 : 0)) {
+ TRACE_CTOR(account_t, "account_t *, const string&, const string&");
+ }
+ account_t(const account_t& other)
+ : supports_flags<>(other.flags()), scope_t(),
+ parent(other.parent),
+ name(other.name),
+ note(other.note),
+ depth(other.depth),
+ accounts(other.accounts) {
+ TRACE_CTOR(account_t, "copy");
+ }
+ ~account_t();
+
+ operator string() const {
+ return fullname();
+ }
+ string fullname() const;
+ string partial_name(bool flat = false) 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);
+ account_t * find_account_re(const string& regexp);
+
+ typedef transform_iterator<function<account_t *(accounts_map::value_type&)>,
+ accounts_map::iterator>
+ accounts_map_seconds_iterator;
+
+ accounts_map_seconds_iterator accounts_begin() {
+ return make_transform_iterator
+ (accounts.begin(), bind(&accounts_map::value_type::second, _1));
+ }
+ accounts_map_seconds_iterator accounts_end() {
+ return make_transform_iterator
+ (accounts.end(), bind(&accounts_map::value_type::second, _1));
+ }
+
+ void add_post(post_t * post) {
+ posts.push_back(post);
+ }
+ bool remove_post(post_t * post);
+
+ posts_list::iterator posts_begin() {
+ return posts.begin();
+ }
+ posts_list::iterator posts_end() {
+ return posts.end();
+ }
+
+ virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind,
+ const string& name);
+
+ bool valid() const;
+
+ friend class journal_t;
+
+ struct xdata_t : public supports_flags<>
+ {
+#define ACCOUNT_EXT_SORT_CALC 0x01
+#define ACCOUNT_EXT_HAS_NON_VIRTUALS 0x02
+#define ACCOUNT_EXT_HAS_UNB_VIRTUALS 0x04
+#define ACCOUNT_EXT_AUTO_VIRTUALIZE 0x08
+#define ACCOUNT_EXT_VISITED 0x10
+#define ACCOUNT_EXT_MATCHING 0x20
+#define ACCOUNT_EXT_TO_DISPLAY 0x40
+#define ACCOUNT_EXT_DISPLAYED 0x80
+
+ struct details_t
+ {
+ value_t total;
+ bool calculated;
+ bool gathered;
+
+ std::size_t posts_count;
+ std::size_t posts_virtuals_count;
+ std::size_t posts_cleared_count;
+ std::size_t posts_last_7_count;
+ std::size_t posts_last_30_count;
+ std::size_t posts_this_month_count;
+
+ date_t earliest_post;
+ date_t earliest_cleared_post;
+ date_t latest_post;
+ date_t latest_cleared_post;
+
+ std::set<path> filenames;
+ std::set<string> accounts_referenced;
+ std::set<string> payees_referenced;
+
+ optional<posts_list::const_iterator> last_post;
+ optional<posts_list::const_iterator> last_reported_post;
+
+ details_t()
+ : calculated(false),
+ gathered(false),
+
+ posts_count(0),
+ posts_virtuals_count(0),
+ posts_cleared_count(0),
+ posts_last_7_count(0),
+ posts_last_30_count(0),
+ posts_this_month_count(0) {}
+
+ details_t& operator+=(const details_t& other);
+
+ void update(post_t& post, bool gather_all = false);
+ };
+
+ details_t self_details;
+ details_t family_details;
+ posts_list reported_posts;
+
+ std::list<sort_value_t> sort_values;
+
+ xdata_t() : supports_flags<>()
+ {
+ TRACE_CTOR(account_t::xdata_t, "");
+ }
+ xdata_t(const xdata_t& other)
+ : supports_flags<>(other.flags()),
+ self_details(other.self_details),
+ family_details(other.family_details),
+ sort_values(other.sort_values)
+ {
+ TRACE_CTOR(account_t::xdata_t, "copy");
+ }
+ ~xdata_t() throw() {
+ TRACE_DTOR(account_t::xdata_t);
+ }
+ };
+
+ // This variable holds optional "extended data" which is usually produced
+ // only during reporting, and only for the posting 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_t& xdata() {
+ if (! xdata_)
+ xdata_ = xdata_t();
+ return *xdata_;
+ }
+ const xdata_t& xdata() const {
+ assert(xdata_);
+ return *xdata_;
+ }
+
+ value_t amount(const optional<expr_t&>& expr = none) const;
+ value_t total(const optional<expr_t&>& expr = none) const;
+
+ const xdata_t::details_t& self_details(bool gather_all = true) const;
+ const xdata_t::details_t& family_details(bool gather_all = true) const;
+
+ bool has_xflags(xdata_t::flags_t flags) const {
+ return xdata_ && xdata_->has_flags(flags);
+ }
+ bool children_with_xdata() const;
+ std::size_t children_with_flags(xdata_t::flags_t flags) const;
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & boost::serialization::base_object<supports_flags<> >(*this);
+ ar & boost::serialization::base_object<scope_t>(*this);
+ ar & parent;
+ ar & name;
+ ar & note;
+ ar & depth;
+ ar & accounts;
+ ar & posts;
+ ar & _fullname;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+std::ostream& operator<<(std::ostream& out, const account_t& account);
+
+} // namespace ledger
+
+#endif // _ACCOUNT_H
diff --git a/src/accum.cc b/src/accum.cc
new file mode 100644
index 00000000..b918c76a
--- /dev/null
+++ b/src/accum.cc
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "utils.h"
+
+namespace ledger {
+
+std::streamsize straccbuf::xsputn(const char * s, std::streamsize num)
+{
+ if (index == 0) {
+ // The first item received is the format string
+ str = std::string(s, num);
+ index++;
+ return num;
+ }
+ else {
+ std::ostringstream buf;
+
+ // Every item thereafter is an argument that substitutes for %# in the
+ // format string
+ bool matched = false;
+ for (const char * p = str.c_str(); *p; p++) {
+ if (*p == '%') {
+ const char * q = p + 1;
+ if (*q && *q != '%' && std::isdigit(*q) &&
+ std::string::size_type(*q - '0') == index) {
+ p++;
+ buf << std::string(s, num);
+ matched = true;
+ } else {
+ buf << *p;
+ }
+ } else {
+ buf << *p;
+ }
+ }
+ if (! matched)
+ buf << std::string(s, num);
+
+ str = buf.str();
+ index++;
+ return num;
+ }
+}
+
+} // namespace ledger
diff --git a/src/accum.h b/src/accum.h
new file mode 100644
index 00000000..878c2b7c
--- /dev/null
+++ b/src/accum.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup util
+ */
+
+/**
+ * @file accum.h
+ * @author John Wiegley
+ *
+ * @ingroup util
+ */
+#ifndef _ACCUM_H
+#define _ACCUM_H
+
+namespace ledger {
+
+class straccbuf : public std::streambuf
+{
+protected:
+ std::string str; // accumulator
+ std::string::size_type index;
+
+public:
+ straccbuf() : index(0) {}
+
+protected:
+ virtual std::streamsize xsputn(const char * s, std::streamsize num);
+
+ friend class straccstream;
+};
+
+class straccstream : public std::ostream
+{
+protected:
+ straccbuf buf;
+
+public:
+ straccstream() : std::ostream(0) {
+ rdbuf(&buf);
+ }
+
+ void clear() {
+ buf.str.clear();
+ buf.index = 0;
+ }
+
+ std::string str() const {
+ return buf.str;
+ }
+};
+
+#define ACCUM(obj) (static_cast<straccstream&>(obj).str())
+
+} // namespace ledger
+
+#endif // _ACCUM_H
diff --git a/src/amount.cc b/src/amount.cc
new file mode 100644
index 00000000..eddbca18
--- /dev/null
+++ b/src/amount.cc
@@ -0,0 +1,1278 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "amount.h"
+#include "commodity.h"
+#include "annotate.h"
+#include "pool.h"
+
+namespace ledger {
+
+bool amount_t::stream_fullstrings = false;
+
+#if !defined(THREADSAFE)
+// These global temporaries are pre-initialized for the sake of
+// efficiency, and are reused over and over again.
+static mpz_t temp;
+static mpq_t tempq;
+static mpfr_t tempf;
+static mpfr_t tempfb;
+#endif
+
+struct amount_t::bigint_t : public supports_flags<>
+{
+#define BIGINT_BULK_ALLOC 0x01
+#define BIGINT_KEEP_PREC 0x02
+
+ mpq_t val;
+ precision_t prec;
+ uint_least32_t refc;
+
+#define MP(bigint) ((bigint)->val)
+
+ bigint_t() : prec(0), refc(1) {
+ TRACE_CTOR(bigint_t, "");
+ mpq_init(val);
+ }
+ bigint_t(const bigint_t& other)
+ : supports_flags<>(static_cast<uint_least8_t>
+ (other.flags() & ~BIGINT_BULK_ALLOC)),
+ prec(other.prec), refc(1) {
+ TRACE_CTOR(bigint_t, "copy");
+ mpq_init(val);
+ mpq_set(val, other.val);
+ }
+ ~bigint_t() {
+ TRACE_DTOR(bigint_t);
+ assert(refc == 0);
+ mpq_clear(val);
+ }
+
+ bool valid() const {
+ if (prec > 1024) {
+ DEBUG("ledger.validate", "amount_t::bigint_t: prec > 1024");
+ return false;
+ }
+ if (flags() & ~(BIGINT_BULK_ALLOC | BIGINT_KEEP_PREC)) {
+ DEBUG("ledger.validate",
+ "amount_t::bigint_t: flags() & ~(BULK_ALLOC | KEEP_PREC)");
+ return false;
+ }
+ return true;
+ }
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */)
+ {
+ ar & boost::serialization::base_object<supports_flags<> >(*this);
+ ar & val;
+ ar & prec;
+ ar & refc;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+bool amount_t::is_initialized = false;
+
+namespace {
+ void stream_out_mpq(std::ostream& out,
+ mpq_t quant,
+ amount_t::precision_t prec,
+ int zeros_prec = -1,
+ const optional<commodity_t&>& comm = none)
+ {
+ char * buf = NULL;
+ try {
+ IF_DEBUG("amount.convert") {
+ char * tbuf = mpq_get_str(NULL, 10, quant);
+ DEBUG("amount.convert", "Rational to convert = " << tbuf);
+ std::free(tbuf);
+ }
+
+ // Convert the rational number to a floating-point, extending the
+ // floating-point to a large enough size to get a precise answer.
+ const std::size_t bits = (mpz_sizeinbase(mpq_numref(quant), 2) +
+ mpz_sizeinbase(mpq_denref(quant), 2));
+ mpfr_set_prec(tempfb, bits + amount_t::extend_by_digits*8);
+ mpfr_set_q(tempfb, quant, GMP_RNDN);
+
+ mpfr_asprintf(&buf, "%.*Rf", prec, tempfb);
+ DEBUG("amount.convert",
+ "mpfr_print = " << buf << " (precision " << prec << ")");
+
+ if (zeros_prec >= 0) {
+ string::size_type index = std::strlen(buf);
+ string::size_type point = 0;
+ for (string::size_type i = 0; i < index; i++) {
+ if (buf[i] == '.') {
+ point = i;
+ break;
+ }
+ }
+ if (point > 0) {
+ while (--index >= (point + 1 + zeros_prec) && buf[index] == '0')
+ buf[index] = '\0';
+ if (index >= (point + zeros_prec) && buf[index] == '.')
+ buf[index] = '\0';
+ }
+ }
+
+ if (comm) {
+ int integer_digits = 0;
+ if (comm && comm->has_flags(COMMODITY_STYLE_THOUSANDS)) {
+ // Count the number of integer digits
+ for (const char * p = buf; *p; p++) {
+ if (*p == '.')
+ break;
+ else if (*p != '-')
+ integer_digits++;
+ }
+ }
+
+ for (const char * p = buf; *p; p++) {
+ if (*p == '.') {
+ if (commodity_t::european_by_default ||
+ (comm && comm->has_flags(COMMODITY_STYLE_EUROPEAN)))
+ out << ',';
+ else
+ out << *p;
+ assert(integer_digits <= 3);
+ }
+ else if (*p == '-') {
+ out << *p;
+ }
+ else {
+ out << *p;
+
+ if (integer_digits > 3 && --integer_digits % 3 == 0) {
+ if (commodity_t::european_by_default ||
+ (comm && comm->has_flags(COMMODITY_STYLE_EUROPEAN)))
+ out << '.';
+ else
+ out << ',';
+ }
+ }
+ }
+ } else {
+ out << buf;
+ }
+ }
+ catch (...) {
+ if (buf != NULL)
+ mpfr_free_str(buf);
+ throw;
+ }
+ if (buf != NULL)
+ mpfr_free_str(buf);
+ }
+}
+
+void amount_t::initialize()
+{
+ if (! is_initialized) {
+ mpz_init(temp);
+ mpq_init(tempq);
+ mpfr_init(tempf);
+ mpfr_init(tempfb);
+
+ commodity_pool_t::current_pool.reset(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 = commodity_pool_t::current_pool->create("s"))
+ commodity->add_flags(COMMODITY_BUILTIN | COMMODITY_NOMARKET);
+ else
+ assert(false);
+
+ // Add a "percentile" commodity
+ if (commodity_t * commodity = commodity_pool_t::current_pool->create("%"))
+ commodity->add_flags(COMMODITY_BUILTIN | COMMODITY_NOMARKET);
+ else
+ assert(false);
+
+ is_initialized = true;
+ }
+}
+
+void amount_t::shutdown()
+{
+ if (is_initialized) {
+ mpz_clear(temp);
+ mpq_clear(tempq);
+ mpfr_clear(tempf);
+ mpfr_clear(tempfb);
+
+ commodity_pool_t::current_pool.reset();
+
+ is_initialized = false;
+ }
+}
+
+void amount_t::_copy(const amount_t& amt)
+{
+ VERIFY(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 << " refc++, now " << (quantity->refc + 1));
+ quantity->refc++;
+ }
+ }
+ commodity_ = amt.commodity_;
+
+ VERIFY(valid());
+}
+
+void amount_t::_dup()
+{
+ VERIFY(valid());
+
+ if (quantity->refc > 1) {
+ bigint_t * q = new bigint_t(*quantity);
+ _release();
+ quantity = q;
+ }
+
+ VERIFY(valid());
+}
+
+void amount_t::_clear()
+{
+ if (quantity) {
+ _release();
+ quantity = NULL;
+ commodity_ = NULL;
+ } else {
+ assert(! commodity_);
+ }
+}
+
+void amount_t::_release()
+{
+ VERIFY(valid());
+
+ DEBUG("amounts.refs", quantity << " refc--, now " << (quantity->refc - 1));
+
+ if (--quantity->refc == 0) {
+ if (quantity->has_flags(BIGINT_BULK_ALLOC))
+ quantity->~bigint_t();
+ else
+ checked_delete(quantity);
+ quantity = NULL;
+ commodity_ = NULL;
+ }
+
+ VERIFY(valid());
+}
+
+
+amount_t::amount_t(const double val) : commodity_(NULL)
+{
+ TRACE_CTOR(amount_t, "const double");
+ quantity = new bigint_t;
+ mpq_set_d(MP(quantity), val);
+ quantity->prec = extend_by_digits; // an approximation
+}
+
+amount_t::amount_t(const unsigned long val) : commodity_(NULL)
+{
+ TRACE_CTOR(amount_t, "const unsigned long");
+ quantity = new bigint_t;
+ mpq_set_ui(MP(quantity), val, 1);
+}
+
+amount_t::amount_t(const long val) : commodity_(NULL)
+{
+ TRACE_CTOR(amount_t, "const long");
+ quantity = new bigint_t;
+ mpq_set_si(MP(quantity), val, 1);
+}
+
+
+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
+{
+ VERIFY(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: %1 and %2")
+ << commodity().symbol() << amt.commodity().symbol());
+
+ return mpq_cmp(MP(quantity), MP(amt.quantity));
+}
+
+bool amount_t::operator==(const amount_t& amt) const
+{
+ if ((quantity && ! amt.quantity) || (! quantity && amt.quantity))
+ return false;
+ else if (! quantity && ! amt.quantity)
+ return true;
+ else if (commodity() != amt.commodity())
+ return false;
+
+ return mpq_equal(MP(quantity), MP(amt.quantity));
+}
+
+
+amount_t& amount_t::operator+=(const amount_t& amt)
+{
+ VERIFY(amt.valid());
+
+ if (! quantity || ! amt.quantity) {
+ if (quantity)
+ throw_(amount_error, _("Cannot add an uninitialized amount to an amount"));
+ else if (amt.quantity)
+ throw_(amount_error, _("Cannot add an amount to an uninitialized amount"));
+ else
+ throw_(amount_error, _("Cannot add two uninitialized amounts"));
+ }
+
+ if (has_commodity() && amt.has_commodity() &&
+ commodity() != amt.commodity())
+ throw_(amount_error,
+ _("Adding amounts with different commodities: %1 != %2")
+ << (has_commodity() ? commodity().symbol() : _("NONE"))
+ << (amt.has_commodity() ? amt.commodity().symbol() : _("NONE")));
+
+ _dup();
+
+ mpq_add(MP(quantity), MP(quantity), MP(amt.quantity));
+
+ if (has_commodity() == amt.has_commodity())
+ if (quantity->prec < amt.quantity->prec)
+ quantity->prec = amt.quantity->prec;
+
+ return *this;
+}
+
+amount_t& amount_t::operator-=(const amount_t& amt)
+{
+ VERIFY(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 (has_commodity() && amt.has_commodity() &&
+ commodity() != amt.commodity())
+ throw_(amount_error,
+ _("Subtracting amounts with different commodities: %1 != %2")
+ << (has_commodity() ? commodity().symbol() : _("NONE"))
+ << (amt.has_commodity() ? amt.commodity().symbol() : _("NONE")));
+
+ _dup();
+
+ mpq_sub(MP(quantity), MP(quantity), MP(amt.quantity));
+
+ if (has_commodity() == amt.has_commodity())
+ if (quantity->prec < amt.quantity->prec)
+ quantity->prec = amt.quantity->prec;
+
+ return *this;
+}
+
+amount_t& amount_t::operator*=(const amount_t& amt)
+{
+ VERIFY(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"));
+ }
+
+ _dup();
+
+ mpq_mul(MP(quantity), MP(quantity), MP(amt.quantity));
+ quantity->prec =
+ static_cast<precision_t>(quantity->prec + amt.quantity->prec);
+
+ if (! has_commodity())
+ commodity_ = amt.commodity_;
+
+ if (has_commodity() && ! keep_precision()) {
+ precision_t comm_prec = commodity().precision();
+ if (quantity->prec > comm_prec + extend_by_digits)
+ quantity->prec = static_cast<precision_t>(comm_prec + extend_by_digits);
+ }
+
+ return *this;
+}
+
+amount_t& amount_t::operator/=(const amount_t& amt)
+{
+ VERIFY(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 (! 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.
+
+ mpq_div(MP(quantity), MP(quantity), MP(amt.quantity));
+ quantity->prec =
+ static_cast<precision_t>(quantity->prec + amt.quantity->prec +
+ extend_by_digits);
+
+ 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() && ! keep_precision()) {
+ precision_t comm_prec = commodity().precision();
+ if (quantity->prec > comm_prec + extend_by_digits)
+ quantity->prec = static_cast<precision_t>(comm_prec + extend_by_digits);
+ }
+
+ 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;
+}
+
+bool amount_t::keep_precision() const
+{
+ if (! quantity)
+ throw_(amount_error,
+ _("Cannot determine if precision of an uninitialized amount is kept"));
+
+ return quantity->has_flags(BIGINT_KEEP_PREC);
+}
+
+void amount_t::set_keep_precision(const bool keep) const
+{
+ if (! quantity)
+ throw_(amount_error,
+ _("Cannot set whether to keep the precision of an uninitialized amount"));
+
+ if (keep)
+ quantity->add_flags(BIGINT_KEEP_PREC);
+ else
+ quantity->drop_flags(BIGINT_KEEP_PREC);
+}
+
+amount_t::precision_t amount_t::display_precision() const
+{
+ if (! quantity)
+ throw_(amount_error,
+ _("Cannot determine display precision of an uninitialized amount"));
+
+ commodity_t& comm(commodity());
+
+ if (! comm || keep_precision())
+ return quantity->prec;
+ else if (comm.precision() != quantity->prec)
+ return comm.precision();
+ else
+ return quantity->prec;
+}
+
+void amount_t::in_place_negate()
+{
+ if (quantity) {
+ _dup();
+ mpq_neg(MP(quantity), MP(quantity));
+ } else {
+ throw_(amount_error, _("Cannot negate an uninitialized amount"));
+ }
+}
+
+amount_t amount_t::inverted() const
+{
+ if (! quantity)
+ throw_(amount_error, _("Cannot invert an uninitialized amount"));
+
+ amount_t t(*this);
+ t._dup();
+ mpq_inv(MP(t.quantity), MP(t.quantity));
+
+ return t;
+}
+
+void amount_t::in_place_round()
+{
+ if (! quantity)
+ throw_(amount_error, _("Cannot set rounding for an uninitialized amount"));
+ else if (! keep_precision())
+ return;
+
+ _dup();
+ set_keep_precision(false);
+}
+
+void amount_t::in_place_floor()
+{
+ if (! quantity)
+ throw_(amount_error, _("Cannot floor an uninitialized amount"));
+
+ _dup();
+
+ std::ostringstream out;
+ stream_out_mpq(out, MP(quantity), 0);
+
+ mpq_set_str(MP(quantity), out.str().c_str(), 10);
+}
+
+void amount_t::in_place_unround()
+{
+ if (! quantity)
+ throw_(amount_error, _("Cannot unround an uninitialized amount"));
+ else if (keep_precision())
+ return;
+
+ _dup();
+
+ DEBUG("amount.unround", "Unrounding " << *this);
+ set_keep_precision(true);
+ DEBUG("amount.unround", "Unrounded = " << *this);
+}
+
+void 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_;
+ }
+}
+
+void amount_t::in_place_unreduce()
+{
+ if (! quantity)
+ throw_(amount_error, _("Cannot unreduce an uninitialized amount"));
+
+ amount_t temp = *this;
+ commodity_t * comm = commodity_;
+ bool shifted = false;
+
+ while (comm && comm->larger()) {
+ amount_t next_temp = temp / comm->larger()->number();
+ if (next_temp.abs() < amount_t(1L))
+ break;
+ temp = next_temp;
+ comm = comm->larger()->commodity_;
+ shifted = true;
+ }
+
+ if (shifted) {
+ *this = temp;
+ commodity_ = comm;
+ }
+}
+
+optional<amount_t>
+amount_t::value(const bool primary_only,
+ const optional<datetime_t>& moment,
+ const optional<commodity_t&>& in_terms_of) const
+{
+ if (quantity) {
+#if defined(DEBUG_ON)
+ DEBUG("commodity.prices.find",
+ "amount_t::value of " << commodity().symbol());
+ if (moment)
+ DEBUG("commodity.prices.find",
+ "amount_t::value: moment = " << *moment);
+ if (in_terms_of)
+ DEBUG("commodity.prices.find",
+ "amount_t::value: in_terms_of = " << in_terms_of->symbol());
+#endif
+ if (has_commodity() &&
+ (! primary_only || ! commodity().has_flags(COMMODITY_PRIMARY))) {
+ if (in_terms_of && commodity() == *in_terms_of) {
+ return *this;
+ }
+ else if (has_annotation() && annotation().price &&
+ annotation().has_flags(ANNOTATION_PRICE_FIXATED)) {
+ return (*annotation().price * number()).rounded();
+ }
+ else {
+ optional<price_point_t> point =
+ commodity().find_price(in_terms_of, moment);
+
+ // Whether a price was found or not, check whether we should attempt
+ // to download a price from the Internet. This is done if (a) no
+ // price was found, or (b) the price is "stale" according to the
+ // setting of --price-exp.
+ point = commodity().check_for_updated_price(point, moment, in_terms_of);
+ if (point)
+ return (point->price * number()).rounded();
+ }
+ }
+ } else {
+ throw_(amount_error,
+ _("Cannot determine value of an uninitialized amount"));
+ }
+ return none;
+}
+
+amount_t amount_t::price() const
+{
+ if (has_annotation() && annotation().price) {
+ amount_t temp(*annotation().price);
+ temp *= *this;
+ DEBUG("amount.price", "Returning price of " << *this << " = " << temp);
+ return temp;
+ }
+ return *this;
+}
+
+
+int amount_t::sign() const
+{
+ if (! quantity)
+ throw_(amount_error, _("Cannot determine sign of an uninitialized amount"));
+
+ return mpq_sgn(MP(quantity));
+}
+
+bool amount_t::is_zero() const
+{
+ if (! quantity)
+ throw_(amount_error, _("Cannot determine if an uninitialized amount is zero"));
+
+ if (has_commodity()) {
+ if (keep_precision() || quantity->prec <= commodity().precision()) {
+ return is_realzero();
+ }
+ else if (is_realzero()) {
+ return true;
+ }
+ else if (mpz_cmp(mpq_numref(MP(quantity)),
+ mpq_denref(MP(quantity))) > 0) {
+ DEBUG("amount.is_zero", "Numerator is larger than the denominator");
+ return false;
+ }
+ else {
+ DEBUG("amount.is_zero", "We have to print the number to check for zero");
+
+ std::ostringstream out;
+ stream_out_mpq(out, MP(quantity), commodity().precision());
+
+ string output = out.str();
+ if (! output.empty()) {
+ for (const char * p = output.c_str(); *p; p++)
+ if (*p != '0' && *p != '.' && *p != '-')
+ return false;
+ }
+ return true;
+ }
+ }
+ return is_realzero();
+}
+
+
+double amount_t::to_double() const
+{
+ if (! quantity)
+ throw_(amount_error, _("Cannot convert an uninitialized amount to a double"));
+
+ mpfr_set_q(tempf, MP(quantity), GMP_RNDN);
+ return mpfr_get_d(tempf, GMP_RNDN);
+}
+
+long amount_t::to_long() const
+{
+ if (! quantity)
+ throw_(amount_error, _("Cannot convert an uninitialized amount to a long"));
+
+ mpfr_set_q(tempf, MP(quantity), GMP_RNDN);
+ return mpfr_get_si(tempf, GMP_RNDN);
+}
+
+bool amount_t::fits_in_long() const
+{
+ mpfr_set_q(tempf, MP(quantity), GMP_RNDN);
+ return mpfr_fits_slong_p(tempf, GMP_RNDN);
+}
+
+commodity_t& amount_t::commodity() const
+{
+ return (has_commodity() ?
+ *commodity_ : *commodity_pool_t::current_pool->null_commodity);
+}
+
+bool amount_t::has_commodity() const
+{
+ return commodity_ && commodity_ != commodity_->pool().null_commodity;
+}
+
+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())
+ return; // ignore attempt to annotate a "bare commodity
+
+ if (commodity().has_annotation()) {
+ 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->pool().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::has_annotation() const
+{
+ if (! quantity)
+ throw_(amount_error,
+ _("Cannot determine if an uninitialized amount's commodity is annotated"));
+
+ assert(! has_commodity() || ! commodity().has_annotation() ||
+ as_annotated_commodity(commodity()).details);
+ return has_commodity() && commodity().has_annotation();
+}
+
+annotation_t& amount_t::annotation()
+{
+ if (! quantity)
+ throw_(amount_error,
+ _("Cannot return commodity annotation details of an uninitialized amount"));
+
+ if (! commodity().has_annotation())
+ 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 keep_details_t& what_to_keep) const
+{
+ if (! quantity)
+ throw_(amount_error,
+ _("Cannot strip commodity annotations from an uninitialized amount"));
+
+ if (! what_to_keep.keep_all(commodity())) {
+ amount_t t(*this);
+ t.set_commodity(commodity().strip_annotations(what_to_keep));
+ return t;
+ }
+ return *this;
+}
+
+
+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 == ',');
+
+ string::size_type 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, const parse_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 = static_cast<char>(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 = static_cast<char>(in.peek())) != '\n'))
+ details.parse(in);
+ }
+ } else {
+ commodity_t::parse_symbol(in, symbol);
+
+ if (! in.eof() && ((n = static_cast<char>(in.peek())) != '\n')) {
+ if (std::isspace(static_cast<char>(in.peek())))
+ comm_flags |= COMMODITY_STYLE_SEPARATED;
+
+ parse_quantity(in, quant);
+
+ if (! quant.empty() && ! in.eof() &&
+ ((n = static_cast<char>(in.peek())) != '\n'))
+ details.parse(in);
+ }
+ }
+
+ if (quant.empty()) {
+ if (flags.has_flags(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> new_quantity;
+
+ if (quantity) {
+ if (quantity->refc > 1)
+ _release();
+ else
+ new_quantity.reset(quantity);
+ quantity = NULL;
+ }
+
+ if (! new_quantity.get())
+ new_quantity.reset(new bigint_t);
+
+ // No one is holding a reference to this now.
+ new_quantity->refc--;
+
+ // 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_ = commodity_pool_t::current_pool->find(symbol);
+ if (! commodity_) {
+ commodity_ = commodity_pool_t::current_pool->create(symbol);
+ newly_created = true;
+ }
+ assert(commodity_);
+
+ if (details)
+ commodity_ =
+ commodity_pool_t::current_pool->find_or_create(*commodity_, details);
+ }
+
+ // Quickly scan through and verify the correctness of the amount's use of
+ // punctuation.
+
+ precision_t decimal_offset = 0;
+ string::size_type string_index = quant.length();
+ string::size_type last_comma = string::npos;
+ string::size_type last_period = string::npos;
+
+ bool no_more_commas = false;
+ bool no_more_periods = false;
+ bool european_style = (commodity_t::european_by_default ||
+ commodity().has_flags(COMMODITY_STYLE_EUROPEAN));
+
+ new_quantity->prec = 0;
+
+ BOOST_REVERSE_FOREACH (const char& ch, quant) {
+ string_index--;
+
+ if (ch == '.') {
+ if (no_more_periods)
+ throw_(amount_error, _("Too many periods in amount"));
+
+ if (european_style) {
+ if (decimal_offset % 3 != 0)
+ throw_(amount_error, _("Incorrect use of european-style period"));
+ comm_flags |= COMMODITY_STYLE_THOUSANDS;
+ no_more_commas = true;
+ } else {
+ if (last_comma != string::npos) {
+ european_style = true;
+ if (decimal_offset % 3 != 0)
+ throw_(amount_error, _("Incorrect use of european-style period"));
+ } else {
+ no_more_periods = true;
+ new_quantity->prec = decimal_offset;
+ decimal_offset = 0;
+ }
+ }
+
+ if (last_period == string::npos)
+ last_period = string_index;
+ }
+ else if (ch == ',') {
+ if (no_more_commas)
+ throw_(amount_error, _("Too many commas in amount"));
+
+ if (european_style) {
+ if (last_period != string::npos) {
+ throw_(amount_error, _("Incorrect use of european-style comma"));
+ } else {
+ no_more_commas = true;
+ new_quantity->prec = decimal_offset;
+ decimal_offset = 0;
+ }
+ } else {
+ if (decimal_offset % 3 != 0) {
+ if (last_comma != string::npos ||
+ last_period != string::npos) {
+ throw_(amount_error, _("Incorrect use of American-style comma"));
+ } else {
+ european_style = true;
+ no_more_commas = true;
+ new_quantity->prec = decimal_offset;
+ decimal_offset = 0;
+ }
+ } else {
+ comm_flags |= COMMODITY_STYLE_THOUSANDS;
+ no_more_periods = true;
+ }
+ }
+
+ if (last_comma == string::npos)
+ last_comma = string_index;
+ }
+ else {
+ decimal_offset++;
+ }
+ }
+
+ if (european_style)
+ comm_flags |= COMMODITY_STYLE_EUROPEAN;
+
+ if (flags.has_flags(PARSE_NO_MIGRATE)) {
+ // Can't call set_keep_precision here, because it assumes that `quantity'
+ // is non-NULL.
+ new_quantity->add_flags(BIGINT_KEEP_PREC);
+ }
+ else if (commodity_) {
+ commodity().add_flags(comm_flags);
+
+ if (new_quantity->prec > commodity().precision())
+ commodity().set_precision(new_quantity->prec);
+ }
+
+ // Now we have the final number. Remove commas and periods, if necessary.
+
+ if (last_comma != string::npos || last_period != string::npos) {
+ string::size_type len = quant.length();
+ scoped_array<char> buf(new char[len + 1]);
+ const char * p = quant.c_str();
+ char * t = buf.get();
+
+ while (*p) {
+ if (*p == ',' || *p == '.')
+ p++;
+ *t++ = *p++;
+ }
+ *t = '\0';
+
+ mpq_set_str(MP(new_quantity.get()), buf.get(), 10);
+ mpz_ui_pow_ui(temp, 10, new_quantity->prec);
+ mpq_set_z(tempq, temp);
+ mpq_div(MP(new_quantity.get()), MP(new_quantity.get()), tempq);
+
+ IF_DEBUG("amount.parse") {
+ char * buf = mpq_get_str(NULL, 10, MP(new_quantity.get()));
+ DEBUG("amount.parse", "Rational parsed = " << buf);
+ std::free(buf);
+ }
+ } else {
+ mpq_set_str(MP(new_quantity.get()), quant.c_str(), 10);
+ }
+
+ if (negative)
+ mpq_neg(MP(new_quantity.get()), MP(new_quantity.get()));
+
+ new_quantity->refc++;
+ quantity = new_quantity.release();
+
+ if (! flags.has_flags(PARSE_NO_REDUCE))
+ in_place_reduce(); // will not throw an exception
+
+ VERIFY(valid());
+
+ return true;
+}
+
+void amount_t::parse_conversion(const string& larger_str,
+ const string& smaller_str)
+{
+ amount_t larger, smaller;
+
+ larger.parse(larger_str, PARSE_NO_REDUCE);
+ smaller.parse(smaller_str, PARSE_NO_REDUCE);
+
+ larger *= smaller.number();
+
+ if (larger.commodity()) {
+ larger.commodity().set_smaller(smaller);
+ larger.commodity().add_flags(smaller.commodity().flags() |
+ COMMODITY_NOMARKET);
+ }
+ if (smaller.commodity())
+ smaller.commodity().set_larger(larger);
+}
+
+void amount_t::print(std::ostream& _out) const
+{
+ VERIFY(valid());
+
+ if (! quantity) {
+ _out << "<null>";
+ return;
+ }
+
+ std::ostringstream out;
+
+ commodity_t& comm(commodity());
+
+ if (! comm.has_flags(COMMODITY_STYLE_SUFFIXED)) {
+ comm.print(out);
+ if (comm.has_flags(COMMODITY_STYLE_SEPARATED))
+ out << " ";
+ }
+
+ stream_out_mpq(out, MP(quantity), display_precision(),
+ comm ? commodity().precision() : 0, comm);
+
+ if (comm.has_flags(COMMODITY_STYLE_SUFFIXED)) {
+ if (comm.has_flags(COMMODITY_STYLE_SEPARATED))
+ out << " ";
+ comm.print(out);
+ }
+
+ // If there are any annotations associated with this commodity, output them
+ // now.
+ comm.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();
+}
+
+bool amount_t::valid() const
+{
+ if (quantity) {
+ if (! quantity->valid()) {
+ DEBUG("ledger.validate", "amount_t: ! quantity->valid()");
+ return false;
+ }
+
+ if (quantity->refc == 0) {
+ DEBUG("ledger.validate", "amount_t: quantity->refc == 0");
+ return false;
+ }
+ }
+ else if (commodity_) {
+ DEBUG("ledger.validate", "amount_t: commodity_ != NULL");
+ return false;
+ }
+ return true;
+}
+
+void to_xml(std::ostream& out, const amount_t& amt, bool commodity_details)
+{
+ push_xml x(out, "amount");
+
+ if (amt.has_commodity())
+ to_xml(out, amt.commodity(), commodity_details);
+
+ {
+ push_xml y(out, "quantity");
+ out << y.guard(amt.quantity_string());
+ }
+}
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+
+template<class Archive>
+void amount_t::serialize(Archive& ar, const unsigned int /* version */)
+{
+ ar & is_initialized;
+ ar & quantity;
+ ar & commodity_;
+}
+
+#endif // HAVE_BOOST_SERIALIZATION
+
+} // namespace ledger
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+namespace boost {
+namespace serialization {
+
+template <class Archive>
+void serialize(Archive& ar, MP_INT& mpz, const unsigned int /* version */)
+{
+ ar & mpz._mp_alloc;
+ ar & mpz._mp_size;
+ ar & mpz._mp_d;
+}
+
+template <class Archive>
+void serialize(Archive& ar, MP_RAT& mpq, const unsigned int /* version */)
+{
+ ar & mpq._mp_num;
+ ar & mpq._mp_den;
+}
+
+template <class Archive>
+void serialize(Archive& ar, long unsigned int& integer,
+ const unsigned int /* version */)
+{
+ ar & make_binary_object(&integer, sizeof(long unsigned int));
+}
+
+} // namespace serialization
+} // namespace boost
+
+BOOST_CLASS_EXPORT(ledger::annotated_commodity_t)
+
+template void boost::serialization::serialize(boost::archive::binary_iarchive&,
+ MP_INT&, const unsigned int);
+template void boost::serialization::serialize(boost::archive::binary_oarchive&,
+ MP_INT&, const unsigned int);
+template void boost::serialization::serialize(boost::archive::binary_iarchive&,
+ MP_RAT&, const unsigned int);
+template void boost::serialization::serialize(boost::archive::binary_oarchive&,
+ MP_RAT&, const unsigned int);
+template void boost::serialization::serialize(boost::archive::binary_iarchive&,
+ long unsigned int&,
+ const unsigned int);
+template void boost::serialization::serialize(boost::archive::binary_oarchive&,
+ long unsigned int&,
+ const unsigned int);
+
+template void ledger::amount_t::serialize(boost::archive::binary_iarchive&,
+ const unsigned int);
+template void ledger::amount_t::serialize(boost::archive::binary_oarchive&,
+ const unsigned int);
+
+#endif // HAVE_BOOST_SERIALIZATION
diff --git a/src/amount.h b/src/amount.h
new file mode 100644
index 00000000..a37efdb8
--- /dev/null
+++ b/src/amount.h
@@ -0,0 +1,761 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @defgroup math Mathematical objects
+ */
+
+/**
+ * @file amount.h
+ * @author John Wiegley
+ *
+ * @ingroup math
+ *
+ * @brief Basic type for handling commoditized math: amount_t
+ *
+ * An amount is the most basic numerical type in Ledger, and relies on
+ * commodity.h to represent commoditized amounts, which allows Ledger to
+ * handle mathematical expressions involving disparate commodities.
+ *
+ * Amounts can be of virtually infinite size and precision. When
+ * division or multiplication is performed, the precision is
+ * automatically expanded to include as many extra digits as necessary
+ * to avoid losing information.
+ */
+#ifndef _AMOUNT_H
+#define _AMOUNT_H
+
+#include "utils.h"
+#include "times.h"
+#include "flags.h"
+
+namespace ledger {
+
+class commodity_t;
+class annotation_t;
+class keep_details_t;
+class commodity_pool_t;
+
+DECLARE_EXCEPTION(amount_error, std::runtime_error);
+
+enum parse_flags_enum_t {
+ PARSE_DEFAULT = 0x00,
+ PARSE_PARTIAL = 0x01,
+ PARSE_SINGLE = 0x02,
+ PARSE_NO_MIGRATE = 0x04,
+ PARSE_NO_REDUCE = 0x08,
+ PARSE_NO_ASSIGN = 0x10,
+ PARSE_NO_DATES = 0x20,
+ PARSE_OP_CONTEXT = 0x40,
+ PARSE_SOFT_FAIL = 0x80
+};
+
+typedef basic_flags_t<parse_flags_enum_t, uint_least8_t> parse_flags_t;
+
+/**
+ * @brief Encapsulate infinite-precision commoditized amounts
+ *
+ * Used to represent commoditized infinite-precision numbers, and
+ * uncommoditized, plain numbers. In the commoditized case, commodities
+ * keep track of how they are used, and are always displayed back to the
+ * user after the same fashion. For uncommoditized numbers, no display
+ * truncation is ever done. In both cases, internal precision is always
+ * kept to an excessive degree.
+ */
+class amount_t
+ : public ordered_field_operators<amount_t,
+ ordered_field_operators<amount_t, double,
+ ordered_field_operators<amount_t, unsigned long,
+ ordered_field_operators<amount_t, long> > > >
+{
+public:
+ /** Ready the amount subsystem for use.
+ @note Normally called by session_t::initialize(). */
+ static void initialize();
+ /** Shutdown the amount subsystem and free all resources.
+ @note Normally called by session_t::shutdown(). */
+ static void shutdown();
+
+ static bool is_initialized;
+
+ /** The amount's decimal precision. */
+ typedef uint_least16_t precision_t;
+
+ /** Number of places of precision by which values are extended to
+ avoid losing precision during division and multiplication. */
+ static const std::size_t extend_by_digits = 6U;
+
+ /** If amounts should be streamed using to_fullstring() rather than
+ to_string(), so that complete precision is always displayed no matter
+ what the precision of an individual commodity may be. */
+ static bool stream_fullstrings;
+
+protected:
+ void _copy(const amount_t& amt);
+ void _dup();
+ void _clear();
+ void _release();
+
+ struct bigint_t;
+
+ bigint_t * quantity;
+ commodity_t * commodity_;
+
+public:
+ /** @name Constructors
+ @{ */
+
+ /** Creates a value for which is_null() is true, and which has no
+ value or commodity. If used in a value expression it evaluates to
+ zero, and its commodity equals \c commodity_t::null_commodity. */
+ amount_t() : quantity(NULL), commodity_(NULL) {
+ TRACE_CTOR(amount_t, "");
+ }
+
+ /** Convert a double to an amount. As much precision as possible is
+ decoded from the binary floating point number. */
+ amount_t(const double val);
+
+ /** Convert an unsigned long to an amount. It's precision is zero. */
+ amount_t(const unsigned long val);
+
+ /** Convert a long to an amount. It's precision is zero, and the sign
+ is preserved. */
+ amount_t(const long val);
+
+ /** Parse a string as an (optionally commoditized) amount. If no
+ commodity is present, the resulting commodity is \c
+ commodity_t::null_commodity. The number may be of infinite
+ precision. */
+ explicit amount_t(const string& val) : quantity(NULL) {
+ TRACE_CTOR(amount_t, "const string&");
+ parse(val);
+ }
+ /** Parse a pointer to a C string as an (optionally commoditized)
+ amount. If no commodity is present, the resulting commodity is \c
+ commodity_t::null_commodity. The number may be of infinite
+ precision. */
+ explicit amount_t(const char * val) : quantity(NULL) {
+ TRACE_CTOR(amount_t, "const char *");
+ assert(val);
+ parse(val);
+ }
+
+ /*@}*/
+
+ /** Create an amount whose display precision is never truncated, even
+ if the amount uses a commodity (which normally causes "round on
+ streaming" to occur). This function is mostly used by debugging
+ code and unit tests. This is the proper way to specify \c
+ $100.005, where display of the extra digit precision is required.
+ If a regular constructor were used, the amount would stream as \c
+ $100.01, even though its internal value equals \c $100.005. */
+ static amount_t exact(const string& value);
+
+ /** Release the reference count held for the underlying \c
+ amount_t::bigint_t object. */
+ ~amount_t() {
+ TRACE_DTOR(amount_t);
+ if (quantity)
+ _release();
+ }
+
+ /** @name Assignment and copy
+ @{*/
+
+ /** Copy an amount object. Copies are very efficient, using a
+ copy-on-write model. Until the copy is changed, it refers to the
+ same memory used by the original via reference counting. The \c
+ amount_t::bigint_t class in amount.cc maintains the reference. */
+ amount_t(const amount_t& amt) : quantity(NULL) {
+ TRACE_CTOR(amount_t, "copy");
+ if (amt.quantity)
+ _copy(amt);
+ else
+ commodity_ = NULL;
+ }
+ /** Copy an amount object, applying the given commodity annotation
+ details afterward. This is equivalent to doing a normal copy
+ (@see amount_t(const amount_t&)) and then calling
+ amount_t::annotate(). */
+ amount_t(const amount_t& amt, const annotation_t& details) : quantity(NULL) {
+ TRACE_CTOR(amount_t, "const amount_t&, const annotation_t&");
+ assert(amt.quantity);
+ _copy(amt);
+ annotate(details);
+ }
+ /** Assign an amount object. This is like copying if the amount was
+ null beforehand, otherwise the previous value's reference is must
+ be freed. */
+ amount_t& operator=(const amount_t& amt);
+
+ amount_t& operator=(const double val) {
+ return *this = amount_t(val);
+ }
+ amount_t& operator=(const unsigned long val) {
+ return *this = amount_t(val);
+ }
+ amount_t& operator=(const long val) {
+ return *this = amount_t(val);
+ }
+
+ /* Assign a string to an amount. This causes the contents of the
+ string to be parsed, look for a commoditized or uncommoditized
+ amount specifier. */
+ amount_t& operator=(const string& str) {
+ return *this = amount_t(str);
+ }
+ amount_t& operator=(const char * str) {
+ assert(str);
+ return *this = amount_t(str);
+ }
+
+ /*@}*/
+
+ /** @name Comparison
+ @{ */
+
+ /** Compare two amounts, returning a number less than zero if \p amt
+ is greater, exactly zero if they are equal, and greater than zero
+ if \p amt is less. This method is used to implement all of the
+ other comparison methods.*/
+ int compare(const amount_t& amt) const;
+
+ /** Test two amounts for equality. First the commodity pointers are
+ quickly tested, then the multi-precision values themselves must be
+ compared. */
+ bool operator==(const amount_t& amt) const;
+
+ template <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;
+ }
+
+ /*@}*/
+
+ /** @name Binary arithmetic
+ */
+ /*@{*/
+
+ amount_t& operator+=(const amount_t& amt);
+ amount_t& operator-=(const amount_t& amt);
+ amount_t& operator*=(const amount_t& amt);
+
+ /** Divide two amounts while extending the precision to preserve the
+ accuracy of the result. For example, if \c 10 is divided by \c 3,
+ the result ends up having a precision of \link
+ amount_t::extend_by_digits \endlink place to avoid losing internal
+ resolution. */
+ amount_t& operator/=(const amount_t& amt);
+
+ /*@}*/
+
+ /** @name Unary arithmetic
+ @{ */
+
+ /** Return an amount's internal precision. To find the precision it
+ should be displayed at -- assuming it was not created using
+ amount_t::exact() -- use the following expression instead:
+ @code
+ amount.commodity().precision()
+ @endcode */
+ precision_t precision() const;
+ bool keep_precision() const;
+ void set_keep_precision(const bool keep = true) const;
+ precision_t display_precision() const;
+
+ /** Returns the negated value of an amount.
+ @see operator-()
+ */
+ amount_t negated() const {
+ amount_t temp(*this);
+ temp.in_place_negate();
+ return temp;
+ }
+ void in_place_negate();
+
+ amount_t operator-() const {
+ return negated();
+ }
+
+ /** Returns the absolute value of an amount. Equivalent to:
+ @code
+ (x < * 0) ? - x : x
+ @endcode
+ */
+ amount_t abs() const {
+ if (sign() < 0)
+ return negated();
+ return *this;
+ }
+
+ amount_t inverted() const;
+
+ /** Yields an amount whose display precision when output is truncated
+ to the display precision of its commodity. This is normally the
+ default state of an amount, but if one has become unrounded, this
+ sets the "keep precision" state back to false.
+ @see set_keep_precision */
+ amount_t rounded() const {
+ amount_t temp(*this);
+ temp.in_place_round();
+ return temp;
+ }
+ void in_place_round();
+
+ /** Yields an amount which has lost all of its extra precision, beyond what
+ the display precision of the commodity would have printed. */
+ amount_t truncated() const {
+ amount_t temp(*this);
+ temp.in_place_truncate();
+ return temp;
+ }
+ void in_place_truncate() {
+ *this = amount_t(to_string());
+ }
+
+ /** Yields an amount which has lost all of its extra precision, beyond what
+ the display precision of the commodity would have printed. */
+ amount_t floored() const {
+ amount_t temp(*this);
+ temp.in_place_floor();
+ return temp;
+ }
+ void in_place_floor();
+
+ /** Yields an amount whose display precision is never truncated, even
+ though its commodity normally displays only rounded values. */
+ amount_t unrounded() const {
+ amount_t temp(*this);
+ temp.in_place_unround();
+ return temp;
+ }
+ void in_place_unround();
+
+ /** reduces a value to its most basic commodity form, for amounts that
+ utilize "scaling commodities". For example, an amount of \c 1h
+ after reduction will be \c 3600s.
+ */
+ amount_t reduced() const {
+ amount_t temp(*this);
+ temp.in_place_reduce();
+ return temp;
+ }
+ void in_place_reduce();
+
+ /** unreduce(), if used with a "scaling commodity", yields the most
+ compact form greater than one. That is, \c 3599s will unreduce to
+ \c 59.98m, while \c 3601 unreduces to \c 1h.
+ */
+ amount_t unreduced() const {
+ amount_t temp(*this);
+ temp.in_place_unreduce();
+ return temp;
+ }
+ void in_place_unreduce();
+
+ /** Returns the historical value for an amount -- the default moment
+ returns the most recently known price -- based on the price history
+ for the given commodity (or determined automatically, if none is
+ provided). For example, if the amount were <tt>10 AAPL</tt>, and
+ on Apr 10, 2000 each share of \c AAPL was worth \c $10, then
+ calling value() for that moment in time would yield the amount \c
+ $100.00.
+ */
+ optional<amount_t>
+ value(const bool primary_only = true,
+ const optional<datetime_t>& moment = none,
+ const optional<commodity_t&>& in_terms_of = none) const;
+
+ amount_t price() const;
+
+ /*@}*/
+
+ /** @name Truth tests
+ */
+ /*@{*/
+
+ /** Truth tests. An amount may be truth test in several ways:
+
+ sign() returns an integer less than, greater than, or equal to
+ zero depending on whether the amount is negative, zero, or
+ greater than zero. Note that this function tests the actual
+ value of the amount -- using its internal precision -- and not
+ the display value. To test its display value, use:
+ `round().sign()'.
+
+ is_nonzero(), or operator bool, returns true if an amount's
+ display value is not zero.
+
+ is_zero() returns true if an amount's display value is zero.
+ Thus, $0.0001 is considered zero if the current display precision
+ for dollars is two decimal places.
+
+ is_realzero() returns true if an amount's actual value is zero.
+ Thus, $0.0001 is never considered realzero.
+
+ is_null() returns true if an amount has no value and no
+ commodity. This only occurs if an uninitialized amount has never
+ been assigned a value.
+ */
+ int sign() const;
+
+ operator bool() const {
+ return is_nonzero();
+ }
+ bool is_nonzero() const {
+ return ! is_zero();
+ }
+
+ bool is_zero() const;
+ bool is_realzero() const {
+ return sign() == 0;
+ }
+
+ bool is_null() const {
+ if (! quantity) {
+ assert(! commodity_);
+ return true;
+ }
+ return false;
+ }
+
+ /*@}*/
+
+ /** @name Conversion
+ */
+ /*@{*/
+
+ /** Conversion methods. An amount may be converted to the same types
+ it can be constructed from -- with the exception of unsigned
+ long. Implicit conversions are not allowed in C++ (though they
+ are in Python), rather the following conversion methods must be
+ called explicitly:
+
+ to_double([bool]) returns an amount as a double. If the optional
+ boolean argument is true (the default), an exception is thrown if
+ the conversion would lose information.
+
+ to_long([bool]) returns an amount as a long integer. If the
+ optional boolean argument is true (the default), an exception is
+ thrown if the conversion would lose information.
+
+ fits_in_long() returns true if to_long() would not lose
+ precision.
+
+ to_string() returns an amount'ss "display value" as a string --
+ after rounding the value according to the commodity's default
+ precision. It is equivalent to: `round().to_fullstring()'.
+
+ to_fullstring() returns an amount's "internal value" as a string,
+ without any rounding.
+
+ quantity_string() returns an amount's "display value", but
+ without any commodity. Note that this is different from
+ `number().to_string()', because in that case the commodity has
+ been stripped and the full, internal precision of the amount
+ would be displayed.
+ */
+ double to_double() const;
+ long to_long() const;
+ bool fits_in_long() const;
+
+ operator string() const {
+ return to_string();
+ }
+ string to_string() const;
+ string to_fullstring() const;
+ string quantity_string() const;
+
+ /*@}*/
+
+ /** @name Commodity methods
+ */
+ /*@{*/
+
+ /** The following methods relate to an
+ amount's commodity:
+
+ commodity() returns an amount's commodity. If the amount has no
+ commodity, the value returned is `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;
+ }
+
+ /*@}*/
+
+ /** @name Commodity annotations
+ */
+ /*@{*/
+
+ /** An amount's commodity may be annotated with special details, such as the
+ price it was purchased for, when it was acquired, or an arbitrary note,
+ identifying perhaps the lot number of an item.
+
+ annotate_commodity(amount_t price, [datetime_t date, string tag])
+ sets the annotations for the current amount's commodity. Only
+ the price argument is required, although it can be passed as
+ `none' if no price is desired.
+
+ commodity_annotated() returns true if an amount's commodity has
+ any annotation details associated with it.
+
+ annotation_details() returns all of the details of an annotated
+ commodity's annotations. The structure returns will evaluate as
+ boolean false if there are no details.
+
+ strip_annotations() returns an amount whose commodity's annotations have
+ been stripped.
+ */
+ void annotate(const annotation_t& details);
+ bool has_annotation() const;
+
+ annotation_t& annotation();
+ const annotation_t& annotation() const {
+ return const_cast<amount_t&>(*this).annotation();
+ }
+
+ /** If the lot price is considered whenever working with commoditized
+ values.
+
+ Let's say a user adds two values of the following form:
+ @code
+ 10 AAPL + 10 AAPL {$20}
+ @endcode
+
+ This expression adds ten shares of Apple stock with another ten
+ shares that were purchased for \c $20 a share. If \c keep_price
+ is false, the result of this expression is an amount equal to
+ <tt>20 AAPL</tt>. If \c keep_price is \c true the expression
+ yields an exception for adding amounts with different commodities.
+ In that case, a \link balance_t \endlink object must be used to
+ store the combined sum. */
+ amount_t strip_annotations(const keep_details_t& what_to_keep) const;
+
+ /*@}*/
+
+ /** @name Parsing
+ */
+ /*@{*/
+
+ /** The `flags' argument of both parsing may be one or more of the
+ following:
+
+ PARSE_NO_MIGRATE means to not pay attention to the way an
+ amount is used. Ordinarily, if an amount were $100.001, for
+ example, it would cause the default display precision for $ to be
+ "widened" to three decimal places. If PARSE_NO_MIGRATE is
+ used, the commodity's default display precision is not changed.
+
+ PARSE_NO_REDUCE means not to call in_place_reduce() on the
+ resulting amount after it is parsed.
+
+ These parsing methods observe the amounts they parse (unless
+ PARSE_NO_MIGRATE is true), and set the display details of
+ the corresponding commodity accordingly. This way, amounts do
+ not require commodities to be pre-defined in any way, but merely
+ displays them back to the user in the same fashion as it saw them
+ used.
+
+ There is also a static convenience method called
+ `parse_conversion' which can be used to define a relationship
+ between scaling commodity values. For example, Ledger uses it to
+ define the relationships among various time values:
+
+ @code
+ amount_t::parse_conversion("1.0m", "60s"); // a minute is 60 seconds
+ amount_t::parse_conversion("1.0h", "60m"); // an hour is 60 minutes
+ @endcode
+
+ The method parse() is used to parse an amount from an input stream
+ or a string. A global operator>>() is also defined which simply
+ calls parse on the input stream. The parse() method has two forms:
+
+ parse(istream, flags_t) parses an amount from the given input
+ stream.
+
+ parse(string, flags_t) parses an amount from the given string.
+
+ parse(string, flags_t) also parses an amount from a string.
+ */
+ bool parse(std::istream& in,
+ const parse_flags_t& flags = PARSE_DEFAULT);
+ bool parse(const string& str,
+ const parse_flags_t& flags = PARSE_DEFAULT) {
+ std::istringstream stream(str);
+ bool result = parse(stream, flags);
+ return result;
+ }
+
+ static void parse_conversion(const string& larger_str,
+ const string& smaller_str);
+
+ /*@}*/
+
+ /** @name Printing
+ */
+ /*@{*/
+
+ /** An amount may be output to a stream using the `print' method. There is
+ also a global operator<< defined which simply calls print for an amount
+ on the given stream. There is one form of the print method, which takes
+ one required argument and two arguments with default values:
+
+ print(ostream, bool omit_commodity = false, bool full_precision = false)
+ prints an amounts to the given output stream, using its commodity's
+ default display characteristics. If `omit_commodity' is true, the
+ commodity will not be displayed, only the amount (although the
+ commodity's display precision is still used). If `full_precision' is
+ true, the full internal precision of the amount is displayed, regardless
+ of its commodity's display precision.
+ */
+ void print(std::ostream& out) const;
+
+ /*@}*/
+
+ /** @name Debugging
+ */
+ /*@{*/
+
+ /** There are two methods defined to help with debugging:
+
+ dump(ostream) dumps an amount to an output stream. There is
+ little different from print(), it simply surrounds the display
+ value with a marker, for example "AMOUNT($1.00)". This code is
+ used by other dumping code elsewhere in Ledger.
+
+ valid() returns true if an amount is valid. This ensures that if
+ an amount has a commodity, it has a valid value pointer, for
+ example, even if that pointer simply points to a zero value.
+ */
+ void dump(std::ostream& out) const {
+ out << "AMOUNT(";
+ print(out);
+ out << ")";
+ }
+
+ bool valid() const;
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */);
+#endif // HAVE_BOOST_SERIALIZATION
+
+ /*@}*/
+};
+
+inline amount_t amount_t::exact(const string& value) {
+ amount_t temp;
+ temp.parse(value, PARSE_NO_MIGRATE);
+ return temp;
+}
+
+inline string amount_t::to_string() const {
+ std::ostringstream bufstream;
+ print(bufstream);
+ return bufstream.str();
+}
+
+inline string amount_t::to_fullstring() const {
+ std::ostringstream bufstream;
+ unrounded().print(bufstream);
+ return bufstream.str();
+}
+
+inline string amount_t::quantity_string() const {
+ std::ostringstream bufstream;
+ number().print(bufstream);
+ return bufstream.str();
+}
+
+inline std::ostream& operator<<(std::ostream& out, const amount_t& amt) {
+ if (amount_t::stream_fullstrings)
+ amt.unrounded().print(out);
+ else
+ amt.print(out);
+ return out;
+}
+inline std::istream& operator>>(std::istream& in, amount_t& amt) {
+ amt.parse(in);
+ return in;
+}
+
+void to_xml(std::ostream& out, const amount_t& amt,
+ bool commodity_details = false);
+
+} // namespace ledger
+
+#endif // _AMOUNT_H
diff --git a/src/annotate.cc b/src/annotate.cc
new file mode 100644
index 00000000..146a7afd
--- /dev/null
+++ b/src/annotate.cc
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "amount.h"
+#include "commodity.h"
+#include "annotate.h"
+#include "pool.h"
+
+namespace ledger {
+
+void annotation_t::parse(std::istream& in)
+{
+ do {
+ istream_pos_type pos = in.tellg();
+
+ char buf[256];
+ char c = peek_next_nonws(in);
+ if (c == '{') {
+ if (price)
+ throw_(amount_error, _("Commodity specifies more than one price"));
+
+ in.get(c);
+ c = peek_next_nonws(in);
+ if (c == '=') {
+ in.get(c);
+ add_flags(ANNOTATION_PRICE_FIXATED);
+ }
+
+ READ_INTO(in, buf, 255, c, c != '}');
+ if (c == '}')
+ in.get(c);
+ else
+ throw_(amount_error, _("Commodity price lacks closing brace"));
+
+ amount_t temp;
+ temp.parse(buf, PARSE_NO_MIGRATE);
+
+ DEBUG("commodity.annotations", "Parsed annotation price: " << temp);
+
+ // Since this price will maintain its own precision, make sure
+ // it is at least as large as the base commodity, since the user
+ // may have only specified {$1} or something similar.
+
+ if (temp.has_commodity() &&
+ temp.precision() > temp.commodity().precision())
+ temp = temp.rounded(); // no need to retain individual precision
+
+ price = temp;
+ }
+ else if (c == '[') {
+ if (date)
+ throw_(amount_error, _("Commodity specifies more than one date"));
+
+ in.get(c);
+ READ_INTO(in, buf, 255, c, c != ']');
+ if (c == ']')
+ in.get(c);
+ else
+ throw_(amount_error, _("Commodity date lacks closing bracket"));
+
+ date = parse_date(buf);
+ }
+ else if (c == '(') {
+ if (tag)
+ throw_(amount_error, _("Commodity specifies more than one tag"));
+
+ in.get(c);
+ READ_INTO(in, buf, 255, c, c != ')');
+ if (c == ')')
+ in.get(c);
+ else
+ throw_(amount_error, _("Commodity tag lacks closing parenthesis"));
+
+ tag = buf;
+ }
+ else {
+ in.clear();
+ in.seekg(pos, std::ios::beg);
+ break;
+ }
+ } while (true);
+
+#if defined(DEBUG_ON)
+ if (SHOW_DEBUG("amounts.commodities") && *this) {
+ DEBUG("amounts.commodities",
+ "Parsed commodity annotations: " << std::endl << *this);
+ }
+#endif
+}
+
+void annotation_t::print(std::ostream& out, bool keep_base) const
+{
+ if (price)
+ out << " {"
+ << (has_flags(ANNOTATION_PRICE_FIXATED) ? "=" : "")
+ << (keep_base ? *price : price->unreduced()).rounded()
+ << '}';
+
+ if (date)
+ out << " [" << format_date(*date, FMT_WRITTEN) << ']';
+
+ if (tag)
+ out << " (" << *tag << ')';
+}
+
+bool keep_details_t::keep_all(const commodity_t& comm) const
+{
+ return (! comm.has_annotation() ||
+ (keep_price && keep_date && keep_tag && ! only_actuals));
+}
+
+bool keep_details_t::keep_any(const commodity_t& comm) const
+{
+ return comm.has_annotation() && (keep_price || keep_date || keep_tag);
+}
+
+bool annotated_commodity_t::operator==(const commodity_t& comm) const
+{
+ // If the base commodities don't match, the game's up.
+ if (base != comm.base)
+ return false;
+
+ assert(annotated);
+ if (! comm.annotated)
+ return false;
+
+ if (details != as_annotated_commodity(comm).details)
+ return false;
+
+ return true;
+}
+
+commodity_t&
+annotated_commodity_t::strip_annotations(const keep_details_t& what_to_keep)
+{
+ DEBUG("commodity.annotated.strip",
+ "Reducing commodity " << *this << std::endl
+ << " keep price " << what_to_keep.keep_price << " "
+ << " keep date " << what_to_keep.keep_date << " "
+ << " keep tag " << what_to_keep.keep_tag);
+
+ commodity_t * new_comm;
+
+ bool keep_price = (what_to_keep.keep_price &&
+ (! what_to_keep.only_actuals ||
+ ! details.has_flags(ANNOTATION_PRICE_CALCULATED)));
+ bool keep_date = (what_to_keep.keep_date &&
+ (! what_to_keep.only_actuals ||
+ ! details.has_flags(ANNOTATION_DATE_CALCULATED)));
+ bool keep_tag = (what_to_keep.keep_tag &&
+ (! what_to_keep.only_actuals ||
+ ! details.has_flags(ANNOTATION_TAG_CALCULATED)));
+
+ if ((keep_price && details.price) ||
+ (keep_date && details.date) ||
+ (keep_tag && details.tag))
+ {
+ new_comm = pool().find_or_create
+ (referent(), annotation_t(keep_price ? details.price : none,
+ keep_date ? details.date : none,
+ keep_tag ? details.tag : none));
+ } else {
+ new_comm = pool().find_or_create(base_symbol());
+ }
+
+ assert(new_comm);
+ return *new_comm;
+}
+
+void annotated_commodity_t::write_annotations(std::ostream& out) const
+{
+ details.print(out, pool().keep_base);
+}
+
+} // namespace ledger
diff --git a/src/annotate.h b/src/annotate.h
new file mode 100644
index 00000000..38ebaeae
--- /dev/null
+++ b/src/annotate.h
@@ -0,0 +1,265 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup math
+ */
+
+/**
+ * @file annotate.h
+ * @author John Wiegley
+ *
+ * @ingroup math
+ *
+ * @brief Types for annotating commodities
+ *
+ * Long.
+ */
+#ifndef _ANNOTATE_H
+#define _ANNOTATE_H
+
+namespace ledger {
+
+struct annotation_t : public supports_flags<>,
+ public equality_comparable<annotation_t>
+{
+#define ANNOTATION_PRICE_CALCULATED 0x01
+#define ANNOTATION_PRICE_FIXATED 0x02
+#define ANNOTATION_DATE_CALCULATED 0x04
+#define ANNOTATION_TAG_CALCULATED 0x08
+
+ optional<amount_t> price;
+ optional<date_t> date;
+ optional<string> tag;
+
+ explicit annotation_t(const optional<amount_t>& _price = none,
+ const optional<date_t>& _date = none,
+ const optional<string>& _tag = none)
+ : supports_flags<>(), price(_price), date(_date), tag(_tag) {
+ TRACE_CTOR(annotation_t, "const optional<amount_t>& + date_t + string");
+ }
+ annotation_t(const annotation_t& other)
+ : supports_flags<>(other.flags()),
+ price(other.price), date(other.date), tag(other.tag) {
+ TRACE_CTOR(annotation_t, "copy");
+ }
+ ~annotation_t() {
+ TRACE_DTOR(annotation_t);
+ }
+
+ operator bool() const {
+ return price || date || tag;
+ }
+
+ bool operator==(const annotation_t& rhs) const {
+ return (price == rhs.price &&
+ date == rhs.date &&
+ tag == rhs.tag);
+ }
+
+ void parse(std::istream& in);
+
+ void print(std::ostream& out, bool keep_base = false) const;
+
+ bool valid() const {
+ assert(*this);
+ return true;
+ }
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & boost::serialization::base_object<supports_flags<> >(*this);
+ ar & price;
+ ar & date;
+ ar & tag;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+inline void to_xml(std::ostream& out, const annotation_t& details)
+{
+ push_xml x(out, "annotation");
+
+ if (details.price)
+ {
+ push_xml y(out, "price");
+ to_xml(out, *details.price);
+ }
+
+ if (details.date)
+ {
+ push_xml y(out, "date");
+ to_xml(out, *details.date, false);
+ }
+
+ if (details.tag)
+ {
+ push_xml y(out, "tag");
+ out << y.guard(*details.tag);
+ }
+}
+
+struct keep_details_t
+{
+ bool keep_price;
+ bool keep_date;
+ bool keep_tag;
+ bool only_actuals;
+
+ explicit keep_details_t(bool _keep_price = false,
+ bool _keep_date = false,
+ bool _keep_tag = false,
+ bool _only_actuals = false)
+ : keep_price(_keep_price),
+ keep_date(_keep_date),
+ keep_tag(_keep_tag),
+ only_actuals(_only_actuals)
+ {
+ TRACE_CTOR(keep_details_t, "bool, bool, bool, bool");
+ }
+ keep_details_t(const keep_details_t& other)
+ : keep_price(other.keep_price), keep_date(other.keep_date),
+ keep_tag(other.keep_tag), only_actuals(other.only_actuals) {
+ TRACE_CTOR(keep_details_t, "copy");
+ }
+ ~keep_details_t() throw() {
+ TRACE_DTOR(keep_details_t);
+ }
+
+ bool keep_all() const {
+ return keep_price && keep_date && keep_tag && ! only_actuals;
+ }
+ bool keep_all(const commodity_t& comm) const;
+
+ bool keep_any() const {
+ return keep_price || keep_date || keep_tag;
+ }
+ bool keep_any(const commodity_t& comm) const;
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & keep_price;
+ ar & keep_date;
+ ar & keep_tag;
+ ar & only_actuals;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+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> >
+{
+protected:
+ friend class commodity_pool_t;
+
+ commodity_t * ptr;
+
+ 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, "commodity_t *, annotation_t");
+ annotated = true;
+ }
+
+public:
+ annotation_t details;
+
+ virtual ~annotated_commodity_t() {
+ TRACE_DTOR(annotated_commodity_t);
+ }
+
+ virtual bool operator==(const commodity_t& comm) const;
+ virtual bool operator==(const annotated_commodity_t& comm) const {
+ return *this == static_cast<const commodity_t&>(comm);
+ }
+
+ virtual commodity_t& referent() {
+ return *ptr;
+ }
+ virtual const commodity_t& referent() const {
+ return *ptr;
+ }
+
+ virtual commodity_t& strip_annotations(const keep_details_t& what_to_keep);
+ virtual void write_annotations(std::ostream& out) const;
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ explicit annotated_commodity_t() : ptr(NULL) {
+ TRACE_CTOR(annotated_commodity_t, "");
+ }
+
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & boost::serialization::base_object<commodity_t>(*this);
+ ar & ptr;
+ ar & details;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+inline annotated_commodity_t&
+as_annotated_commodity(commodity_t& commodity) {
+ return downcast<annotated_commodity_t>(commodity);
+}
+inline const annotated_commodity_t&
+as_annotated_commodity(const commodity_t& commodity) {
+ return downcast<const annotated_commodity_t>(commodity);
+}
+
+} // namespace ledger
+
+#endif // _ANNOTATE_H
diff --git a/src/archive.cc b/src/archive.cc
new file mode 100644
index 00000000..7306f8d3
--- /dev/null
+++ b/src/archive.cc
@@ -0,0 +1,295 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+
+#include "archive.h"
+#include "amount.h"
+#include "commodity.h"
+#include "pool.h"
+#include "scope.h"
+#include "account.h"
+#include "post.h"
+#include "xact.h"
+
+#define LEDGER_MAGIC 0x4c454447
+#define ARCHIVE_VERSION 0x03000006
+
+//BOOST_IS_ABSTRACT(ledger::scope_t)
+BOOST_CLASS_EXPORT(ledger::scope_t)
+BOOST_CLASS_EXPORT(ledger::child_scope_t)
+BOOST_CLASS_EXPORT(ledger::symbol_scope_t)
+BOOST_CLASS_EXPORT(ledger::call_scope_t)
+BOOST_CLASS_EXPORT(ledger::account_t)
+BOOST_CLASS_EXPORT(ledger::item_t)
+BOOST_CLASS_EXPORT(ledger::post_t)
+BOOST_CLASS_EXPORT(ledger::xact_base_t)
+BOOST_CLASS_EXPORT(ledger::xact_t)
+BOOST_CLASS_EXPORT(ledger::auto_xact_t)
+BOOST_CLASS_EXPORT(ledger::period_xact_t)
+
+template void ledger::journal_t::serialize(boost::archive::binary_oarchive&,
+ const unsigned int);
+template void ledger::journal_t::serialize(boost::archive::binary_iarchive&,
+ const unsigned int);
+namespace ledger {
+
+namespace {
+ bool read_header_bits(std::istream& in) {
+ uint32_t bytes;
+
+ assert(sizeof(uint32_t) == 4);
+ in.read(reinterpret_cast<char *>(&bytes), sizeof(uint32_t));
+ if (bytes != LEDGER_MAGIC) {
+ DEBUG("archive.journal", "Magic bytes not present");
+ return false;
+ }
+
+ in.read(reinterpret_cast<char *>(&bytes), sizeof(uint32_t));
+ if (bytes != ARCHIVE_VERSION) {
+ DEBUG("archive.journal", "Archive version mismatch");
+ return false;
+ }
+
+ return true;
+ }
+
+ void write_header_bits(std::ostream& out) {
+ uint32_t bytes;
+
+ assert(sizeof(uint32_t) == 4);
+ bytes = LEDGER_MAGIC;
+ out.write(reinterpret_cast<char *>(&bytes), sizeof(uint32_t));
+
+ bytes = ARCHIVE_VERSION;
+ out.write(reinterpret_cast<char *>(&bytes), sizeof(uint32_t));
+ }
+}
+
+bool archive_t::read_header()
+{
+ uintmax_t size = file_size(file);
+ if (size < 8)
+ return false;
+
+ // Open the stream, read the version number and the list of sources
+ ifstream stream(file, std::ios::binary);
+ if (! read_header_bits(stream))
+ return false;
+
+ boost::archive::binary_iarchive iarchive(stream);
+
+ DEBUG("archive.journal", "Reading header from archive");
+ iarchive >> *this;
+
+ DEBUG("archive.journal",
+ "Version number: " << std::hex << ARCHIVE_VERSION << std::dec);
+ DEBUG("archive.journal", "Number of sources: " << sources.size());
+
+#if defined(DEBUG_ON)
+ foreach (const journal_t::fileinfo_t& i, sources)
+ DEBUG("archive.journal", "Loaded source: " << *i.filename);
+#endif
+
+ return true;
+}
+
+bool archive_t::should_load(const std::list<path>& data_files)
+{
+ std::size_t found = 0;
+
+ DEBUG("archive.journal", "Should the archive be loaded?");
+
+ if (! exists(file)) {
+ DEBUG("archive.journal", "No, it does not exist");
+ return false;
+ }
+
+ if (! read_header()) {
+ DEBUG("archive.journal", "No, header failed to read");
+ return false;
+ }
+
+ if (data_files.empty()) {
+ DEBUG("archive.journal", "No, there were no data files!");
+ return false;
+ }
+
+ if (sources.empty()) {
+ DEBUG("archive.journal", "No, there were no sources!");
+ return false;
+ }
+
+ if (data_files.size() != sources.size()) {
+ DEBUG("archive.journal", "No, number of sources doesn't match: "
+ << data_files.size() << " != " << sources.size());
+ return false;
+ }
+
+ foreach (const path& p, data_files) {
+ DEBUG("archive.journal", "Scanning for data file: " << p);
+
+ if (! exists(p)) {
+ DEBUG("archive.journal", "No, an input source no longer exists: " << p);
+ return false;
+ }
+
+ foreach (const journal_t::fileinfo_t& i, sources) {
+ assert(! i.from_stream);
+ assert(i.filename);
+
+ DEBUG("archive.journal", "Comparing against source file: " << *i.filename);
+
+ if (*i.filename == p) {
+ if (! exists(*i.filename)) {
+ DEBUG("archive.journal",
+ "No, a referent source no longer exists: " << *i.filename);
+ return false;
+ }
+
+ if (i.modtime != posix_time::from_time_t(last_write_time(p))) {
+ DEBUG("archive.journal", "No, a source's modtime has changed: " << p);
+ return false;
+ }
+
+ if (i.size != file_size(p)) {
+ DEBUG("archive.journal", "No, a source's size has changed: " << p);
+ return false;
+ }
+
+ found++;
+ }
+ }
+ }
+
+ if (found != data_files.size()) {
+ DEBUG("archive.journal", "No, not every source's name matched");
+ return false;
+ }
+
+ DEBUG("archive.journal", "Yes, it should be loaded!");
+ return true;
+}
+
+bool archive_t::should_save(journal_t& journal)
+{
+ std::list<path> data_files;
+
+ DEBUG("archive.journal", "Should the archive be saved?");
+
+ if (journal.was_loaded) {
+ DEBUG("archive.journal", "No, it's one we loaded before");
+ return false;
+ }
+
+ if (journal.sources.empty()) {
+ DEBUG("archive.journal", "No, there were no sources!");
+ return false;
+ }
+
+ foreach (const journal_t::fileinfo_t& i, journal.sources) {
+ if (i.from_stream) {
+ DEBUG("archive.journal", "No, one source was from a stream");
+ return false;
+ }
+
+ if (! exists(*i.filename)) {
+ DEBUG("archive.journal",
+ "No, a source no longer exists: " << *i.filename);
+ return false;
+ }
+
+ data_files.push_back(*i.filename);
+ }
+
+ if (should_load(data_files)) {
+ DEBUG("archive.journal", "No, because it's still loadable");
+ return false;
+ }
+
+ DEBUG("archive.journal", "Yes, it should be saved!");
+ return true;
+}
+
+void archive_t::save(journal_t& journal)
+{
+ INFO_START(archive, "Saved journal file cache");
+
+ ofstream stream(file, std::ios::binary);
+
+ write_header_bits(stream);
+ sources = journal.sources;
+
+#if defined(DEBUG_ON)
+ foreach (const journal_t::fileinfo_t& i, sources)
+ DEBUG("archive.journal", "Saving source: " << *i.filename);
+#endif
+
+ boost::archive::binary_oarchive oa(stream);
+
+ DEBUG("archive.journal", "Creating archive with version "
+ << std::hex << ARCHIVE_VERSION << std::dec);
+ oa << *this;
+
+ DEBUG("archive.journal",
+ "Archiving journal with " << sources.size() << " sources");
+ oa << journal;
+
+ INFO_FINISH(archive);
+}
+
+bool archive_t::load(journal_t& journal)
+{
+ INFO_START(archive, "Read cached journal file");
+
+ ifstream stream(file, std::ios::binary);
+ if (! read_header_bits(stream))
+ return false;
+
+ boost::archive::binary_iarchive iarchive(stream);
+
+ // Skip past the archive header, it was already read in before
+ archive_t temp;
+ iarchive >> temp;
+
+ iarchive >> journal;
+ journal.was_loaded = true;
+
+ INFO_FINISH(archive);
+
+ return true;
+}
+
+} // namespace ledger
+
+#endif // HAVE_BOOST_SERIALIZATION
diff --git a/src/archive.h b/src/archive.h
new file mode 100644
index 00000000..03fc970a
--- /dev/null
+++ b/src/archive.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @defgroup report Reporting
+ */
+
+/**
+ * @file archive.h
+ * @author John Wiegley
+ *
+ * @ingroup report
+ */
+#ifndef _ARCHIVE_H
+#define _ARCHIVE_H
+
+#include "journal.h"
+
+namespace ledger {
+
+class archive_t
+{
+ path file;
+
+ std::list<journal_t::fileinfo_t> sources;
+
+public:
+ archive_t() {
+ TRACE_CTOR(archive_t, "");
+ }
+ archive_t(const path& _file) : file(_file) {
+ TRACE_CTOR(archive_t, "const path&");
+ }
+ archive_t(const archive_t& ar) : file(ar.file) {
+ TRACE_CTOR(archive_t, "copy");
+ }
+ ~archive_t() {
+ TRACE_DTOR(archive_t);
+ }
+
+ bool read_header();
+
+ bool should_load(const std::list<path>& data_files);
+ bool should_save(journal_t& journal);
+
+ void save(journal_t& journal);
+ bool load(journal_t& journal);
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & sources;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+} // namespace ledger
+
+#endif // _ARCHIVE_H
diff --git a/src/balance.cc b/src/balance.cc
new file mode 100644
index 00000000..4ff51ffc
--- /dev/null
+++ b/src/balance.cc
@@ -0,0 +1,310 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "balance.h"
+#include "commodity.h"
+#include "annotate.h"
+#include "pool.h"
+#include "unistring.h" // for justify()
+
+namespace ledger {
+
+balance_t::balance_t(const double val)
+{
+ TRACE_CTOR(balance_t, "const double");
+ amounts.insert
+ (amounts_map::value_type(commodity_pool_t::current_pool->null_commodity, val));
+}
+
+balance_t::balance_t(const unsigned long val)
+{
+ TRACE_CTOR(balance_t, "const unsigned long");
+ amounts.insert
+ (amounts_map::value_type(commodity_pool_t::current_pool->null_commodity, val));
+}
+
+balance_t::balance_t(const long val)
+{
+ TRACE_CTOR(balance_t, "const long");
+ amounts.insert
+ (amounts_map::value_type(commodity_pool_t::current_pool->null_commodity, val));
+}
+
+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.negated()));
+ }
+ 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 bool primary_only,
+ const optional<datetime_t>& moment,
+ const optional<commodity_t&>& in_terms_of) const
+{
+ balance_t temp;
+ bool resolved = false;
+
+ foreach (const amounts_map::value_type& pair, amounts) {
+ if (optional<amount_t> val = pair.second.value(primary_only, moment,
+ in_terms_of)) {
+ temp += *val;
+ resolved = true;
+ } else {
+ temp += pair.second;
+ }
+ }
+ return resolved ? temp : optional<balance_t>();
+}
+
+balance_t balance_t::price() const
+{
+ balance_t temp;
+
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.price();
+
+ return temp;
+}
+
+optional<amount_t>
+balance_t::commodity_amount(const optional<const commodity_t&>& commodity) const
+{
+ if (! commodity) {
+ if (amounts.size() == 1) {
+ return amounts.begin()->second;
+ }
+ else if (amounts.size() > 1) {
+ // Try stripping annotations before giving an error.
+ balance_t temp(strip_annotations(keep_details_t()));
+ if (temp.amounts.size() == 1)
+ return temp.commodity_amount(commodity);
+
+ throw_(amount_error,
+ _("Requested amount of a balance with multiple commodities: %1")
+ << temp);
+ }
+ }
+ else if (amounts.size() > 0) {
+ amounts_map::const_iterator i =
+ amounts.find(const_cast<commodity_t *>(&*commodity));
+ if (i != amounts.end())
+ return i->second;
+ }
+ return none;
+}
+
+balance_t
+balance_t::strip_annotations(const keep_details_t& what_to_keep) const
+{
+ balance_t temp;
+
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.strip_annotations(what_to_keep);
+
+ return temp;
+}
+
+void balance_t::print(std::ostream& out,
+ const int first_width,
+ const int latter_width,
+ const bool right_justify,
+ const bool colorize) 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;
+ }
+
+ std::ostringstream buf;
+ buf << *amount;
+ justify(out, buf.str(), width, right_justify,
+ colorize && amount->sign() < 0);
+ }
+
+ if (first) {
+ out.width(first_width);
+ if (right_justify)
+ out << std::right;
+ else
+ out << std::left;
+ out << 0;
+ }
+}
+
+void to_xml(std::ostream& out, const balance_t& bal)
+{
+ push_xml x(out, "balance");
+
+ foreach (const balance_t::amounts_map::value_type& pair, bal.amounts)
+ to_xml(out, pair.second);
+}
+
+} // namespace ledger
diff --git a/src/balance.h b/src/balance.h
new file mode 100644
index 00000000..826de134
--- /dev/null
+++ b/src/balance.h
@@ -0,0 +1,602 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup math
+ */
+
+/**
+ * @file balance.h
+ * @author John Wiegley
+ *
+ * @ingroup math
+ *
+ * @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<commodity_t *, amount_t> amounts_map;
+
+ amounts_map amounts;
+
+ /**
+ * 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));
+ }
+ balance_t(const double val);
+ balance_t(const unsigned long val);
+ balance_t(const long 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.
+ */
+ ~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 == amount_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 double val) {
+ return *this += amount_t(val);
+ }
+ balance_t& operator+=(const unsigned long val) {
+ return *this += amount_t(val);
+ }
+ balance_t& operator+=(const long val) {
+ return *this += amount_t(val);
+ }
+
+ balance_t& operator-=(const balance_t& bal);
+ balance_t& operator-=(const amount_t& amt);
+ balance_t& operator-=(const double val) {
+ return *this -= amount_t(val);
+ }
+ balance_t& operator-=(const unsigned long val) {
+ return *this -= amount_t(val);
+ }
+ balance_t& operator-=(const long val) {
+ return *this -= amount_t(val);
+ }
+
+ balance_t& operator*=(const amount_t& amt);
+ balance_t& operator*=(const double val) {
+ return *this *= amount_t(val);
+ }
+ balance_t& operator*=(const unsigned long val) {
+ return *this *= amount_t(val);
+ }
+ balance_t& operator*=(const long val) {
+ return *this *= amount_t(val);
+ }
+
+ balance_t& operator/=(const amount_t& amt);
+ balance_t& operator/=(const double val) {
+ return *this /= amount_t(val);
+ }
+ 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 negated() const {
+ balance_t temp(*this);
+ temp.in_place_negate();
+ return temp;
+ }
+ void in_place_negate() {
+ foreach (amounts_map::value_type& pair, amounts)
+ pair.second.in_place_negate();
+ }
+ balance_t operator-() const {
+ return negated();
+ }
+
+ balance_t abs() const {
+ balance_t temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.abs();
+ return temp;
+ }
+
+ balance_t rounded() const {
+ balance_t temp(*this);
+ temp.in_place_round();
+ return temp;
+ }
+ void in_place_round() {
+ balance_t temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.rounded();
+ *this = temp;
+ }
+
+ balance_t truncated() const {
+ balance_t temp(*this);
+ temp.in_place_truncate();
+ return temp;
+ }
+ void in_place_truncate() {
+ balance_t temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.truncated();
+ *this = temp;
+ }
+
+ balance_t floored() const {
+ balance_t temp(*this);
+ temp.in_place_floor();
+ return temp;
+ }
+ void in_place_floor() {
+ balance_t temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.floored();
+ *this = temp;
+ }
+
+ balance_t unrounded() const {
+ balance_t temp(*this);
+ temp.in_place_unround();
+ return temp;
+ }
+ void in_place_unround() {
+ balance_t temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.unrounded();
+ *this = temp;
+ }
+
+ balance_t reduced() const {
+ balance_t temp(*this);
+ temp.in_place_reduce();
+ return temp;
+ }
+ void 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.reduced();
+ *this = temp;
+ }
+
+ balance_t unreduced() const {
+ balance_t temp(*this);
+ temp.in_place_unreduce();
+ return temp;
+ }
+ void 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.unreduced();
+ *this = temp;
+ }
+
+ optional<balance_t>
+ value(const bool primary_only = false,
+ const optional<datetime_t>& moment = none,
+ const optional<commodity_t&>& in_terms_of = none) const;
+
+ balance_t price() const;
+
+ /**
+ * Truth tests. An balance may be truth test in two ways:
+ *
+ * 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 {
+ return is_nonzero();
+ }
+
+ bool is_nonzero() const {
+ if (is_empty())
+ return false;
+
+ 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;
+ }
+ bool single_amount() const {
+ return amounts.size() == 1;
+ }
+
+ /**
+ * Conversion methods. A balance can be converted to an amount, but
+ * only if contains a single component amount.
+ */
+ operator string() const {
+ return to_string();
+ }
+ string to_string() const {
+ std::ostringstream buf;
+ print(buf);
+ return buf.str();
+ }
+
+ 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"));
+ return amount_t();
+ }
+
+ /**
+ * 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;
+
+ balance_t number() const {
+ balance_t temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.number();
+ return temp;
+ }
+
+ /**
+ * 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 keep_details_t& what_to_keep) 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 = -1,
+ const int latter_width = -1,
+ const bool right_justify = false,
+ const bool colorize = false) 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 << ")";
+ }
+
+ bool valid() const {
+ foreach (const amounts_map::value_type& pair, amounts)
+ if (! pair.second.valid()) {
+ DEBUG("ledger.validate", "balance_t: ! pair.second.valid()");
+ return false;
+ }
+ return true;
+ }
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & amounts;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+inline std::ostream& operator<<(std::ostream& out, const balance_t& bal) {
+ bal.print(out, 12);
+ return out;
+}
+
+void to_xml(std::ostream& out, const balance_t& amt);
+
+} // namespace ledger
+
+#endif // _BALANCE_H
diff --git a/src/chain.cc b/src/chain.cc
new file mode 100644
index 00000000..113a71d8
--- /dev/null
+++ b/src/chain.cc
@@ -0,0 +1,241 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "chain.h"
+#include "predicate.h"
+#include "filters.h"
+#include "report.h"
+#include "session.h"
+
+namespace ledger {
+
+post_handler_ptr chain_post_handlers(report_t& report,
+ post_handler_ptr base_handler,
+ bool for_accounts_report)
+{
+ post_handler_ptr handler(base_handler);
+ predicate_t display_predicate;
+ predicate_t only_predicate;
+
+ assert(report.HANDLED(amount_));
+ expr_t& expr(report.HANDLER(amount_).expr);
+ expr.set_context(&report);
+
+ if (! for_accounts_report) {
+ // Make sure only forecast postings which match are allowed through
+ if (report.HANDLED(forecast_while_)) {
+ handler.reset(new filter_posts
+ (handler, predicate_t(report.HANDLER(forecast_while_).str(),
+ report.what_to_keep()),
+ report));
+ }
+
+ // truncate_xacts cuts off a certain number of _xacts_ from being
+ // displayed. It does not affect calculation.
+ if (report.HANDLED(head_) || report.HANDLED(tail_))
+ handler.reset
+ (new truncate_xacts(handler,
+ report.HANDLED(head_) ?
+ report.HANDLER(head_).value.to_int() : 0,
+ report.HANDLED(tail_) ?
+ report.HANDLER(tail_).value.to_int() : 0));
+
+ // filter_posts will only pass through posts matching the
+ // `display_predicate'.
+ if (report.HANDLED(display_)) {
+ display_predicate = predicate_t(report.HANDLER(display_).str(),
+ report.what_to_keep());
+ handler.reset(new filter_posts(handler, display_predicate, report));
+ }
+ }
+
+ // changed_value_posts adds virtual posts to the list to account for changes
+ // in market value of commodities, which otherwise would affect the running
+ // total unpredictably.
+ if (report.HANDLED(revalued) && (! for_accounts_report ||
+ report.HANDLED(unrealized)))
+ handler.reset(new changed_value_posts(handler, report,
+ for_accounts_report,
+ report.HANDLED(unrealized)));
+
+ // calc_posts computes the running total. When this appears will determine,
+ // for example, whether filtered posts are included or excluded from the
+ // running total.
+ handler.reset(new calc_posts(handler, expr, (! for_accounts_report ||
+ (report.HANDLED(revalued) &&
+ report.HANDLED(unrealized)))));
+
+ // filter_posts will only pass through posts matching the
+ // `secondary_predicate'.
+ if (report.HANDLED(only_)) {
+ only_predicate = predicate_t(report.HANDLER(only_).str(),
+ report.what_to_keep());
+ handler.reset(new filter_posts(handler, only_predicate, report));
+ }
+
+ if (! for_accounts_report) {
+ // sort_posts will sort all the posts it sees, based on the `sort_order'
+ // value expression.
+ if (report.HANDLED(sort_)) {
+ if (report.HANDLED(sort_xacts_))
+ handler.reset(new sort_xacts(handler, report.HANDLER(sort_).str()));
+ else
+ handler.reset(new sort_posts(handler, report.HANDLER(sort_).str()));
+ }
+ else if (! report.HANDLED(period_) &&
+ ! report.HANDLED(unsorted)) {
+ handler.reset(new sort_posts(handler, "date"));
+ }
+
+ // collapse_posts causes xacts with multiple posts to appear as xacts
+ // with a subtotaled post for each commodity used.
+ if (report.HANDLED(collapse))
+ handler.reset(new collapse_posts(handler, expr,
+ display_predicate, only_predicate,
+ report.HANDLED(collapse_if_zero)));
+
+ // subtotal_posts combines all the posts it receives into one subtotal
+ // xact, which has one post for each commodity in each account.
+ //
+ // period_posts is like subtotal_posts, but it subtotals according to time
+ // periods rather than totalling everything.
+ //
+ // dow_posts is like period_posts, except that it reports all the posts
+ // that fall on each subsequent day of the week.
+ if (report.HANDLED(equity))
+ handler.reset(new posts_as_equity(handler, expr));
+ else if (report.HANDLED(subtotal))
+ handler.reset(new subtotal_posts(handler, expr));
+ }
+
+ if (report.HANDLED(dow))
+ handler.reset(new dow_posts(handler, expr));
+ else if (report.HANDLED(by_payee))
+ handler.reset(new by_payee_posts(handler, expr));
+
+ // interval_posts groups posts together based on a time period, such as
+ // weekly or monthly.
+ if (report.HANDLED(period_)) {
+ handler.reset(new interval_posts(handler, expr,
+ report.HANDLER(period_).str(),
+ report.HANDLED(exact),
+ report.HANDLED(empty)));
+ handler.reset(new sort_posts(handler, "date"));
+ }
+
+ if (report.HANDLED(date_))
+ handler.reset(new transfer_details(handler, transfer_details::SET_DATE,
+ report.session.journal->master,
+ report.HANDLER(date_).str(),
+ report));
+ if (report.HANDLED(account_))
+ handler.reset(new transfer_details(handler, transfer_details::SET_ACCOUNT,
+ report.session.journal->master,
+ report.HANDLER(account_).str(),
+ report));
+ if (report.HANDLED(payee_))
+ handler.reset(new transfer_details(handler, transfer_details::SET_PAYEE,
+ report.session.journal->master,
+ report.HANDLER(payee_).str(),
+ report));
+
+ // related_posts will pass along all posts related to the post received. If
+ // the `related_all' handler is on, then all the xact's posts are passed;
+ // meaning that if one post of an xact is to be printed, all the post for
+ // that xact will be printed.
+ if (report.HANDLED(related))
+ handler.reset(new related_posts(handler, report.HANDLED(related_all)));
+
+ // anonymize_posts removes all meaningful information from xact payee's and
+ // account names, for the sake of creating useful bug reports.
+ if (report.HANDLED(anon))
+ handler.reset(new anonymize_posts(handler));
+
+ // This filter_posts will only pass through posts matching the `predicate'.
+ if (report.HANDLED(limit_)) {
+ DEBUG("report.predicate",
+ "Report predicate expression = " << report.HANDLER(limit_).str());
+ handler.reset(new filter_posts
+ (handler, predicate_t(report.HANDLER(limit_).str(),
+ report.what_to_keep()),
+ report));
+ }
+
+ // budget_posts takes a set of posts from a data file and uses them to
+ // generate "budget posts" which balance against the reported posts.
+ //
+ // forecast_posts is a lot like budget_posts, except that it adds xacts
+ // only for the future, and does not balance them against anything but the
+ // future balance.
+
+ if (report.budget_flags != BUDGET_NO_BUDGET) {
+ budget_posts * budget_handler = new budget_posts(handler,
+ report.budget_flags);
+ budget_handler->add_period_xacts(report.session.journal->period_xacts);
+ handler.reset(budget_handler);
+
+ // Apply this before the budget handler, so that only matching posts are
+ // calculated toward the budget. The use of filter_posts above will
+ // further clean the results so that no automated posts that don't match
+ // the filter get reported.
+ if (report.HANDLED(limit_))
+ handler.reset(new filter_posts
+ (handler, predicate_t(report.HANDLER(limit_).str(),
+ report.what_to_keep()),
+ report));
+ }
+ else if (report.HANDLED(forecast_while_)) {
+ forecast_posts * forecast_handler
+ = new forecast_posts(handler,
+ predicate_t(report.HANDLER(forecast_while_).str(),
+ report.what_to_keep()),
+ report,
+ report.HANDLED(forecast_years_) ?
+ static_cast<std::size_t>
+ (report.HANDLER(forecast_years_).value.to_long()) :
+ 5UL);
+ forecast_handler->add_period_xacts(report.session.journal->period_xacts);
+ handler.reset(forecast_handler);
+
+ // See above, under budget_posts.
+ if (report.HANDLED(limit_))
+ handler.reset(new filter_posts
+ (handler, predicate_t(report.HANDLER(limit_).str(),
+ report.what_to_keep()),
+ report));
+ }
+
+ return handler;
+}
+
+} // namespace ledger
diff --git a/src/chain.h b/src/chain.h
new file mode 100644
index 00000000..a2b75b3a
--- /dev/null
+++ b/src/chain.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup report
+ */
+
+/**
+ * @file chain.h
+ * @author John Wiegley
+ *
+ * @ingroup report
+ */
+#ifndef _CHAIN_H
+#define _CHAIN_H
+
+#include "utils.h"
+
+namespace ledger {
+
+class post_t;
+class account_t;
+
+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()) {
+ check_for_signal();
+ (*handler.get())(item);
+ }
+ }
+};
+
+typedef shared_ptr<item_handler<post_t> > post_handler_ptr;
+typedef shared_ptr<item_handler<account_t> > acct_handler_ptr;
+
+class report_t;
+post_handler_ptr
+chain_post_handlers(report_t& report,
+ post_handler_ptr base_handler,
+ bool for_accounts_report = false);
+
+} // namespace ledger
+
+#endif // _CHAIN_H
diff --git a/src/commodity.cc b/src/commodity.cc
new file mode 100644
index 00000000..79ed77fe
--- /dev/null
+++ b/src/commodity.cc
@@ -0,0 +1,705 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "amount.h"
+#include "commodity.h"
+#include "annotate.h"
+#include "pool.h"
+
+namespace ledger {
+
+bool commodity_t::european_by_default = false;
+
+void commodity_t::history_t::add_price(commodity_t& source,
+ const datetime_t& date,
+ const amount_t& price,
+ const bool reflexive)
+{
+ DEBUG("commodity.prices.add", "add_price to " << source
+ << (reflexive ? " (secondary)" : " (primary)")
+ << " : " << date << ", " << price);
+
+ history_map::iterator i = prices.find(date);
+ if (i != prices.end()) {
+ (*i).second = price;
+ } else {
+ std::pair<history_map::iterator, bool> result
+ = prices.insert(history_map::value_type(date, price));
+ assert(result.second);
+ }
+
+ if (reflexive) {
+ amount_t inverse = price.inverted();
+ inverse.set_commodity(const_cast<commodity_t&>(source));
+ price.commodity().add_price(date, inverse, false);
+ } else {
+ DEBUG("commodity.prices.add",
+ "marking commodity " << source.symbol() << " as primary");
+ source.add_flags(COMMODITY_PRIMARY);
+ }
+}
+
+bool commodity_t::history_t::remove_price(const datetime_t& date)
+{
+ DEBUG("commodity.prices.add", "remove_price: " << date);
+
+ history_map::size_type n = prices.erase(date);
+ if (n > 0)
+ return true;
+ return false;
+}
+
+void commodity_t::varied_history_t::
+ add_price(commodity_t& source,
+ const datetime_t& date,
+ const amount_t& price,
+ const bool reflexive)
+{
+ optional<history_t&> hist = history(price.commodity());
+ if (! hist) {
+ std::pair<history_by_commodity_map::iterator, bool> result
+ = histories.insert(history_by_commodity_map::value_type
+ (&price.commodity(), history_t()));
+ assert(result.second);
+
+ hist = (*result.first).second;
+ }
+ assert(hist);
+
+ hist->add_price(source, date, price, reflexive);
+}
+
+bool commodity_t::varied_history_t::remove_price(const datetime_t& date,
+ commodity_t& comm)
+{
+ DEBUG("commodity.prices.add", "varied_remove_price: " << date << ", " << comm);
+
+ if (optional<history_t&> hist = history(comm))
+ return hist->remove_price(date);
+ return false;
+}
+
+optional<price_point_t>
+commodity_t::history_t::find_price(const optional<datetime_t>& moment,
+ const optional<datetime_t>& oldest
+#if defined(DEBUG_ON)
+ , const int indent
+#endif
+ ) const
+{
+ price_point_t point;
+ bool found = false;
+
+#define DEBUG_INDENT(cat, indent) \
+ do { \
+ if (SHOW_DEBUG(cat)) \
+ for (int i = 0; i < indent; i++) \
+ ledger::_log_buffer << " "; \
+ } while (false)
+
+#if defined(DEBUG_ON)
+ DEBUG_INDENT("commodity.prices.find", indent);
+ if (moment)
+ DEBUG("commodity.prices.find", "find price nearest before or on: " << *moment);
+ else
+ DEBUG("commodity.prices.find", "find any price");
+
+ if (oldest) {
+ DEBUG_INDENT("commodity.prices.find", indent);
+ DEBUG("commodity.prices.find", " but no older than: " << *oldest);
+ }
+#endif
+
+ if (prices.size() == 0) {
+#if defined(DEBUG_ON)
+ DEBUG_INDENT("commodity.prices.find", indent);
+ DEBUG("commodity.prices.find", " there are no prices in this history");
+#endif
+ return none;
+ }
+
+ if (! moment) {
+ history_map::const_reverse_iterator r = prices.rbegin();
+ point.when = (*r).first;
+ point.price = (*r).second;
+ found = true;
+#if defined(DEBUG_ON)
+ DEBUG_INDENT("commodity.prices.find", indent);
+ DEBUG("commodity.prices.find", " using most recent price");
+#endif
+ } else {
+ history_map::const_iterator i = prices.lower_bound(*moment);
+ if (i == prices.end()) {
+ history_map::const_reverse_iterator r = prices.rbegin();
+ point.when = (*r).first;
+ point.price = (*r).second;
+ found = true;
+#if defined(DEBUG_ON)
+ DEBUG_INDENT("commodity.prices.find", indent);
+ DEBUG("commodity.prices.find", " using last price");
+#endif
+ } else {
+ point.when = (*i).first;
+ if (*moment < point.when) {
+ if (i != prices.begin()) {
+ --i;
+ point.when = (*i).first;
+ point.price = (*i).second;
+ found = true;
+ }
+ } else {
+ point.price = (*i).second;
+ found = true;
+ }
+#if defined(DEBUG_ON)
+ DEBUG_INDENT("commodity.prices.find", indent);
+ DEBUG("commodity.prices.find", " using found price");
+#endif
+ }
+ }
+
+ if (! found) {
+#if defined(DEBUG_ON)
+ DEBUG_INDENT("commodity.prices.find", indent);
+ DEBUG("commodity.prices.find", " could not find a price");
+#endif
+ return none;
+ }
+ else if (moment && point.when > *moment) {
+#if defined(DEBUG_ON)
+ DEBUG_INDENT("commodity.prices.find", indent);
+ DEBUG("commodity.prices.find", " price is too young ");
+#endif
+ return none;
+ }
+ else if (oldest && point.when < *oldest) {
+#if defined(DEBUG_ON)
+ DEBUG_INDENT("commodity.prices.find", indent);
+ DEBUG("commodity.prices.find", " price is too old ");
+#endif
+ return none;
+ }
+ else {
+#if defined(DEBUG_ON)
+ DEBUG_INDENT("commodity.prices.find", indent);
+ DEBUG("commodity.prices.find",
+ " returning price: " << point.when << ", " << point.price);
+#endif
+ return point;
+ }
+}
+
+optional<price_point_t>
+commodity_t::varied_history_t::find_price(const commodity_t& source,
+ const optional<commodity_t&>& commodity,
+ const optional<datetime_t>& moment,
+ const optional<datetime_t>& oldest
+#if defined(DEBUG_ON)
+ , const int indent
+#endif
+ ) const
+{
+ optional<price_point_t> point;
+ optional<datetime_t> limit = oldest;
+
+ assert(! commodity || source != *commodity);
+
+#if defined(DEBUG_ON)
+ DEBUG_INDENT("commodity.prices.find", indent);
+ DEBUG("commodity.prices.find", "varied_find_price for: " << source);
+
+ DEBUG_INDENT("commodity.prices.find", indent);
+ if (commodity)
+ DEBUG("commodity.prices.find", " looking for: commodity '" << *commodity << "'");
+ else
+ DEBUG("commodity.prices.find", " looking for: any commodity");
+
+ if (moment) {
+ DEBUG_INDENT("commodity.prices.find", indent);
+ DEBUG("commodity.prices.find", " time index: " << *moment);
+ }
+
+ if (oldest) {
+ DEBUG_INDENT("commodity.prices.find", indent);
+ DEBUG("commodity.prices.find", " only consider prices younger than: " << *oldest);
+ }
+#endif
+
+ // Either we couldn't find a history for the target commodity, or we
+ // couldn't find a price. In either case, search all histories known
+ // to this commodity for a price which we can calculate in terms of
+ // the goal commodity.
+ price_point_t best;
+ bool found = false;
+
+ foreach (history_by_commodity_map::value_type hist, histories) {
+ commodity_t& comm(*hist.first);
+ if (comm == source)
+ continue;
+
+#if defined(DEBUG_ON)
+ DEBUG_INDENT("commodity.prices.find", indent + 1);
+ DEBUG("commodity.prices.find",
+ " searching for price via commodity '" << comm << "'");
+#endif
+
+ point = hist.second.find_price(moment, limit
+#if defined(DEBUG_ON)
+ , indent + 2
+#endif
+ );
+ assert(! point || point->price.commodity() == comm);
+
+ if (point) {
+ optional<price_point_t> xlat;
+
+ if (commodity && comm != *commodity) {
+#if defined(DEBUG_ON)
+ DEBUG_INDENT("commodity.prices.find", indent + 1);
+ DEBUG("commodity.prices.find", " looking for translation price");
+#endif
+
+ xlat = comm.find_price(commodity, moment, limit
+#if defined(DEBUG_ON)
+ , indent + 2
+#endif
+ );
+ if (xlat) {
+#if defined(DEBUG_ON)
+ DEBUG_INDENT("commodity.prices.find", indent + 1);
+ DEBUG("commodity.prices.find", " found translated price "
+ << xlat->price << " from " << xlat->when);
+#endif
+
+ point->price = xlat->price * point->price;
+ if (xlat->when < point->when) {
+ point->when = xlat->when;
+#if defined(DEBUG_ON)
+ DEBUG_INDENT("commodity.prices.find", indent + 1);
+ DEBUG("commodity.prices.find",
+ " adjusting date of result back to " << point->when);
+#endif
+ }
+ } else {
+#if defined(DEBUG_ON)
+ DEBUG_INDENT("commodity.prices.find", indent + 1);
+ DEBUG("commodity.prices.find", " saw no translated price there");
+#endif
+ continue;
+ }
+ }
+
+ assert(! commodity || point->price.commodity() == *commodity);
+#if defined(DEBUG_ON)
+ DEBUG_INDENT("commodity.prices.find", indent + 1);
+ DEBUG("commodity.prices.find",
+ " saw a price there: " << point->price << " from " << point->when);
+#endif
+ if (! limit || point->when > *limit) {
+ limit = point->when;
+ best = *point;
+ found = true;
+ }
+ } else {
+#if defined(DEBUG_ON)
+ DEBUG_INDENT("commodity.prices.find", indent + 1);
+ DEBUG("commodity.prices.find", " saw no price there");
+#endif
+ }
+ }
+
+ if (found) {
+#if defined(DEBUG_ON)
+ DEBUG_INDENT("commodity.prices.find", indent);
+ DEBUG("commodity.prices.find",
+ " found price " << best.price << " from " << best.when);
+ DEBUG("commodity.download",
+ "found price " << best.price << " from " << best.when);
+#endif
+ return best;
+ }
+ return none;
+}
+
+optional<commodity_t::history_t&>
+commodity_t::varied_history_t::history(const optional<commodity_t&>& commodity)
+{
+ commodity_t * comm = NULL;
+ if (! commodity) {
+ if (histories.size() > 1)
+ return none;
+ comm = (*histories.begin()).first;
+ } else {
+ comm = &(*commodity);
+ }
+
+ history_by_commodity_map::iterator i = histories.find(comm);
+ if (i != histories.end())
+ return (*i).second;
+
+ return none;
+}
+
+optional<price_point_t>
+commodity_t::check_for_updated_price(const optional<price_point_t>& point,
+ const optional<datetime_t>& moment,
+ const optional<commodity_t&>& in_terms_of)
+{
+ if (pool().get_quotes && ! has_flags(COMMODITY_NOMARKET)) {
+ bool exceeds_leeway = true;
+
+ if (point) {
+ time_duration_t::sec_type seconds_diff;
+ if (moment) {
+ seconds_diff = (*moment - point->when).total_seconds();
+ DEBUG("commodity.download", "moment = " << *moment);
+ DEBUG("commodity.download", "slip.moment = " << seconds_diff);
+ } else {
+ seconds_diff = (TRUE_CURRENT_TIME() - point->when).total_seconds();
+ DEBUG("commodity.download", "slip.now = " << seconds_diff);
+ }
+
+ DEBUG("commodity.download", "leeway = " << pool().quote_leeway);
+ if (seconds_diff < pool().quote_leeway)
+ exceeds_leeway = false;
+ }
+
+ if (exceeds_leeway) {
+ DEBUG("commodity.download",
+ "attempting to download a more current quote...");
+ if (optional<price_point_t> quote =
+ pool().get_commodity_quote(*this, in_terms_of)) {
+ if (! in_terms_of ||
+ (quote->price.has_commodity() &&
+ quote->price.commodity() == *in_terms_of))
+ return quote;
+ }
+ }
+ }
+ return point;
+}
+
+commodity_t::operator bool() const
+{
+ return this != pool().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;
+}
+
+namespace {
+ bool is_reserved_token(const char * buf)
+ {
+ switch (buf[0]) {
+ case 'a':
+ return std::strcmp(buf, "and") == 0;
+ case 'd':
+ return std::strcmp(buf, "div") == 0;
+ case 'e':
+ return std::strcmp(buf, "else") == 0;
+ case 'f':
+ return std::strcmp(buf, "false") == 0;
+ case 'i':
+ return std::strcmp(buf, "if") == 0;
+ case 'o':
+ return std::strcmp(buf, "or") == 0;
+ case 'n':
+ return std::strcmp(buf, "not") == 0;
+ case 't':
+ return std::strcmp(buf, "true") == 0;
+ }
+ 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, 1, 1, 1, 1,
+ /* 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,
+ };
+
+ istream_pos_type pos = in.tellg();
+
+ 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 {
+ char * _p = buf;
+ c = static_cast<char>(in.peek());
+ while (_p - buf < 255 && in.good() && ! in.eof() && c != '\n') {
+ std::size_t bytes = 0;
+ std::ptrdiff_t size = _p - buf;
+ unsigned char d = c;
+
+ // Check for the start of a UTF-8 multi-byte encoded string
+ if (d >= 192 && d <= 223 && size < 254)
+ bytes = 2;
+ else if (d >= 224 && d <= 239 && size < 253)
+ bytes = 3;
+ else if (d >= 240 && d <= 247 && size < 252)
+ bytes = 4;
+ else if (d >= 248 && d <= 251 && size < 251)
+ bytes = 5;
+ else if (d >= 252 && d <= 253 && size < 250)
+ bytes = 6;
+ else if (d >= 254) // UTF-8 encoding error
+ break;
+
+ if (bytes > 0) { // we're looking at a UTF-8 encoding
+ for (std::size_t i = 0; i < bytes; i++) {
+ in.get(c);
+ if (in.bad() || in.eof())
+ throw_(amount_error, _("Invalid UTF-8 encoding for commodity name"));
+ *_p++ = c;
+ }
+ }
+ else if (invalid_chars[static_cast<unsigned char>(c)]) {
+ break;
+ }
+ else {
+ in.get(c);
+ if (in.eof())
+ break;
+ if (c == '\\') {
+ in.get(c);
+ if (in.eof())
+ throw_(amount_error, _("Backslash at end of commodity name"));
+ }
+ *_p++ = c;
+ }
+
+ c = static_cast<char>(in.peek());
+ }
+ *_p = '\0';
+
+ if (is_reserved_token(buf))
+ buf[0] = '\0';
+ }
+ symbol = buf;
+
+ if (symbol.length() == 0) {
+ in.clear();
+ in.seekg(pos, std::ios::beg);
+ }
+}
+
+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 != pool().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;
+}
+
+bool compare_amount_commodities::operator()(const amount_t * left,
+ const amount_t * right) const
+{
+ commodity_t& leftcomm(left->commodity());
+ commodity_t& rightcomm(right->commodity());
+
+ DEBUG("commodity.compare", " left symbol (" << leftcomm << ")");
+ DEBUG("commodity.compare", "right symbol (" << rightcomm << ")");
+
+ int cmp = leftcomm.base_symbol().compare(rightcomm.base_symbol());
+ if (cmp != 0)
+ return cmp < 0;
+
+ if (! leftcomm.has_annotation()) {
+ return rightcomm.has_annotation();
+ }
+ else if (! rightcomm.has_annotation()) {
+ return ! leftcomm.has_annotation();
+ }
+ 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);
+ amount_t rightprice(*arightcomm.details.price);
+
+ 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) {
+ gregorian::date_duration 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;
+ }
+}
+
+void to_xml(std::ostream& out, const commodity_t& comm,
+ bool commodity_details)
+{
+ push_xml x(out, "commodity", true);
+
+ out << " flags=\"";
+ if (! (comm.has_flags(COMMODITY_STYLE_SUFFIXED))) out << 'P';
+ if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) out << 'S';
+ if (comm.has_flags(COMMODITY_STYLE_THOUSANDS)) out << 'T';
+ if (comm.has_flags(COMMODITY_STYLE_EUROPEAN)) out << 'E';
+ out << '"';
+
+ x.close_attrs();
+
+ {
+ push_xml y(out, "symbol");
+ out << y.guard(comm.symbol());
+ }
+
+ if (commodity_details) {
+ if (comm.has_annotation())
+ to_xml(out, as_annotated_commodity(comm).details);
+
+ if (comm.varied_history()) {
+ push_xml y(out, "varied-history");
+
+ foreach (const commodity_t::history_by_commodity_map::value_type& pair,
+ comm.varied_history()->histories) {
+ {
+ push_xml z(out, "symbol");
+ out << y.guard(pair.first->symbol());
+ }
+ {
+ push_xml z(out, "history");
+
+ foreach (const commodity_t::history_map::value_type& inner_pair,
+ pair.second.prices) {
+ push_xml w(out, "price-point");
+ to_xml(out, inner_pair.first);
+ to_xml(out, inner_pair.second);
+ }
+ }
+ }
+ }
+ }
+}
+
+} // namespace ledger
diff --git a/src/commodity.h b/src/commodity.h
new file mode 100644
index 00000000..3370f3f2
--- /dev/null
+++ b/src/commodity.h
@@ -0,0 +1,431 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup math
+ */
+
+/**
+ * @file commodity.h
+ * @author John Wiegley
+ *
+ * @ingroup math
+ *
+ * @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 keep_details_t;
+
+DECLARE_EXCEPTION(commodity_error, std::runtime_error);
+
+struct price_point_t
+{
+ datetime_t when;
+ amount_t price;
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & when;
+ ar & price;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+class commodity_t
+ : public delegates_flags<uint_least16_t>,
+ public equality_comparable1<commodity_t, noncopyable>
+{
+public:
+ typedef std::map<const datetime_t, amount_t> history_map;
+
+ struct history_t
+ {
+ history_map prices;
+
+ void add_price(commodity_t& source,
+ const datetime_t& date,
+ const amount_t& price,
+ const bool reflexive = true);
+ bool remove_price(const datetime_t& date);
+
+ optional<price_point_t>
+ find_price(const optional<datetime_t>& moment = none,
+ const optional<datetime_t>& oldest = none
+#if defined(DEBUG_ON)
+ , const int indent = 0
+#endif
+ ) const;
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+ private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & prices;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+ };
+
+ typedef std::map<commodity_t *, history_t> history_by_commodity_map;
+
+ struct varied_history_t
+ {
+ history_by_commodity_map histories;
+
+ void add_price(commodity_t& source,
+ const datetime_t& date,
+ const amount_t& price,
+ const bool reflexive = true);
+ bool remove_price(const datetime_t& date, commodity_t& commodity);
+
+ optional<price_point_t>
+ find_price(const commodity_t& source,
+ const optional<commodity_t&>& commodity = none,
+ const optional<datetime_t>& moment = none,
+ const optional<datetime_t>& oldest = none
+#if defined(DEBUG_ON)
+ , const int indent = 0
+#endif
+ ) const;
+
+ optional<history_t&>
+ history(const optional<commodity_t&>& commodity = none);
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+ private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & histories;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+ };
+
+protected:
+ friend class commodity_pool_t;
+ friend class annotated_commodity_t;
+
+ class base_t : public noncopyable, public supports_flags<uint_least16_t>
+ {
+ public:
+#define COMMODITY_STYLE_DEFAULTS 0x000
+#define COMMODITY_STYLE_SUFFIXED 0x001
+#define COMMODITY_STYLE_SEPARATED 0x002
+#define COMMODITY_STYLE_EUROPEAN 0x004
+#define COMMODITY_STYLE_THOUSANDS 0x008
+#define COMMODITY_NOMARKET 0x010
+#define COMMODITY_BUILTIN 0x020
+#define COMMODITY_WALKED 0x040
+#define COMMODITY_KNOWN 0x080
+#define COMMODITY_PRIMARY 0x100
+
+ string symbol;
+ amount_t::precision_t precision;
+ optional<string> name;
+ optional<string> note;
+ optional<varied_history_t> varied_history;
+ optional<amount_t> smaller;
+ optional<amount_t> larger;
+
+ mutable bool searched;
+
+ public:
+ explicit base_t(const string& _symbol)
+ : supports_flags<uint_least16_t>
+ (commodity_t::european_by_default ?
+ static_cast<uint_least16_t>(COMMODITY_STYLE_EUROPEAN) :
+ static_cast<uint_least16_t>(COMMODITY_STYLE_DEFAULTS)),
+ symbol(_symbol), precision(0), searched(false) {
+ TRACE_CTOR(base_t, "const string&");
+ }
+ virtual ~base_t() {
+ TRACE_DTOR(base_t);
+ }
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+ private:
+ base_t() {
+ TRACE_CTOR(base_t, "");
+ }
+
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & boost::serialization::base_object<supports_flags<uint_least16_t> >(*this);
+ ar & symbol;
+ ar & precision;
+ ar & name;
+ ar & note;
+ ar & varied_history;
+ ar & smaller;
+ ar & larger;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+ };
+
+ shared_ptr<base_t> base;
+
+ commodity_pool_t * parent_;
+ optional<string> qualified_symbol;
+ optional<string> mapping_key_;
+ bool annotated;
+
+ explicit commodity_t(commodity_pool_t * _parent,
+ const shared_ptr<base_t>& _base)
+ : delegates_flags<uint_least16_t>(*_base.get()), base(_base),
+ parent_(_parent), annotated(false) {
+ TRACE_CTOR(commodity_t, "commodity_pool_t *, shared_ptr<base_t>");
+ }
+
+public:
+ static bool european_by_default;
+
+ virtual ~commodity_t() {
+ TRACE_DTOR(commodity_t);
+ }
+
+ operator bool() const;
+
+ virtual bool operator==(const commodity_t& comm) const {
+ if (comm.annotated)
+ return comm == *this;
+ return base.get() == comm.base.get();
+ }
+
+ static bool symbol_needs_quotes(const string& symbol);
+
+ virtual commodity_t& referent() {
+ return *this;
+ }
+ virtual const commodity_t& referent() const {
+ return *this;
+ }
+
+ bool has_annotation() const {
+ return annotated;
+ }
+
+ virtual commodity_t& strip_annotations(const keep_details_t&) {
+ return *this;
+ }
+ virtual void write_annotations(std::ostream&) const {}
+
+ commodity_pool_t& pool() 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<varied_history_t&> varied_history() {
+ if (base->varied_history)
+ return *base->varied_history;
+ return none;
+ }
+ optional<const varied_history_t&> varied_history() const {
+ if (base->varied_history)
+ return *base->varied_history;
+ return none;
+ }
+
+ optional<history_t&> history(const optional<commodity_t&>& commodity);
+
+ // These methods provide a transparent pass-through to the underlying
+ // base->varied_history object.
+
+ void add_price(const datetime_t& date, const amount_t& price,
+ const bool reflexive = true) {
+ if (! base->varied_history)
+ base->varied_history = varied_history_t();
+
+ base->varied_history->add_price(*this, date, price, reflexive);
+ }
+ bool remove_price(const datetime_t& date, commodity_t& commodity) {
+ if (base->varied_history)
+ base->varied_history->remove_price(date, commodity);
+ return false;
+ }
+
+ optional<price_point_t>
+ find_price(const optional<commodity_t&>& commodity = none,
+ const optional<datetime_t>& moment = none,
+ const optional<datetime_t>& oldest = none
+#if defined(DEBUG_ON)
+ , const int indent = 0
+#endif
+ ) const {
+ if (base->varied_history && ! has_flags(COMMODITY_WALKED)) {
+ const_cast<commodity_t&>(*this).add_flags(COMMODITY_WALKED);
+ optional<price_point_t> point =
+ base->varied_history->find_price(*this, commodity, moment, oldest
+#if defined(DEBUG_ON)
+ , indent
+#endif
+ );
+ const_cast<commodity_t&>(*this).drop_flags(COMMODITY_WALKED);
+ return point;
+ }
+ return none;
+ }
+
+ optional<price_point_t>
+ check_for_updated_price(const optional<price_point_t>& point,
+ const optional<datetime_t>& moment,
+ const optional<commodity_t&>& in_terms_of);
+
+ // Methods related to parsing, reading, writing, etc., the commodity
+ // itself.
+
+ 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();
+ }
+
+ bool valid() const;
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ supports_flags<uint_least16_t> temp_flags;
+
+protected:
+ explicit commodity_t()
+ : delegates_flags<uint_least16_t>(temp_flags), parent_(NULL),
+ annotated(false) {
+ TRACE_CTOR(commodity_t, "");
+ }
+
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & boost::serialization::base_object<delegates_flags<uint_least16_t> >(*this);
+ ar & base;
+ ar & parent_;
+ ar & qualified_symbol;
+ ar & mapping_key_;
+ ar & annotated;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+inline std::ostream& operator<<(std::ostream& out, const commodity_t& comm) {
+ comm.print(out);
+ return out;
+}
+
+struct compare_amount_commodities {
+ bool operator()(const amount_t * left, const amount_t * right) const;
+};
+
+void to_xml(std::ostream& out, const commodity_t& comm,
+ bool commodity_details = false);
+
+} // namespace ledger
+
+#endif // _COMMODITY_H
diff --git a/src/compare.cc b/src/compare.cc
new file mode 100644
index 00000000..36884a46
--- /dev/null
+++ b/src/compare.cc
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "compare.h"
+#include "op.h"
+#include "post.h"
+#include "account.h"
+
+namespace ledger {
+
+void push_sort_value(std::list<sort_value_t>& sort_values,
+ expr_t::ptr_op_t node, scope_t& scope)
+{
+ if (node->kind == expr_t::op_t::O_CONS) {
+ push_sort_value(sort_values, node->left(), scope);
+ push_sort_value(sort_values, node->right(), scope);
+ } else {
+ bool inverted = false;
+
+ if (node->kind == expr_t::op_t::O_NEG) {
+ inverted = true;
+ node = node->left();
+ }
+
+ sort_values.push_back(sort_value_t());
+ sort_values.back().inverted = inverted;
+ sort_values.back().value = expr_t(node).calc(scope).simplified();
+
+ if (sort_values.back().value.is_null())
+ throw_(calc_error,
+ _("Could not determine sorting value based an expression"));
+ }
+}
+
+template <>
+bool compare_items<post_t>::operator()(post_t * left, post_t * right)
+{
+ assert(left);
+ assert(right);
+
+ post_t::xdata_t& lxdata(left->xdata());
+ if (! lxdata.has_flags(POST_EXT_SORT_CALC)) {
+ bind_scope_t bound_scope(*sort_order.get_context(), *left);
+ find_sort_values(lxdata.sort_values, bound_scope);
+ lxdata.add_flags(POST_EXT_SORT_CALC);
+ }
+
+ post_t::xdata_t& rxdata(right->xdata());
+ if (! rxdata.has_flags(POST_EXT_SORT_CALC)) {
+ bind_scope_t bound_scope(*sort_order.get_context(), *right);
+ find_sort_values(rxdata.sort_values, bound_scope);
+ rxdata.add_flags(POST_EXT_SORT_CALC);
+ }
+
+ return sort_value_is_less_than(lxdata.sort_values, rxdata.sort_values);
+}
+
+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)) {
+ bind_scope_t bound_scope(*sort_order.get_context(), *left);
+ find_sort_values(lxdata.sort_values, bound_scope);
+ lxdata.add_flags(ACCOUNT_EXT_SORT_CALC);
+ }
+
+ account_t::xdata_t& rxdata(right->xdata());
+ if (! rxdata.has_flags(ACCOUNT_EXT_SORT_CALC)) {
+ bind_scope_t bound_scope(*sort_order.get_context(), *right);
+ find_sort_values(rxdata.sort_values, bound_scope);
+ rxdata.add_flags(ACCOUNT_EXT_SORT_CALC);
+ }
+
+ DEBUG("value.sort", "Comparing accounts " << left->fullname()
+ << " <> " << right->fullname());
+
+ return sort_value_is_less_than(lxdata.sort_values, rxdata.sort_values);
+}
+
+} // namespace ledger
diff --git a/src/compare.h b/src/compare.h
new file mode 100644
index 00000000..740ba275
--- /dev/null
+++ b/src/compare.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup data
+ */
+
+/**
+ * @file compare.h
+ * @author John Wiegley
+ *
+ * @ingroup data
+ */
+#ifndef _COMPARE_H
+#define _COMPARE_H
+
+#include "expr.h"
+
+namespace ledger {
+
+class post_t;
+class account_t;
+
+void push_sort_value(std::list<sort_value_t>& sort_values,
+ expr_t::ptr_op_t node, scope_t& scope);
+
+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);
+ }
+
+ void find_sort_values(std::list<sort_value_t>& sort_values, scope_t& scope) {
+ push_sort_value(sort_values, sort_order.get_op(), scope);
+ }
+
+ bool operator()(T * left, T * right);
+};
+
+sort_value_t calc_sort_value(const expr_t::ptr_op_t op);
+
+template <typename T>
+bool compare_items<T>::operator()(T * left, T * right)
+{
+ assert(left); assert(right);
+ return sort_value_is_less_than(find_sort_values(left),
+ find_sort_values(right));
+}
+
+template <>
+bool compare_items<post_t>::operator()(post_t * left, post_t * right);
+template <>
+bool compare_items<account_t>::operator()(account_t * left,
+ account_t * right);
+
+} // namespace ledger
+
+#endif // _COMPARE_H
diff --git a/src/draft.cc b/src/draft.cc
new file mode 100644
index 00000000..8478a31d
--- /dev/null
+++ b/src/draft.cc
@@ -0,0 +1,527 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "draft.h"
+#include "xact.h"
+#include "post.h"
+#include "account.h"
+#include "journal.h"
+#include "session.h"
+#include "report.h"
+#include "output.h"
+
+namespace ledger {
+
+void draft_t::xact_template_t::dump(std::ostream& out) const
+{
+ if (date)
+ out << _("Date: ") << *date << std::endl;
+ else
+ out << _("Date: <today>") << std::endl;
+
+ if (code)
+ out << _("Code: ") << *code << std::endl;
+ if (note)
+ out << _("Note: ") << *note << std::endl;
+
+ if (payee_mask.empty())
+ out << _("Payee mask: INVALID (template expression will cause an error)")
+ << std::endl;
+ else
+ out << _("Payee mask: ") << payee_mask << std::endl;
+
+ if (posts.empty()) {
+ out << std::endl
+ << _("<Posting copied from last related transaction>")
+ << std::endl;
+ } else {
+ bool has_only_from = true;
+ bool has_only_to = true;
+
+ foreach (const post_template_t& post, posts) {
+ if (post.from)
+ has_only_to = false;
+ else
+ has_only_from = false;
+ }
+
+ foreach (const post_template_t& post, posts) {
+ straccstream accum;
+ out << std::endl
+ << ACCUM(accum << _("[Posting \"%1\"]")
+ << (post.from ? _("from") : _("to")))
+ << std::endl;
+
+ if (post.account_mask)
+ out << _(" Account mask: ") << *post.account_mask << std::endl;
+ else if (post.from)
+ out << _(" Account mask: <use last of last related accounts>") << std::endl;
+ else
+ out << _(" Account mask: <use first of last related accounts>") << std::endl;
+
+ if (post.amount)
+ out << _(" Amount: ") << *post.amount << std::endl;
+
+ if (post.cost)
+ out << _(" Cost: ") << *post.cost_operator
+ << " " << *post.cost << std::endl;
+ }
+ }
+}
+
+void draft_t::parse_args(const value_t& args)
+{
+ regex date_mask(_("([0-9]+(?:[-/.][0-9]+)?(?:[-/.][0-9]+))?"));
+ smatch what;
+ bool check_for_date = true;
+
+ tmpl = xact_template_t();
+
+ optional<date_time::weekdays> weekday;
+ xact_template_t::post_template_t * post = NULL;
+
+ value_t::sequence_t::const_iterator begin = args.begin();
+ value_t::sequence_t::const_iterator end = args.end();
+
+ for (; begin != end; begin++) {
+ if (check_for_date &&
+ regex_match((*begin).to_string(), what, date_mask)) {
+ tmpl->date = parse_date(what[0]);
+ check_for_date = false;
+ }
+ else if (check_for_date &&
+ bool(weekday = string_to_day_of_week(what[0]))) {
+ short dow = static_cast<short>(*weekday);
+ date_t date = CURRENT_DATE() - date_duration(1);
+ while (date.day_of_week() != dow)
+ date -= date_duration(1);
+ tmpl->date = date;
+ check_for_date = false;
+ }
+ else {
+ string arg = (*begin).to_string();
+
+ if (arg == "at") {
+ if (begin == end)
+ throw std::runtime_error(_("Invalid xact command arguments"));
+ tmpl->payee_mask = (*++begin).to_string();
+ }
+ else if (arg == "to" || arg == "from") {
+ if (! post || post->account_mask) {
+ tmpl->posts.push_back(xact_template_t::post_template_t());
+ post = &tmpl->posts.back();
+ }
+ if (begin == end)
+ throw std::runtime_error(_("Invalid xact command arguments"));
+ post->account_mask = mask_t((*++begin).to_string());
+ post->from = arg == "from";
+ }
+ else if (arg == "on") {
+ if (begin == end)
+ throw std::runtime_error(_("Invalid xact command arguments"));
+ tmpl->date = parse_date((*++begin).to_string());
+ check_for_date = false;
+ }
+ else if (arg == "code") {
+ if (begin == end)
+ throw std::runtime_error(_("Invalid xact command arguments"));
+ tmpl->code = (*++begin).to_string();
+ }
+ else if (arg == "note") {
+ if (begin == end)
+ throw std::runtime_error(_("Invalid xact command arguments"));
+ tmpl->note = (*++begin).to_string();
+ }
+ else if (arg == "rest") {
+ ; // just ignore this argument
+ }
+ else if (arg == "@" || arg == "@@") {
+ amount_t cost;
+ post->cost_operator = arg;
+ if (begin == end)
+ throw std::runtime_error(_("Invalid xact command arguments"));
+ arg = (*++begin).to_string();
+ if (! cost.parse(arg, PARSE_SOFT_FAIL | PARSE_NO_MIGRATE))
+ throw std::runtime_error(_("Invalid xact command arguments"));
+ post->cost = cost;
+ }
+ else {
+ // Without a preposition, it is either:
+ //
+ // A payee, if we have not seen one
+ // An account or an amount, if we have
+ // An account if an amount has just been seen
+ // An amount if an account has just been seen
+
+ if (tmpl->payee_mask.empty()) {
+ tmpl->payee_mask = arg;
+ }
+ else {
+ amount_t amt;
+ optional<mask_t> account;
+
+ if (! amt.parse(arg, PARSE_SOFT_FAIL | PARSE_NO_MIGRATE))
+ account = mask_t(arg);
+
+ if (! post ||
+ (account && post->account_mask) ||
+ (! account && post->amount)) {
+ tmpl->posts.push_back(xact_template_t::post_template_t());
+ post = &tmpl->posts.back();
+ }
+
+ if (account) {
+ post->from = false;
+ post->account_mask = account;
+ } else {
+ post->amount = amt;
+ }
+ }
+ }
+ }
+ }
+
+ if (! tmpl->posts.empty()) {
+ bool has_only_from = true;
+ bool has_only_to = true;
+
+ // A single account at the end of the line is the "from" account
+ if (tmpl->posts.size() > 1 &&
+ tmpl->posts.back().account_mask && ! tmpl->posts.back().amount)
+ tmpl->posts.back().from = true;
+
+ foreach (xact_template_t::post_template_t& post, tmpl->posts) {
+ if (post.from)
+ has_only_to = false;
+ else
+ has_only_from = false;
+ }
+
+ if (has_only_from) {
+ tmpl->posts.push_front(xact_template_t::post_template_t());
+ }
+ else if (has_only_to) {
+ tmpl->posts.push_back(xact_template_t::post_template_t());
+ tmpl->posts.back().from = true;
+ }
+ }
+}
+
+xact_t * draft_t::insert(journal_t& journal)
+{
+ if (tmpl->payee_mask.empty())
+ throw std::runtime_error(_("xact' command requires at least a payee"));
+
+ xact_t * matching = NULL;
+
+ std::auto_ptr<xact_t> added(new xact_t);
+
+ for (xacts_list::reverse_iterator j = journal.xacts.rbegin();
+ j != journal.xacts.rend();
+ j++) {
+ if (tmpl->payee_mask.match((*j)->payee)) {
+ matching = *j;
+ DEBUG("derive.xact",
+ "Found payee match: transaction on line " << (*j)->pos->beg_line);
+ break;
+ }
+ }
+
+ if (! tmpl->date) {
+ added->_date = CURRENT_DATE();
+ DEBUG("derive.xact", "Setting date to current date");
+ } else {
+ added->_date = tmpl->date;
+ DEBUG("derive.xact", "Setting date to template date: " << *tmpl->date);
+ }
+
+ added->set_state(item_t::UNCLEARED);
+
+ if (matching) {
+ added->payee = matching->payee;
+ added->code = matching->code;
+ added->note = matching->note;
+
+#if defined(DEBUG_ON)
+ DEBUG("derive.xact", "Setting payee from match: " << added->payee);
+ if (added->code)
+ DEBUG("derive.xact", "Setting code from match: " << *added->code);
+ if (added->note)
+ DEBUG("derive.xact", "Setting note from match: " << *added->note);
+#endif
+ } else {
+ added->payee = tmpl->payee_mask.str();
+ DEBUG("derive.xact", "Setting payee from template: " << added->payee);
+ }
+
+ if (tmpl->code) {
+ added->code = tmpl->code;
+ DEBUG("derive.xact", "Now setting code from template: " << *added->code);
+ }
+ if (tmpl->note) {
+ added->note = tmpl->note;
+ DEBUG("derive.xact", "Now setting note from template: " << *added->note);
+ }
+
+ if (tmpl->posts.empty()) {
+ if (matching) {
+ DEBUG("derive.xact", "Template had no postings, copying from match");
+
+ foreach (post_t * post, matching->posts) {
+ added->add_post(new post_t(*post));
+ added->posts.back()->set_state(item_t::UNCLEARED);
+ }
+ } else {
+ throw_(std::runtime_error,
+ _("No accounts, and no past transaction matching '%1'")
+ << tmpl->payee_mask);
+ }
+ } else {
+ DEBUG("derive.xact", "Template had postings");
+
+ bool any_post_has_amount = false;
+ foreach (xact_template_t::post_template_t& post, tmpl->posts) {
+ if (post.amount) {
+ DEBUG("derive.xact", " and at least one has an amount specified");
+ any_post_has_amount = true;
+ break;
+ }
+ }
+
+ foreach (xact_template_t::post_template_t& post, tmpl->posts) {
+ std::auto_ptr<post_t> new_post;
+
+ commodity_t * found_commodity = NULL;
+
+ if (matching) {
+ if (post.account_mask) {
+ DEBUG("derive.xact",
+ "Looking for matching posting based on account mask");
+
+ foreach (post_t * x, matching->posts) {
+ if (post.account_mask->match(x->account->fullname())) {
+ new_post.reset(new post_t(*x));
+ DEBUG("derive.xact",
+ "Founding posting from line " << x->pos->beg_line);
+ break;
+ }
+ }
+ } else {
+ if (post.from) {
+ for (posts_list::reverse_iterator j = matching->posts.rbegin();
+ j != matching->posts.rend();
+ j++) {
+ if ((*j)->must_balance()) {
+ new_post.reset(new post_t(**j));
+ DEBUG("derive.xact",
+ "Copied last real posting from matching");
+ break;
+ }
+ }
+ } else {
+ for (posts_list::iterator j = matching->posts.begin();
+ j != matching->posts.end();
+ j++) {
+ if ((*j)->must_balance()) {
+ new_post.reset(new post_t(**j));
+ DEBUG("derive.xact",
+ "Copied first real posting from matching");
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (! new_post.get()) {
+ new_post.reset(new post_t);
+ DEBUG("derive.xact", "New posting was NULL, creating a blank one");
+ }
+
+ if (! new_post->account) {
+ DEBUG("derive.xact", "New posting still needs an account");
+
+ if (post.account_mask) {
+ DEBUG("derive.xact", "The template has an account mask");
+
+ account_t * acct = NULL;
+ if (! acct) {
+ acct = journal.find_account_re(post.account_mask->str());
+#if defined(DEBUG_ON)
+ if (acct)
+ DEBUG("derive.xact", "Found account as a regular expression");
+#endif
+ }
+ if (! acct) {
+ acct = journal.find_account(post.account_mask->str());
+#if defined(DEBUG_ON)
+ if (acct)
+ DEBUG("derive.xact", "Found (or created) account by name");
+#endif
+ }
+
+ // Find out the default commodity to use by looking at the last
+ // commodity used in that account
+ for (xacts_list::reverse_iterator j = journal.xacts.rbegin();
+ j != journal.xacts.rend();
+ j++) {
+ foreach (post_t * x, (*j)->posts) {
+ if (x->account == acct && ! x->amount.is_null()) {
+ new_post.reset(new post_t(*x));
+ DEBUG("derive.xact",
+ "Found account in journal postings, setting new posting");
+ break;
+ }
+ }
+ }
+
+ new_post->account = acct;
+ DEBUG("derive.xact",
+ "Set new posting's account to: " << acct->fullname());
+ } else {
+ if (post.from) {
+ new_post->account = journal.find_account(_("Liabilities:Unknown"));
+ DEBUG("derive.xact",
+ "Set new posting's account to: Liabilities:Unknown");
+ } else {
+ new_post->account = journal.find_account(_("Expenses:Unknown"));
+ DEBUG("derive.xact",
+ "Set new posting's account to: Expenses:Unknown");
+ }
+ }
+ }
+ assert(new_post->account);
+
+ if (new_post.get() && ! new_post->amount.is_null()) {
+ found_commodity = &new_post->amount.commodity();
+
+ if (any_post_has_amount) {
+ new_post->amount = amount_t();
+ DEBUG("derive.xact", "New posting has an amount, but we cleared it");
+ } else {
+ any_post_has_amount = true;
+ DEBUG("derive.xact", "New posting has an amount, and we're using it");
+ }
+ }
+
+ if (post.amount) {
+ new_post->amount = *post.amount;
+ DEBUG("derive.xact", "Copied over posting amount");
+
+ if (post.from) {
+ new_post->amount.in_place_negate();
+ DEBUG("derive.xact", "Negated new posting amount");
+ }
+ }
+
+ if (post.cost) {
+ if (post.cost->sign() < 0)
+ throw parse_error(_("A posting's cost may not be negative"));
+
+ post.cost->in_place_unround();
+
+ if (*post.cost_operator == "@") {
+ // For the sole case where the cost might be uncommoditized,
+ // guarantee that the commodity of the cost after multiplication
+ // is the same as it was before.
+ commodity_t& cost_commodity(post.cost->commodity());
+ *post.cost *= new_post->amount;
+ post.cost->set_commodity(cost_commodity);
+ }
+
+ new_post->cost = *post.cost;
+ DEBUG("derive.xact", "Copied over posting cost");
+ }
+
+ if (found_commodity &&
+ ! new_post->amount.is_null() &&
+ ! new_post->amount.has_commodity()) {
+ new_post->amount.set_commodity(*found_commodity);
+ DEBUG("derive.xact", "Set posting amount commodity to: "
+ << new_post->amount.commodity());
+
+ new_post->amount = new_post->amount.rounded();
+ DEBUG("derive.xact",
+ "Rounded posting amount to: " << new_post->amount);
+ }
+
+ added->add_post(new_post.release());
+ added->posts.back()->account->add_post(added->posts.back());
+ added->posts.back()->set_state(item_t::UNCLEARED);
+
+ DEBUG("derive.xact", "Added new posting to derived entry");
+ }
+ }
+
+ if (! journal.add_xact(added.get()))
+ throw_(std::runtime_error,
+ _("Failed to finalize derived transaction (check commodities)"));
+
+ return added.release();
+}
+
+value_t template_command(call_scope_t& args)
+{
+ report_t& report(find_scope<report_t>(args));
+ std::ostream& out(report.output_stream);
+
+ out << _("--- Input arguments ---") << std::endl;
+ args.value().dump(out);
+ out << std::endl << std::endl;
+
+ draft_t draft(args.value());
+
+ out << _("--- Transaction template ---") << std::endl;
+ draft.dump(out);
+
+ return true;
+}
+
+value_t xact_command(call_scope_t& args)
+{
+ report_t& report(find_scope<report_t>(args));
+ draft_t draft(args.value());
+
+ xact_t * new_xact = draft.insert(*report.session.journal.get());
+
+ // Only consider actual postings for the "xact" command
+ report.HANDLER(limit_).on(string("#xact"), "actual");
+
+ report.xact_report(post_handler_ptr
+ (new format_posts(report,
+ report.HANDLER(print_format_).str())),
+ *new_xact);
+ return true;
+}
+
+} // namespace ledger
diff --git a/src/draft.h b/src/draft.h
new file mode 100644
index 00000000..93e98ff5
--- /dev/null
+++ b/src/draft.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup expr
+ */
+
+/**
+ * @file draft.h
+ * @author John Wiegley
+ *
+ * @ingroup report
+ */
+#ifndef _DRAFT_H
+#define _DRAFT_H
+
+#include "exprbase.h"
+#include "value.h"
+
+namespace ledger {
+
+class journal_t;
+class xact_t;
+
+class draft_t : public expr_base_t<value_t>
+{
+ typedef expr_base_t<value_t> base_type;
+
+ struct xact_template_t
+ {
+ optional<date_t> date;
+ optional<string> code;
+ optional<string> note;
+ mask_t payee_mask;
+
+ struct post_template_t {
+ bool from;
+ optional<mask_t> account_mask;
+ optional<amount_t> amount;
+ optional<string> cost_operator;
+ optional<amount_t> cost;
+
+ post_template_t() : from(false) {}
+ };
+
+ std::list<post_template_t> posts;
+
+ xact_template_t() {}
+
+ void dump(std::ostream& out) const;
+ };
+
+ optional<xact_template_t> tmpl;
+
+public:
+ draft_t(const value_t& args) : base_type() {
+ TRACE_CTOR(draft_t, "value_t");
+ if (! args.empty())
+ parse_args(args);
+ }
+ virtual ~draft_t() {
+ TRACE_DTOR(draft_t);
+ }
+
+ void parse_args(const value_t& args);
+
+ virtual result_type real_calc(scope_t&) {
+ assert(false);
+ return true;
+ }
+
+ xact_t * insert(journal_t& journal);
+
+ virtual void dump(std::ostream& out) const {
+ if (tmpl)
+ tmpl->dump(out);
+ }
+};
+
+value_t xact_command(call_scope_t& args);
+value_t template_command(call_scope_t& args);
+
+} // namespace ledger
+
+#endif // _DRAFT_H
diff --git a/src/emacs.cc b/src/emacs.cc
new file mode 100644
index 00000000..dc1a18ae
--- /dev/null
+++ b/src/emacs.cc
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "emacs.h"
+#include "xact.h"
+#include "post.h"
+#include "account.h"
+
+namespace ledger {
+
+void format_emacs_posts::write_xact(xact_t& xact)
+{
+ out << "\"" << xact.pos->pathname << "\" "
+ << xact.pos->beg_line << " ";
+
+ tm when = gregorian::to_tm(xact.date());
+ std::time_t date = std::mktime(&when);
+
+ out << "(" << (date / 65536) << " " << (date % 65536) << " 0) ";
+
+ if (! xact.code)
+ out << "nil ";
+ else
+ out << "\"" << *xact.code << "\" ";
+
+ if (xact.payee.empty())
+ out << "nil";
+ else
+ out << "\"" << xact.payee << "\"";
+
+ out << "\n";
+}
+
+void format_emacs_posts::operator()(post_t& post)
+{
+ if (! post.has_xdata() ||
+ ! post.xdata().has_flags(POST_EXT_DISPLAYED)) {
+ if (! last_xact) {
+ out << "((";
+ write_xact(*post.xact);
+ }
+ else if (post.xact != last_xact) {
+ out << ")\n (";
+ write_xact(*post.xact);
+ }
+ else {
+ out << "\n";
+ }
+
+ out << " (" << post.pos->beg_line << " ";
+ out << "\"" << post.reported_account()->fullname() << "\" \""
+ << post.amount << "\"";
+
+ switch (post.state()) {
+ case item_t::CLEARED:
+ out << " t";
+ break;
+ case item_t::PENDING:
+ out << " pending";
+ break;
+ default:
+ out << " nil";
+ break;
+ }
+
+ if (post.cost)
+ out << " \"" << *post.cost << "\"";
+ if (post.note)
+ out << " \"" << *post.note << "\"";
+ out << ")";
+
+ last_xact = post.xact;
+
+ post.xdata().add_flags(POST_EXT_DISPLAYED);
+ }
+}
+
+} // namespace ledger
diff --git a/src/emacs.h b/src/emacs.h
new file mode 100644
index 00000000..4b2a452a
--- /dev/null
+++ b/src/emacs.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup report
+ */
+
+/**
+ * @file emacs.h
+ * @author John Wiegley
+ *
+ * @ingroup report
+ */
+#ifndef _EMACS_H
+#define _EMACS_H
+
+#include "chain.h"
+
+namespace ledger {
+
+class xact_t;
+
+class format_emacs_posts : public item_handler<post_t>
+{
+ format_emacs_posts();
+
+protected:
+ std::ostream& out;
+ xact_t * last_xact;
+
+public:
+ format_emacs_posts(std::ostream& _out)
+ : out(_out), last_xact(NULL) {
+ TRACE_CTOR(format_emacs_posts, "std::ostream&");
+ }
+ ~format_emacs_posts() {
+ TRACE_DTOR(format_emacs_posts);
+ }
+
+ virtual void write_xact(xact_t& xact);
+ virtual void flush() {
+ if (last_xact)
+ out << "))\n";
+ out.flush();
+ }
+ virtual void operator()(post_t& post);
+};
+
+} // namespace ledger
+
+#endif // _REPORT_H
diff --git a/src/error.cc b/src/error.cc
new file mode 100644
index 00000000..d5abe4de
--- /dev/null
+++ b/src/error.cc
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "utils.h"
+
+namespace ledger {
+
+straccstream _ctxt_accum;
+std::ostringstream _ctxt_buffer;
+straccstream _desc_accum;
+std::ostringstream _desc_buffer;
+
+string error_context()
+{
+ string context = _ctxt_buffer.str();
+ _ctxt_buffer.str("");
+ return context;
+}
+
+string file_context(const path& file, const std::size_t line)
+{
+ std::ostringstream buf;
+ buf << "\"" << file << "\", line " << line << ": ";
+ return buf.str();
+}
+
+string line_context(const string& line,
+ const string::size_type pos,
+ const string::size_type end_pos)
+{
+ std::ostringstream buf;
+ buf << " " << line << "\n";
+
+ if (pos != 0) {
+ buf << " ";
+ if (end_pos == 0) {
+ for (string::size_type i = 0; i < pos; i += 1)
+ buf << " ";
+ buf << "^";
+ } else {
+ for (string::size_type i = 0; i < end_pos; i += 1) {
+ if (i >= pos)
+ buf << "^";
+ else
+ buf << " ";
+ }
+ }
+ }
+ return buf.str();
+}
+
+string source_context(const path& file,
+ const istream_pos_type pos,
+ const istream_pos_type end_pos,
+ const string& prefix)
+{
+ const std::streamoff len = end_pos - pos;
+ if (! len || file == path("/dev/stdin"))
+ return _("<no source context>");
+
+ assert(len > 0);
+ assert(len < 2048);
+
+ std::ostringstream out;
+
+ ifstream in(file);
+ in.seekg(pos, std::ios::beg);
+
+ scoped_array<char> buf(new char[static_cast<std::size_t>(len) + 1]);
+ in.read(buf.get(), static_cast<std::streamsize>(len));
+ assert(in.gcount() == static_cast<std::streamsize>(len));
+ buf[static_cast<std::size_t>(len)] = '\0';
+
+ bool first = true;
+ for (char * p = std::strtok(buf.get(), "\n");
+ p;
+ p = std::strtok(NULL, "\n")) {
+ if (first)
+ first = false;
+ else
+ out << '\n';
+ out << prefix << p;
+ }
+
+ return out.str();
+}
+
+} // namespace ledger
diff --git a/src/error.h b/src/error.h
new file mode 100644
index 00000000..5369faf1
--- /dev/null
+++ b/src/error.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup util
+ */
+
+/**
+ * @file error.h
+ * @author John Wiegley
+ *
+ * @ingroup util
+ */
+#ifndef _ERROR_H
+#define _ERROR_H
+
+#include "accum.h"
+
+namespace ledger {
+
+extern straccstream _desc_accum;
+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 << ACCUM(_desc_accum << msg)), \
+ _desc_accum.clear(), \
+ throw_func<cls>(_desc_buffer.str()))
+
+inline void warning_func(const string& message) {
+ std::cerr << "Warning: " << message << std::endl;
+ _desc_buffer.str("");
+}
+
+#define warning_(msg) \
+ ((_desc_buffer << ACCUM(_desc_accum << msg)), \
+ _desc_accum.clear(), \
+ warning_func(_desc_buffer.str()))
+
+extern straccstream _ctxt_accum;
+extern std::ostringstream _ctxt_buffer;
+
+#define add_error_context(msg) \
+ ((long(_ctxt_buffer.tellp()) == 0) ? \
+ ((_ctxt_buffer << ACCUM(_ctxt_accum << msg)), \
+ _ctxt_accum.clear()) : \
+ ((_ctxt_buffer << std::endl << ACCUM(_ctxt_accum << msg)), \
+ _ctxt_accum.clear()))
+
+string error_context();
+
+string file_context(const path& file, std::size_t line);
+string line_context(const string& line,
+ const string::size_type pos = 0,
+ const string::size_type end_pos = 0);
+
+string source_context(const path& file,
+ const istream_pos_type pos,
+ const istream_pos_type end_pos,
+ const string& prefix = "");
+
+#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..c59f8a57
--- /dev/null
+++ b/src/expr.cc
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "expr.h"
+#include "parser.h"
+
+namespace ledger {
+
+void expr_t::parse(std::istream& in, const parse_flags_t& flags,
+ const optional<string>& original_string)
+{
+ base_type::parse(in, flags, original_string);
+
+ parser_t parser;
+ ptr = parser.parse(in, flags, original_string);
+}
+
+void expr_t::compile(scope_t& scope)
+{
+ if (! compiled && ptr) {
+ ptr = ptr->compile(scope);
+ base_type::compile(scope);
+ }
+}
+
+value_t expr_t::real_calc(scope_t& scope)
+{
+ if (ptr) {
+ ptr_op_t locus;
+ try {
+ return ptr->calc(scope, &locus);
+ }
+ catch (const std::exception& err) {
+ if (locus) {
+ add_error_context(_("While evaluating value expression:"));
+ add_error_context(op_context(ptr, locus));
+
+ if (SHOW_INFO()) {
+ add_error_context(_("The value expression tree was:"));
+ std::ostringstream buf;
+ ptr->dump(buf, 0);
+
+ std::istringstream in(buf.str());
+ std::ostringstream out;
+ char linebuf[1024];
+ bool first = true;
+ while (in.good() && ! in.eof()) {
+ in.getline(linebuf, 1023);
+ std::streamsize len = in.gcount();
+ if (len > 0) {
+ if (first)
+ first = false;
+ else
+ out << '\n';
+ out << " " << linebuf;
+ }
+ }
+ add_error_context(out.str());
+ }
+ }
+ throw;
+ }
+ }
+ return NULL_VALUE;
+}
+
+bool expr_t::is_constant() const
+{
+ assert(compiled);
+ return ptr && ptr->is_value();
+}
+
+bool expr_t::is_function() const
+{
+ assert(compiled);
+ return ptr && 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();
+}
+
+expr_t::func_t& expr_t::get_function()
+{
+ assert(is_function());
+ return ptr->as_function_lval();
+}
+
+string expr_t::context_to_str() const
+{
+ return ptr ? op_context(ptr) : _("<empty expression>");
+}
+
+void expr_t::print(std::ostream& out) const
+{
+ if (ptr)
+ ptr->print(out);
+}
+
+void expr_t::dump(std::ostream& out) const
+{
+ if (ptr) ptr->dump(out, 0);
+}
+
+} // namespace ledger
diff --git a/src/expr.h b/src/expr.h
new file mode 100644
index 00000000..638855b1
--- /dev/null
+++ b/src/expr.h
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup expr
+ */
+
+/**
+ * @file expr.h
+ * @author John Wiegley
+ *
+ * @ingroup expr
+ */
+#ifndef _EXPR_H
+#define _EXPR_H
+
+#include "exprbase.h"
+#include "value.h"
+
+namespace ledger {
+
+class expr_t : public expr_base_t<value_t>
+{
+ struct token_t;
+ class parser_t;
+
+ typedef expr_base_t<value_t> base_type;
+
+public:
+ class op_t;
+ typedef intrusive_ptr<op_t> ptr_op_t;
+ typedef intrusive_ptr<const op_t> const_ptr_op_t;
+
+protected:
+ ptr_op_t ptr;
+
+public:
+ expr_t() : base_type() {
+ TRACE_CTOR(expr_t, "");
+ }
+ expr_t(const expr_t& other)
+ : base_type(other), ptr(other.ptr) {
+ TRACE_CTOR(expr_t, "copy");
+ }
+ expr_t(ptr_op_t _ptr, scope_t * _context = NULL)
+ : base_type(_context), ptr(_ptr) {
+ TRACE_CTOR(expr_t, "const ptr_op_t&, scope_t *");
+ }
+
+ expr_t(const string& _str, const parse_flags_t& flags = PARSE_DEFAULT)
+ : base_type() {
+ TRACE_CTOR(expr_t, "string, parse_flags_t");
+ if (! _str.empty())
+ parse(_str, flags);
+ }
+ expr_t(std::istream& in, const parse_flags_t& flags = PARSE_DEFAULT)
+ : base_type() {
+ TRACE_CTOR(expr_t, "std::istream&, parse_flags_t");
+ parse(in, flags);
+ }
+
+ virtual ~expr_t() {
+ TRACE_DTOR(expr_t);
+ }
+
+ expr_t& operator=(const expr_t& _expr) {
+ if (this != &_expr) {
+ base_type::operator=(_expr);
+ ptr = _expr.ptr;
+ }
+ return *this;
+ }
+
+ virtual operator bool() const throw() {
+ return ptr.get() != NULL;
+ }
+
+ ptr_op_t get_op() throw() {
+ return ptr;
+ }
+
+ void parse(const string& str, const parse_flags_t& flags = PARSE_DEFAULT) {
+ std::istringstream stream(str);
+ return parse(stream, flags, str);
+ }
+
+ virtual void parse(std::istream& in,
+ const parse_flags_t& flags = PARSE_DEFAULT,
+ const optional<string>& original_string = none);
+ virtual void compile(scope_t& scope);
+ virtual value_t real_calc(scope_t& scope);
+
+ bool is_constant() const;
+ value_t& constant_value();
+ const value_t& constant_value() const;
+ bool is_function() const;
+ func_t& get_function();
+
+ virtual string context_to_str() const;
+ virtual void print(std::ostream& out) const;
+ virtual void dump(std::ostream& out) const;
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & boost::serialization::base_object<base_type>(*this);
+ ar & ptr;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+} // namespace ledger
+
+#endif // _EXPR_H
diff --git a/src/exprbase.h b/src/exprbase.h
new file mode 100644
index 00000000..0b3466b0
--- /dev/null
+++ b/src/exprbase.h
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup expr
+ */
+
+/**
+ * @file exprbase.h
+ * @author John Wiegley
+ *
+ * @ingroup expr
+ *
+ * This class provides basic behavior for all the domain specific expression
+ * languages used in Leger:
+ *
+ * | Typename | Description | result_type | Derives |
+ * |-------------+----------------------------+-----------------+-------------|
+ * | expr_t | Value expressions | value_t | |
+ * | predicate_t | Special form of expr_t | bool | expr_t |
+ * | query_t | Report queries | bool | predicate_t |
+ * | period_t | Time periods and durations | date_interval_t | |
+ * | draft_t | Partially filled xacts | xact_t * | |
+ * | format_t | Format strings | string | |
+ */
+#ifndef _EXPRBASE_H
+#define _EXPRBASE_H
+
+#include "utils.h"
+#include "amount.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;
+
+template <typename ResultType>
+class expr_base_t
+{
+public:
+ typedef ResultType result_type;
+
+ typedef function<result_type (call_scope_t&)> func_t;
+
+protected:
+ scope_t * context;
+ string str;
+ bool compiled;
+
+ virtual result_type real_calc(scope_t& scope) = 0;
+
+public:
+ expr_base_t(const expr_base_t& other)
+ : context(other.context), str(other.str), compiled(false) {
+ TRACE_CTOR(expr_base_t, "copy");
+ }
+ expr_base_t(scope_t * _context = NULL)
+ : context(_context), compiled(false)
+ {
+ TRACE_CTOR(expr_base_t, "scope_t *");
+ }
+ virtual ~expr_base_t() {
+ TRACE_DTOR(expr_base_t);
+ }
+
+ expr_base_t& operator=(const expr_base_t& _expr) {
+ if (this != &_expr) {
+ str = _expr.str;
+ context = _expr.context;
+ compiled = _expr.compiled;
+ }
+ return *this;
+ }
+ expr_base_t& operator=(const string& _expr) {
+ parse(_expr);
+ return *this;
+ }
+
+ virtual operator bool() const throw() {
+ return ! str.empty();
+ }
+
+ virtual string text() {
+ return str;
+ }
+ void set_text(const string& txt) {
+ str = txt;
+ compiled = false;
+ }
+
+ void parse(const string& str, const parse_flags_t& flags = PARSE_DEFAULT) {
+ std::istringstream stream(str);
+ return parse(stream, flags, str);
+ }
+ virtual void parse(std::istream&,
+ const parse_flags_t& = PARSE_DEFAULT,
+ const optional<string>& original_string = none) {
+ set_text(original_string ? *original_string : "<stream>");
+ }
+
+ void mark_uncompiled() {
+ compiled = false;
+ }
+
+ void recompile(scope_t& scope) {
+ compiled = false;
+ compile(scope);
+ }
+
+ virtual void compile(scope_t& scope) {
+ if (! compiled) {
+ // Derived classes need to do something here.
+ context = &scope;
+ compiled = true;
+ }
+ }
+
+ result_type operator()(scope_t& scope) {
+ return calc(scope);
+ }
+
+ result_type calc(scope_t& scope)
+ {
+ if (! compiled) {
+ if (SHOW_DEBUG("expr.compile")) {
+ DEBUG("expr.compile", "Before compilation:");
+ dump(*_log_stream);
+ }
+
+ compile(scope);
+
+ if (SHOW_DEBUG("expr.compile")) {
+ DEBUG("expr.compile", "After compilation:");
+ dump(*_log_stream);
+ }
+ }
+
+ return real_calc(scope);
+ }
+
+ result_type calc() {
+ assert(context);
+ return calc(*context);
+ }
+
+ scope_t * get_context() {
+ return context;
+ }
+ void set_context(scope_t * scope) {
+ context = scope;
+ }
+
+ virtual string context_to_str() const {
+ return empty_string;
+ }
+
+ string print_to_str() const {
+ std::ostringstream out;
+ print(out);
+ return out.str();
+ }
+ string dump_to_str() const {
+ std::ostringstream out;
+ dump(out);
+ return out.str();
+ }
+ string preview_to_str(scope_t& scope) const {
+ std::ostringstream out;
+ preview(out);
+ return out.str();
+ }
+
+ virtual void print(std::ostream&) const {}
+ virtual void dump(std::ostream&) const {}
+
+ result_type preview(std::ostream& out, scope_t& scope) const {
+ out << _("--- Input expression ---") << std::endl;
+ out << text() << std::endl;
+
+ out << std::endl << _("--- Text as parsed ---") << std::endl;
+ print(out);
+ out << std::endl;
+
+ out << std::endl << _("--- Expression tree ---") << std::endl;
+ dump(out);
+
+ out << std::endl << _("--- Compiled tree ---") << std::endl;
+ compile(scope);
+ dump(out);
+
+ out << std::endl << _("--- Result value ---") << std::endl;
+ return calc();
+ }
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & context;
+ ar & str;
+ if (Archive::is_loading::value)
+ compiled = false;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+template <typename ResultType>
+std::ostream& operator<<(std::ostream& out,
+ const expr_base_t<ResultType>& expr) {
+ expr.print(out);
+ return out;
+}
+
+} // namespace ledger
+
+#endif // _EXPRBASE_H
diff --git a/src/filters.cc b/src/filters.cc
new file mode 100644
index 00000000..0084fac7
--- /dev/null
+++ b/src/filters.cc
@@ -0,0 +1,1055 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "filters.h"
+#include "iterators.h"
+#include "journal.h"
+#include "report.h"
+#include "compare.h"
+
+namespace ledger {
+
+pass_down_posts::pass_down_posts(post_handler_ptr handler,
+ posts_iterator& iter)
+ : item_handler<post_t>(handler)
+{
+ TRACE_CTOR(pass_down_posts, "post_handler_ptr, posts_iterator");
+
+ for (post_t * post = iter(); post; post = iter()) {
+ try {
+ item_handler<post_t>::operator()(*post);
+ }
+ catch (const std::exception& err) {
+ add_error_context(item_context(*post, _("While handling posting")));
+ throw;
+ }
+ }
+
+ item_handler<post_t>::flush();
+}
+
+void truncate_xacts::flush()
+{
+ if (! posts.size())
+ return;
+
+ xact_t * xact = (*posts.begin())->xact;
+
+ int l = 0;
+ foreach (post_t * post, posts)
+ if (xact != post->xact) {
+ l++;
+ xact = post->xact;
+ }
+ l++;
+
+ xact = (*posts.begin())->xact;
+
+ int i = 0;
+ foreach (post_t * post, posts) {
+ if (xact != post->xact) {
+ xact = post->xact;
+ 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<post_t>::operator()(*post);
+ }
+ posts.clear();
+
+ item_handler<post_t>::flush();
+}
+
+void truncate_xacts::operator()(post_t& post)
+{
+ if (last_xact != post.xact) {
+ if (last_xact)
+ xacts_seen++;
+ last_xact = post.xact;
+ }
+
+ if (tail_count == 0 && head_count > 0 &&
+ static_cast<int>(xacts_seen) >= head_count)
+ return;
+
+ posts.push_back(&post);
+}
+
+void sort_posts::post_accumulated_posts()
+{
+ std::stable_sort(posts.begin(), posts.end(),
+ compare_items<post_t>(sort_order));
+
+ foreach (post_t * post, posts) {
+ post->xdata().drop_flags(POST_EXT_SORT_CALC);
+ item_handler<post_t>::operator()(*post);
+ }
+
+ posts.clear();
+}
+
+namespace {
+ void split_string(const string& str, const char ch,
+ std::list<string>& strings)
+ {
+ const char * b = str.c_str();
+ for (const char * p = b; *p; p++) {
+ if (*p == ch) {
+ strings.push_back(string(b, p - b));
+ b = p + 1;
+ }
+ }
+ strings.push_back(string(b));
+ }
+
+ account_t * create_temp_account_from_path(std::list<string>& account_names,
+ temporaries_t& temps,
+ account_t * master)
+ {
+ account_t * new_account = NULL;
+ foreach (const string& name, account_names) {
+ if (new_account) {
+ new_account = new_account->find_account(name);
+ } else {
+ new_account = master->find_account(name, false);
+ if (! new_account)
+ new_account = &temps.create_account(name, master);
+ }
+ }
+
+ assert(new_account != NULL);
+ return new_account;
+ }
+}
+
+void anonymize_posts::operator()(post_t& post)
+{
+ SHA1 sha;
+ uint_least32_t message_digest[5];
+ bool copy_xact_details = false;
+
+ if (last_xact != post.xact) {
+ temps.copy_xact(*post.xact);
+ last_xact = post.xact;
+ copy_xact_details = true;
+ }
+ xact_t& xact = temps.last_xact();
+
+ if (copy_xact_details) {
+ xact.copy_details(*post.xact);
+
+ sha.Reset();
+ sha << post.xact->payee.c_str();
+ sha.Result(message_digest);
+
+ xact.payee = to_hex(message_digest);
+ xact.note = none;
+ }
+
+ std::list<string> account_names;
+
+ for (account_t * acct = post.account;
+ acct;
+ acct = acct->parent) {
+ sha.Reset();
+ sha << acct->name.c_str();
+ sha.Result(message_digest);
+
+ account_names.push_front(to_hex(message_digest));
+ }
+
+ account_t * new_account =
+ create_temp_account_from_path(account_names, temps, xact.journal->master);
+ post_t& temp = temps.copy_post(post, xact, new_account);
+ temp.note = none;
+
+ (*handler)(temp);
+}
+
+void calc_posts::operator()(post_t& post)
+{
+ post_t::xdata_t& xdata(post.xdata());
+
+ if (last_post) {
+ assert(last_post->has_xdata());
+ if (calc_running_total)
+ xdata.total = last_post->xdata().total;
+ xdata.count = last_post->xdata().count + 1;
+ } else {
+ xdata.count = 1;
+ }
+
+ post.add_to_value(xdata.visited_value, amount_expr);
+ xdata.add_flags(POST_EXT_VISITED);
+
+ account_t * acct = post.reported_account();
+ acct->xdata().add_flags(ACCOUNT_EXT_VISITED);
+
+ if (calc_running_total)
+ add_or_set_value(xdata.total, xdata.visited_value);
+
+ item_handler<post_t>::operator()(post);
+
+ last_post = &post;
+}
+
+namespace {
+ typedef function<void (post_t&)> post_functor_t;
+
+ void handle_value(const value_t& value,
+ account_t * account,
+ xact_t * xact,
+ temporaries_t& temps,
+ post_handler_ptr handler,
+ const date_t& date = date_t(),
+ const value_t& total = value_t(),
+ const bool direct_amount = false,
+ const bool mark_visited = false,
+ const optional<post_functor_t>& functor = none)
+ {
+ post_t& post = temps.create_post(*xact, account);
+ post.add_flags(ITEM_GENERATED);
+
+ // If the account for this post is all virtual, then report the post as
+ // such. This allows subtotal reports to show "(Account)" for accounts
+ // that contain only virtual posts.
+ if (account && account->has_xdata() &&
+ account->xdata().has_flags(ACCOUNT_EXT_AUTO_VIRTUALIZE)) {
+ if (! account->xdata().has_flags(ACCOUNT_EXT_HAS_NON_VIRTUALS)) {
+ post.add_flags(POST_VIRTUAL);
+ if (! account->xdata().has_flags(ACCOUNT_EXT_HAS_UNB_VIRTUALS))
+ post.add_flags(POST_MUST_BALANCE);
+ }
+ }
+
+ post_t::xdata_t& xdata(post.xdata());
+
+ if (is_valid(date))
+ xdata.date = date;
+
+ value_t temp(value);
+
+ switch (value.type()) {
+ case value_t::BOOLEAN:
+ case value_t::INTEGER:
+ temp.in_place_cast(value_t::AMOUNT);
+ // fall through...
+
+ case value_t::AMOUNT:
+ post.amount = temp.as_amount();
+ break;
+
+ case value_t::BALANCE:
+ case value_t::SEQUENCE:
+ xdata.compound_value = temp;
+ xdata.add_flags(POST_EXT_COMPOUND);
+ break;
+
+ case value_t::DATETIME:
+ case value_t::DATE:
+ default:
+ assert(false);
+ break;
+ }
+
+ if (! total.is_null())
+ xdata.total = total;
+
+ if (direct_amount)
+ xdata.add_flags(POST_EXT_DIRECT_AMT);
+
+ if (functor)
+ (*functor)(post);
+
+ DEBUG("filter.changed_value.rounding", "post.amount = " << post.amount);
+
+ (*handler)(post);
+
+ if (mark_visited) {
+ post.xdata().add_flags(POST_EXT_VISITED);
+ post.account->xdata().add_flags(ACCOUNT_EXT_VISITED);
+ }
+ }
+}
+
+void collapse_posts::report_subtotal()
+{
+ if (! count)
+ return;
+
+ std::size_t displayed_count = 0;
+ foreach (post_t * post, component_posts) {
+ if (only_predicate(*post) && display_predicate(*post))
+ displayed_count++;
+ }
+
+ if (displayed_count == 1) {
+ item_handler<post_t>::operator()(*last_post);
+ }
+ else if (only_collapse_if_zero && ! subtotal.is_zero()) {
+ foreach (post_t * post, component_posts)
+ item_handler<post_t>::operator()(*post);
+ }
+ else {
+ date_t earliest_date;
+
+ foreach (post_t * post, component_posts) {
+ date_t reported = post->date();
+ if (! is_valid(earliest_date) ||
+ reported < earliest_date)
+ earliest_date = reported;
+ }
+
+ xact_t& xact = temps.create_xact();
+ xact.payee = last_xact->payee;
+ xact._date = (is_valid(earliest_date) ?
+ earliest_date : last_xact->_date);
+ DEBUG("filter.collapse", "Pseudo-xact date = " << *xact._date);
+
+ handle_value(subtotal, &totals_account, &xact, temps, handler);
+ }
+
+ component_posts.clear();
+
+ last_xact = NULL;
+ last_post = NULL;
+ subtotal = 0L;
+ count = 0;
+}
+
+void collapse_posts::operator()(post_t& post)
+{
+ // If we've reached a new xact, report on the subtotal
+ // accumulated thus far.
+
+ if (last_xact != post.xact && count > 0)
+ report_subtotal();
+
+ post.add_to_value(subtotal, amount_expr);
+
+ component_posts.push_back(&post);
+
+ last_xact = post.xact;
+ last_post = &post;
+ count++;
+}
+
+void related_posts::flush()
+{
+ if (posts.size() > 0) {
+ foreach (post_t * post, posts) {
+ if (post->xact) {
+ foreach (post_t * r_post, post->xact->posts) {
+ post_t::xdata_t& xdata(r_post->xdata());
+ if (! xdata.has_flags(POST_EXT_HANDLED) &&
+ (! xdata.has_flags(POST_EXT_RECEIVED) ?
+ ! r_post->has_flags(ITEM_GENERATED | POST_VIRTUAL) :
+ also_matching)) {
+ xdata.add_flags(POST_EXT_HANDLED);
+ item_handler<post_t>::operator()(*r_post);
+ }
+ }
+ } else {
+ // This code should only be reachable from the "output"
+ // command, since that is the only command which attempts to
+ // output auto or period xacts.
+ post_t::xdata_t& xdata(post->xdata());
+ if (! xdata.has_flags(POST_EXT_HANDLED) &&
+ ! post->has_flags(ITEM_GENERATED)) {
+ xdata.add_flags(POST_EXT_HANDLED);
+ item_handler<post_t>::operator()(*post);
+ }
+ }
+ }
+ }
+
+ item_handler<post_t>::flush();
+}
+
+changed_value_posts::changed_value_posts(post_handler_ptr handler,
+ report_t& _report,
+ bool _for_accounts_report,
+ bool _show_unrealized)
+ : item_handler<post_t>(handler), report(_report),
+ for_accounts_report(_for_accounts_report),
+ show_unrealized(_show_unrealized), last_post(NULL),
+ revalued_account(temps.create_account(_("<Revalued>"))),
+ rounding_account(temps.create_account(_("<Rounding>")))
+{
+ TRACE_CTOR(changed_value_posts, "post_handler_ptr, report_t&, bool");
+
+ display_amount_expr = report.HANDLER(display_amount_).expr;
+ total_expr = (report.HANDLED(revalued_total_) ?
+ report.HANDLER(revalued_total_).expr :
+ report.HANDLER(display_total_).expr);
+ display_total_expr = report.HANDLER(display_total_).expr;
+ changed_values_only = report.HANDLED(revalued_only);
+
+ string gains_equity_account_name;
+ if (report.HANDLED(unrealized_gains_))
+ gains_equity_account_name = report.HANDLER(unrealized_gains_).str();
+ else
+ gains_equity_account_name = _("Equity:Unrealized Gains");
+ gains_equity_account =
+ report.session.journal->master->find_account(gains_equity_account_name);
+ gains_equity_account->add_flags(ACCOUNT_GENERATED);
+
+ string losses_equity_account_name;
+ if (report.HANDLED(unrealized_losses_))
+ losses_equity_account_name = report.HANDLER(unrealized_losses_).str();
+ else
+ losses_equity_account_name = _("Equity:Unrealized Losses");
+ losses_equity_account =
+ report.session.journal->master->find_account(losses_equity_account_name);
+ losses_equity_account->add_flags(ACCOUNT_GENERATED);
+}
+
+void changed_value_posts::flush()
+{
+ if (last_post && last_post->date() <= report.terminus.date()) {
+ output_revaluation(*last_post, report.terminus.date());
+ last_post = NULL;
+ }
+ item_handler<post_t>::flush();
+}
+
+void changed_value_posts::output_revaluation(post_t& post, const date_t& date)
+{
+ if (is_valid(date))
+ post.xdata().date = date;
+
+ value_t repriced_total;
+ try {
+ bind_scope_t bound_scope(report, post);
+ repriced_total = total_expr.calc(bound_scope);
+ }
+ catch (...) {
+ post.xdata().date = date_t();
+ throw;
+ }
+ post.xdata().date = date_t();
+
+ DEBUG("filter.changed_value",
+ "output_revaluation(last_balance) = " << last_total);
+ DEBUG("filter.changed_value",
+ "output_revaluation(repriced_total) = " << repriced_total);
+
+ if (! last_total.is_null()) {
+ if (value_t diff = repriced_total - last_total) {
+ DEBUG("filter.changed_value", "output_revaluation(strip(diff)) = "
+ << diff.strip_annotations(report.what_to_keep()));
+
+ xact_t& xact = temps.create_xact();
+ xact.payee = _("Commodities revalued");
+ xact._date = is_valid(date) ? date : post.date();
+
+ if (! for_accounts_report) {
+ handle_value
+ (/* value= */ diff,
+ /* account= */ &revalued_account,
+ /* xact= */ &xact,
+ /* temps= */ temps,
+ /* handler= */ handler,
+ /* date= */ *xact._date,
+ /* total= */ repriced_total,
+ /* direct_amount= */ false,
+ /* mark_visited= */ false,
+ /* functor= */ (optional<post_functor_t>
+ (bind(&changed_value_posts::output_rounding,
+ this, _1))));
+ }
+ else if (show_unrealized) {
+ handle_value
+ (/* value= */ - diff,
+ /* account= */ (diff < 0L ?
+ losses_equity_account :
+ gains_equity_account),
+ /* xact= */ &xact,
+ /* temps= */ temps,
+ /* handler= */ handler,
+ /* date= */ *xact._date,
+ /* total= */ value_t(),
+ /* direct_amount= */ false,
+ /* mark_visited= */ true);
+ }
+ }
+ }
+}
+
+void changed_value_posts::output_rounding(post_t& post)
+{
+ bind_scope_t bound_scope(report, post);
+ value_t new_display_total(display_total_expr.calc(bound_scope));
+
+ DEBUG("filter.changed_value.rounding",
+ "rounding.new_display_total = " << new_display_total);
+
+ if (! last_display_total.is_null()) {
+ if (value_t repriced_amount = display_amount_expr.calc(bound_scope)) {
+ DEBUG("filter.changed_value.rounding",
+ "rounding.repriced_amount = " << repriced_amount);
+
+ value_t precise_display_total(new_display_total.truncated() -
+ repriced_amount.truncated());
+
+ DEBUG("filter.changed_value.rounding",
+ "rounding.precise_display_total = " << precise_display_total);
+ DEBUG("filter.changed_value.rounding",
+ "rounding.last_display_total = " << last_display_total);
+
+ if (value_t diff = precise_display_total - last_display_total) {
+ DEBUG("filter.changed_value.rounding",
+ "rounding.diff = " << diff);
+
+ xact_t& xact = temps.create_xact();
+ xact.payee = _("Commodity rounding");
+ xact._date = post.date();
+
+ handle_value(diff, &rounding_account, &xact, temps, handler,
+ *xact._date, precise_display_total, true);
+ }
+ }
+ }
+ last_display_total = new_display_total;
+}
+
+void changed_value_posts::operator()(post_t& post)
+{
+ if (last_post)
+ output_revaluation(*last_post, post.date());
+
+ if (changed_values_only)
+ post.xdata().add_flags(POST_EXT_DISPLAYED);
+
+ if (! for_accounts_report)
+ output_rounding(post);
+
+ item_handler<post_t>::operator()(post);
+
+ bind_scope_t bound_scope(report, post);
+ last_total = total_expr.calc(bound_scope);
+ last_post = &post;
+}
+
+void subtotal_posts::report_subtotal(const char * spec_fmt,
+ const optional<date_interval_t>& interval)
+{
+ if (component_posts.empty())
+ return;
+
+ optional<date_t> range_start = interval ? interval->start : none;
+ optional<date_t> range_finish = interval ? interval->inclusive_end() : none;
+
+ if (! range_start || ! range_finish) {
+ foreach (post_t * post, component_posts) {
+ date_t date = post->date();
+ if (! range_start || date < *range_start)
+ range_start = date;
+ if (! range_finish || date > *range_finish)
+ range_finish = date;
+ }
+ }
+ component_posts.clear();
+
+ std::ostringstream out_date;
+ if (spec_fmt) {
+ out_date << format_date(*range_finish, FMT_CUSTOM, spec_fmt);
+ }
+ else if (date_format) {
+ out_date << "- " << format_date(*range_finish, FMT_CUSTOM,
+ date_format->c_str());
+ }
+ else {
+ out_date << "- " << format_date(*range_finish);
+ }
+
+ xact_t& xact = temps.create_xact();
+ xact.payee = out_date.str();
+ xact._date = *range_start;
+
+ foreach (values_map::value_type& pair, values)
+ handle_value(pair.second.value, pair.second.account, &xact, temps,
+ handler);
+
+ values.clear();
+}
+
+void subtotal_posts::operator()(post_t& post)
+{
+ component_posts.push_back(&post);
+
+ account_t * acct = post.reported_account();
+ assert(acct);
+
+ values_map::iterator i = values.find(acct->fullname());
+ if (i == values.end()) {
+ value_t temp;
+ post.add_to_value(temp, amount_expr);
+ std::pair<values_map::iterator, bool> result
+ = values.insert(values_pair(acct->fullname(), acct_value_t(acct, temp)));
+ assert(result.second);
+ } else {
+ post.add_to_value((*i).second.value, amount_expr);
+ }
+
+ // If the account for this post is all virtual, mark it as
+ // such, so that `handle_value' can show "(Account)" for accounts
+ // that contain only virtual posts.
+
+ post.reported_account()->xdata().add_flags(ACCOUNT_EXT_AUTO_VIRTUALIZE);
+
+ if (! post.has_flags(POST_VIRTUAL))
+ post.reported_account()->xdata().add_flags(ACCOUNT_EXT_HAS_NON_VIRTUALS);
+ else if (! post.has_flags(POST_MUST_BALANCE))
+ post.reported_account()->xdata().add_flags(ACCOUNT_EXT_HAS_UNB_VIRTUALS);
+}
+
+void interval_posts::report_subtotal(const date_interval_t& interval)
+{
+ if (last_post && interval) {
+ if (exact_periods)
+ subtotal_posts::report_subtotal();
+ else
+ subtotal_posts::report_subtotal(NULL, interval);
+ }
+
+ last_post = NULL;
+}
+
+void interval_posts::operator()(post_t& post)
+{
+ date_t date = post.date();
+
+ if (! interval.find_period(post.date()))
+ return;
+
+ if (interval.duration) {
+ if (last_interval && interval != last_interval) {
+ report_subtotal(last_interval);
+
+ if (generate_empty_posts) {
+ for (++last_interval; interval != last_interval; ++last_interval) {
+ // Generate a null posting, so the intervening periods can be
+ // seen when -E is used, or if the calculated amount ends up being
+ // non-zero
+ xact_t& null_xact = temps.create_xact();
+ null_xact._date = last_interval.inclusive_end();
+
+ post_t& null_post = temps.create_post(null_xact, &empty_account);
+ null_post.add_flags(POST_CALCULATED);
+ null_post.amount = 0L;
+
+ last_post = &null_post;
+ subtotal_posts::operator()(null_post);
+
+ report_subtotal(last_interval);
+ }
+ assert(interval == last_interval);
+ } else {
+ last_interval = interval;
+ }
+ } else {
+ last_interval = interval;
+ }
+ subtotal_posts::operator()(post);
+ } else {
+ item_handler<post_t>::operator()(post);
+ }
+
+ last_post = &post;
+}
+
+void posts_as_equity::report_subtotal()
+{
+ date_t finish;
+ foreach (post_t * post, component_posts) {
+ date_t date = post->date();
+ if (! is_valid(finish) || date > finish)
+ finish = date;
+ }
+ component_posts.clear();
+
+ xact_t& xact = temps.create_xact();
+ xact.payee = _("Opening Balances");
+ xact._date = finish;
+
+ value_t total = 0L;
+ foreach (values_map::value_type& pair, values) {
+ if (pair.second.value.is_balance()) {
+ foreach (balance_t::amounts_map::value_type amount_pair,
+ pair.second.value.as_balance().amounts)
+ handle_value(amount_pair.second, pair.second.account, &xact, temps,
+ handler);
+ } else {
+ handle_value(pair.second.value, pair.second.account, &xact, temps,
+ handler);
+ }
+ total += pair.second.value;
+ }
+ values.clear();
+
+ if (total.is_balance()) {
+ foreach (balance_t::amounts_map::value_type pair,
+ total.as_balance().amounts) {
+ post_t& balance_post = temps.create_post(xact, balance_account);
+ balance_post.amount = - pair.second;
+ (*handler)(balance_post);
+ }
+ } else {
+ post_t& balance_post = temps.create_post(xact, balance_account);
+ balance_post.amount = - total.to_amount();
+ (*handler)(balance_post);
+ }
+}
+
+void by_payee_posts::flush()
+{
+ foreach (payee_subtotals_map::value_type& pair, payee_subtotals)
+ pair.second->report_subtotal(pair.first.c_str());
+
+ item_handler<post_t>::flush();
+
+ payee_subtotals.clear();
+}
+
+void by_payee_posts::operator()(post_t& post)
+{
+ payee_subtotals_map::iterator i = payee_subtotals.find(post.xact->payee);
+ if (i == payee_subtotals.end()) {
+ payee_subtotals_pair
+ temp(post.xact->payee,
+ shared_ptr<subtotal_posts>(new subtotal_posts(handler, amount_expr)));
+ std::pair<payee_subtotals_map::iterator, bool> result
+ = payee_subtotals.insert(temp);
+
+ assert(result.second);
+ if (! result.second)
+ return;
+ i = result.first;
+ }
+
+ (*(*i).second)(post);
+}
+
+void transfer_details::operator()(post_t& post)
+{
+ xact_t& xact = temps.copy_xact(*post.xact);
+ xact._date = post.date();
+
+ post_t& temp = temps.copy_post(post, xact);
+ temp.set_state(post.state());
+
+ bind_scope_t bound_scope(scope, temp);
+ value_t substitute(expr.calc(bound_scope));
+
+ switch (which_element) {
+ case SET_DATE:
+ temp.xdata().date = substitute.to_date();
+ break;
+
+ case SET_ACCOUNT: {
+ std::list<string> account_names;
+ temp.account->remove_post(&temp);
+ split_string(substitute.to_string(), ':', account_names);
+ temp.account = create_temp_account_from_path(account_names, temps,
+ xact.journal->master);
+ temp.account->add_post(&temp);
+ break;
+ }
+
+ case SET_PAYEE:
+ xact.payee = substitute.to_string();
+ break;
+
+ default:
+ assert(false);
+ break;
+ }
+
+ item_handler<post_t>::operator()(temp);
+}
+
+void dow_posts::flush()
+{
+ for (int i = 0; i < 7; i++) {
+ foreach (post_t * post, days_of_the_week[i])
+ subtotal_posts::operator()(*post);
+ subtotal_posts::report_subtotal("%As");
+ days_of_the_week[i].clear();
+ }
+
+ subtotal_posts::flush();
+}
+
+void generate_posts::add_period_xacts(period_xacts_list& period_xacts)
+{
+ foreach (period_xact_t * xact, period_xacts)
+ foreach (post_t * post, xact->posts)
+ add_post(xact->period, *post);
+}
+
+void generate_posts::add_post(const date_interval_t& period, post_t& post)
+{
+ pending_posts.push_back(pending_posts_pair(period, &post));
+}
+
+void budget_posts::report_budget_items(const date_t& date)
+{
+ if (pending_posts.size() == 0)
+ return;
+
+ bool reported;
+ do {
+ reported = false;
+ foreach (pending_posts_list::value_type& pair, pending_posts) {
+ optional<date_t> begin = pair.first.start;
+ if (! begin) {
+ if (! pair.first.find_period(date))
+ throw_(std::runtime_error, _("Something odd has happened"));
+ begin = pair.first.start;
+ }
+ assert(begin);
+
+ if (*begin <= date &&
+ (! pair.first.finish || *begin < *pair.first.finish)) {
+ post_t& post = *pair.second;
+
+ DEBUG("budget.generate", "Reporting budget for "
+ << post.reported_account()->fullname());
+
+ xact_t& xact = temps.create_xact();
+ xact.payee = _("Budget transaction");
+ xact._date = begin;
+
+ post_t& temp = temps.copy_post(post, xact);
+ temp.amount.in_place_negate();
+
+ if (flags & BUDGET_WRAP_VALUES) {
+ value_t seq;
+ seq.push_back(0L);
+ seq.push_back(temp.amount);
+
+ temp.xdata().compound_value = seq;
+ temp.xdata().add_flags(POST_EXT_COMPOUND);
+ }
+
+ ++pair.first;
+ begin = *pair.first.start;
+
+ item_handler<post_t>::operator()(temp);
+
+ reported = true;
+ }
+ }
+ } while (reported);
+}
+
+void budget_posts::operator()(post_t& post)
+{
+ bool post_in_budget = false;
+
+ foreach (pending_posts_list::value_type& pair, pending_posts) {
+ for (account_t * acct = post.reported_account();
+ acct;
+ acct = acct->parent) {
+ if (acct == (*pair.second).reported_account()) {
+ post_in_budget = true;
+ // Report the post as if it had occurred in the parent account.
+ if (post.reported_account() != acct)
+ post.set_reported_account(acct);
+ goto handle;
+ }
+ }
+ }
+
+ handle:
+ if (post_in_budget && flags & BUDGET_BUDGETED) {
+ report_budget_items(post.date());
+ item_handler<post_t>::operator()(post);
+ }
+ else if (! post_in_budget && flags & BUDGET_UNBUDGETED) {
+ item_handler<post_t>::operator()(post);
+ }
+}
+
+void forecast_posts::add_post(const date_interval_t& period, post_t& post)
+{
+ generate_posts::add_post(period, post);
+
+ // Advance the period's interval until it is at or beyond the current date.
+ date_interval_t& i = pending_posts.back().first;
+ if (! i.start) {
+ if (! i.find_period(CURRENT_DATE()))
+ throw_(std::runtime_error, _("Something odd has happened"));
+ ++i;
+ } else {
+ while (*i.start < CURRENT_DATE())
+ ++i;
+ }
+}
+
+void forecast_posts::flush()
+{
+ posts_list passed;
+ date_t last = CURRENT_DATE();
+
+ // If there are period transactions to apply in a continuing series until
+ // the forecast condition is met, generate those transactions now. Note
+ // that no matter what, we abandon forecasting beyond the next 5 years.
+ //
+ // It works like this:
+ //
+ // Earlier, in forecast_posts::add_period_xacts, we cut up all the periodic
+ // transactions into their components postings, so that we have N "periodic
+ // postings". For example, if the user had this:
+ //
+ // ~ daily
+ // Expenses:Food $10
+ // Expenses:Auto:Gas $20
+ // ~ monthly
+ // Expenses:Food $100
+ // Expenses:Auto:Gas $200
+ //
+ // We now have 4 periodic postings in `pending_posts'.
+ //
+ // Each periodic postings gets its own copy of its parent transaction's
+ // period, which is modified as we go. This is found in the second member
+ // of the pending_posts_list for each posting.
+ //
+ // The algorithm below works by iterating through the N periodic postings
+ // over and over, until each of them mets the termination critera for the
+ // forecast and is removed from the set.
+
+ while (pending_posts.size() > 0) {
+ // At each step through the loop, we find the first periodic posting whose
+ // period contains the earliest starting date.
+ pending_posts_list::iterator least = pending_posts.begin();
+ for (pending_posts_list::iterator i = ++pending_posts.begin();
+ i != pending_posts.end();
+ i++) {
+ if (*(*i).first.start < *(*least).first.start)
+ least = i;
+ }
+
+ date_t& begin = *(*least).first.start;
+ if ((*least).first.finish)
+ assert(begin < *(*least).first.finish);
+
+ // If the next date in the series for this periodic posting is more than 5
+ // years beyond the last valid post we generated, drop it from further
+ // consideration.
+ date_t next = *(*least).first.next;
+ assert(next > begin);
+
+ if (static_cast<std::size_t>((next - last).days()) >
+ static_cast<std::size_t>(365U) * forecast_years) {
+ DEBUG("filters.forecast",
+ "Forecast transaction exceeds " << forecast_years
+ << " years beyond today");
+ pending_posts.erase(least);
+ continue;
+ }
+
+ begin = next;
+ ++(*least).first;
+
+ // `post' refers to the posting defined in the period transaction. We
+ // make a copy of it within a temporary transaction with the payee
+ // "Forecast transaction".
+ post_t& post = *(*least).second;
+ xact_t& xact = temps.create_xact();
+ xact.payee = _("Forecast transaction");
+ xact._date = begin;
+ post_t& temp = temps.copy_post(post, xact);
+
+ // Submit the generated posting
+ DEBUG("filters.forecast",
+ "Forecast transaction: " << temp.date()
+ << " " << temp.account->fullname()
+ << " " << temp.amount);
+ item_handler<post_t>::operator()(temp);
+
+ // If the generated posting matches the user's report query, check whether
+ // it also fails to match the continuation condition for the forecast. If
+ // it does, drop this periodic posting from consideration.
+ if (temp.has_xdata() && temp.xdata().has_flags(POST_EXT_MATCHES)) {
+ DEBUG("filters.forecast", " matches report query");
+ bind_scope_t bound_scope(context, temp);
+ if (! pred(bound_scope)) {
+ DEBUG("filters.forecast", " fails to match continuation criteria");
+ pending_posts.erase(least);
+ continue;
+ }
+ }
+ }
+
+ item_handler<post_t>::flush();
+}
+
+pass_down_accounts::pass_down_accounts(acct_handler_ptr handler,
+ accounts_iterator& iter,
+ const optional<predicate_t>& _pred,
+ const optional<scope_t&>& _context)
+ : item_handler<account_t>(handler), pred(_pred), context(_context)
+{
+ TRACE_CTOR(pass_down_accounts, "acct_handler_ptr, accounts_iterator, ...");
+
+ for (account_t * account = iter(); account; account = iter()) {
+ if (! pred) {
+ item_handler<account_t>::operator()(*account);
+ } else {
+ bind_scope_t bound_scope(*context, *account);
+ if ((*pred)(bound_scope))
+ item_handler<account_t>::operator()(*account);
+ }
+ }
+
+ item_handler<account_t>::flush();
+}
+
+} // namespace ledger
diff --git a/src/filters.h b/src/filters.h
new file mode 100644
index 00000000..92148dbe
--- /dev/null
+++ b/src/filters.h
@@ -0,0 +1,721 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup report
+ */
+
+/**
+ * @file filters.h
+ * @author John Wiegley
+ *
+ * @ingroup report
+ */
+#ifndef _FILTERS_H
+#define _FILTERS_H
+
+#include "chain.h"
+#include "xact.h"
+#include "post.h"
+#include "account.h"
+#include "temps.h"
+
+namespace ledger {
+
+//////////////////////////////////////////////////////////////////////
+//
+// Posting filters
+//
+
+class ignore_posts : public item_handler<post_t>
+{
+public:
+ virtual void operator()(post_t&) {}
+};
+
+class collect_posts : public item_handler<post_t>
+{
+public:
+ std::vector<post_t *> posts;
+
+ collect_posts() : item_handler<post_t>() {
+ TRACE_CTOR(collect_posts, "");
+ }
+ virtual ~collect_posts() {
+ TRACE_DTOR(collect_posts);
+ }
+
+ std::size_t length() const {
+ return posts.size();
+ }
+
+ std::vector<post_t *>::iterator begin() {
+ return posts.begin();
+ }
+ std::vector<post_t *>::iterator end() {
+ return posts.end();
+ }
+
+ virtual void flush() {}
+ virtual void operator()(post_t& post) {
+ posts.push_back(&post);
+ }
+};
+
+class posts_iterator;
+
+class pass_down_posts : public item_handler<post_t>
+{
+ pass_down_posts();
+
+public:
+ pass_down_posts(post_handler_ptr handler, posts_iterator& iter);
+
+ virtual ~pass_down_posts() {
+ TRACE_DTOR(pass_down_posts);
+ }
+};
+
+class push_to_posts_list : public item_handler<post_t>
+{
+ push_to_posts_list();
+
+public:
+ posts_list& posts;
+
+ push_to_posts_list(posts_list& _posts) : posts(_posts) {
+ TRACE_CTOR(push_to_posts_list, "posts_list&");
+ }
+ virtual ~push_to_posts_list() {
+ TRACE_DTOR(push_to_posts_list);
+ }
+
+ virtual void operator()(post_t& post) {
+ posts.push_back(&post);
+ }
+};
+
+class truncate_xacts : public item_handler<post_t>
+{
+ int head_count;
+ int tail_count;
+
+ posts_list posts;
+ std::size_t xacts_seen;
+ xact_t * last_xact;
+
+ truncate_xacts();
+
+public:
+ truncate_xacts(post_handler_ptr handler,
+ int _head_count, int _tail_count)
+ : item_handler<post_t>(handler),
+ head_count(_head_count), tail_count(_tail_count),
+ xacts_seen(0), last_xact(NULL) {
+ TRACE_CTOR(truncate_xacts, "post_handler_ptr, int, int");
+ }
+ virtual ~truncate_xacts() {
+ TRACE_DTOR(truncate_xacts);
+ }
+
+ virtual void flush();
+ virtual void operator()(post_t& post);
+};
+
+class sort_posts : public item_handler<post_t>
+{
+ typedef std::deque<post_t *> posts_deque;
+
+ posts_deque posts;
+ const expr_t sort_order;
+
+ sort_posts();
+
+public:
+ sort_posts(post_handler_ptr handler,
+ const expr_t& _sort_order)
+ : item_handler<post_t>(handler),
+ sort_order(_sort_order) {
+ TRACE_CTOR(sort_posts,
+ "post_handler_ptr, const value_expr&");
+ }
+ sort_posts(post_handler_ptr handler,
+ const string& _sort_order)
+ : item_handler<post_t>(handler),
+ sort_order(_sort_order) {
+ TRACE_CTOR(sort_posts,
+ "post_handler_ptr, const string&");
+ }
+ virtual ~sort_posts() {
+ TRACE_DTOR(sort_posts);
+ }
+
+ virtual void post_accumulated_posts();
+
+ virtual void flush() {
+ post_accumulated_posts();
+ item_handler<post_t>::flush();
+ }
+
+ virtual void operator()(post_t& post) {
+ posts.push_back(&post);
+ }
+};
+
+class sort_xacts : public item_handler<post_t>
+{
+ sort_posts sorter;
+ xact_t * last_xact;
+
+ sort_xacts();
+
+public:
+ sort_xacts(post_handler_ptr handler,
+ const expr_t& _sort_order)
+ : sorter(handler, _sort_order) {
+ TRACE_CTOR(sort_xacts,
+ "post_handler_ptr, const value_expr&");
+ }
+ sort_xacts(post_handler_ptr handler,
+ const string& _sort_order)
+ : sorter(handler, _sort_order) {
+ TRACE_CTOR(sort_xacts,
+ "post_handler_ptr, const string&");
+ }
+ virtual ~sort_xacts() {
+ TRACE_DTOR(sort_xacts);
+ }
+
+ virtual void flush() {
+ sorter.flush();
+ item_handler<post_t>::flush();
+ }
+
+ virtual void operator()(post_t& post) {
+ if (last_xact && post.xact != last_xact)
+ sorter.post_accumulated_posts();
+
+ sorter(post);
+
+ last_xact = post.xact;
+ }
+};
+
+class filter_posts : public item_handler<post_t>
+{
+ predicate_t pred;
+ scope_t& context;
+
+ filter_posts();
+
+public:
+ filter_posts(post_handler_ptr handler,
+ const predicate_t& predicate,
+ scope_t& _context)
+ : item_handler<post_t>(handler), pred(predicate), context(_context) {
+ TRACE_CTOR(filter_posts, "post_handler_ptr, predicate_t, scope_t&");
+ }
+ virtual ~filter_posts() {
+ TRACE_DTOR(filter_posts);
+ }
+
+ virtual void operator()(post_t& post) {
+ bind_scope_t bound_scope(context, post);
+ if (pred(bound_scope)) {
+ post.xdata().add_flags(POST_EXT_MATCHES);
+ (*handler)(post);
+ }
+ }
+};
+
+class anonymize_posts : public item_handler<post_t>
+{
+ temporaries_t temps;
+ xact_t * last_xact;
+
+ anonymize_posts();
+
+public:
+ anonymize_posts(post_handler_ptr handler)
+ : item_handler<post_t>(handler), last_xact(NULL) {
+ TRACE_CTOR(anonymize_posts, "post_handler_ptr");
+ }
+ virtual ~anonymize_posts() {
+ TRACE_DTOR(anonymize_posts);
+ }
+
+ virtual void operator()(post_t& post);
+};
+
+class calc_posts : public item_handler<post_t>
+{
+ post_t * last_post;
+ expr_t& amount_expr;
+ bool calc_running_total;
+
+ calc_posts();
+
+public:
+ calc_posts(post_handler_ptr handler,
+ expr_t& _amount_expr,
+ bool _calc_running_total = false)
+ : item_handler<post_t>(handler), last_post(NULL),
+ amount_expr(_amount_expr), calc_running_total(_calc_running_total) {
+ TRACE_CTOR(calc_posts, "post_handler_ptr, expr_t&, bool");
+ }
+ virtual ~calc_posts() {
+ TRACE_DTOR(calc_posts);
+ }
+
+ virtual void operator()(post_t& post);
+};
+
+class collapse_posts : public item_handler<post_t>
+{
+ expr_t& amount_expr;
+ predicate_t display_predicate;
+ predicate_t only_predicate;
+ value_t subtotal;
+ std::size_t count;
+ xact_t * last_xact;
+ post_t * last_post;
+ temporaries_t temps;
+ account_t& totals_account;
+ bool only_collapse_if_zero;
+ std::list<post_t *> component_posts;
+
+ collapse_posts();
+
+public:
+ collapse_posts(post_handler_ptr handler,
+ expr_t& _amount_expr,
+ predicate_t _display_predicate,
+ predicate_t _only_predicate,
+ bool _only_collapse_if_zero = false)
+ : item_handler<post_t>(handler), amount_expr(_amount_expr),
+ display_predicate(_display_predicate),
+ only_predicate(_only_predicate), count(0),
+ last_xact(NULL), last_post(NULL),
+ totals_account(temps.create_account(_("<Total>"))),
+ only_collapse_if_zero(_only_collapse_if_zero) {
+ TRACE_CTOR(collapse_posts, "post_handler_ptr");
+ }
+ virtual ~collapse_posts() {
+ TRACE_DTOR(collapse_posts);
+ }
+
+ virtual void flush() {
+ report_subtotal();
+ item_handler<post_t>::flush();
+ }
+
+ void report_subtotal();
+
+ virtual void operator()(post_t& post);
+};
+
+class related_posts : public item_handler<post_t>
+{
+ posts_list posts;
+ bool also_matching;
+
+ related_posts();
+
+public:
+ related_posts(post_handler_ptr handler,
+ const bool _also_matching = false)
+ : item_handler<post_t>(handler),
+ also_matching(_also_matching) {
+ TRACE_CTOR(related_posts,
+ "post_handler_ptr, const bool");
+ }
+ virtual ~related_posts() throw() {
+ TRACE_DTOR(related_posts);
+ }
+
+ virtual void flush();
+ virtual void operator()(post_t& post) {
+ post.xdata().add_flags(POST_EXT_RECEIVED);
+ posts.push_back(&post);
+ }
+};
+
+class changed_value_posts : public item_handler<post_t>
+{
+ // This filter requires that calc_posts be used at some point
+ // later in the chain.
+
+ expr_t display_amount_expr;
+ expr_t total_expr;
+ expr_t display_total_expr;
+ report_t& report;
+ bool changed_values_only;
+ bool for_accounts_report;
+ bool show_unrealized;
+ post_t * last_post;
+ value_t last_total;
+ value_t last_display_total;
+ temporaries_t temps;
+ account_t& revalued_account;
+ account_t& rounding_account;
+ account_t * gains_equity_account;
+ account_t * losses_equity_account;
+
+ changed_value_posts();
+
+public:
+ changed_value_posts(post_handler_ptr handler,
+ report_t& _report,
+ bool _for_accounts_report,
+ bool _show_unrealized);
+
+ virtual ~changed_value_posts() {
+ TRACE_DTOR(changed_value_posts);
+ }
+
+ virtual void flush();
+
+ void output_revaluation(post_t& post, const date_t& current);
+ void output_rounding(post_t& post);
+
+ virtual void operator()(post_t& post);
+};
+
+class subtotal_posts : public item_handler<post_t>
+{
+ subtotal_posts();
+
+protected:
+ class acct_value_t
+ {
+ acct_value_t();
+
+ public:
+ account_t * account;
+ value_t value;
+
+ acct_value_t(account_t * a) : account(a) {
+ TRACE_CTOR(acct_value_t, "account_t *");
+ }
+ acct_value_t(account_t * a, value_t& v) : account(a), value(v) {
+ TRACE_CTOR(acct_value_t, "account_t *, value_t&");
+ }
+ acct_value_t(const acct_value_t& av)
+ : account(av.account), value(av.value) {
+ 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;
+
+protected:
+ expr_t& amount_expr;
+ values_map values;
+ optional<string> date_format;
+ temporaries_t temps;
+ std::list<post_t *> component_posts;
+
+public:
+ subtotal_posts(post_handler_ptr handler, expr_t& _amount_expr,
+ const optional<string>& _date_format = none)
+ : item_handler<post_t>(handler), amount_expr(_amount_expr),
+ date_format(_date_format) {
+ TRACE_CTOR(subtotal_posts,
+ "post_handler_ptr, expr_t&, const optional<string>&");
+ }
+ virtual ~subtotal_posts() {
+ TRACE_DTOR(subtotal_posts);
+ }
+
+ void report_subtotal(const char * spec_fmt = NULL,
+ const optional<date_interval_t>& interval = none);
+
+ virtual void flush() {
+ if (values.size() > 0)
+ report_subtotal();
+ item_handler<post_t>::flush();
+ }
+ virtual void operator()(post_t& post);
+};
+
+class interval_posts : public subtotal_posts
+{
+ date_interval_t interval;
+ date_interval_t last_interval;
+ post_t * last_post;
+ account_t& empty_account;
+ bool exact_periods;
+ bool generate_empty_posts;
+
+ interval_posts();
+
+public:
+
+ interval_posts(post_handler_ptr _handler,
+ expr_t& amount_expr,
+ const date_interval_t& _interval,
+ bool _exact_periods = false,
+ bool _generate_empty_posts = false)
+ : subtotal_posts(_handler, amount_expr), interval(_interval),
+ last_post(NULL), empty_account(temps.create_account(_("<None>"))),
+ exact_periods(_exact_periods),
+ generate_empty_posts(_generate_empty_posts) {
+ TRACE_CTOR(interval_posts,
+ "post_handler_ptr, expr_t&, date_interval_t, bool, bool");
+ }
+ virtual ~interval_posts() throw() {
+ TRACE_DTOR(interval_posts);
+ }
+
+ void report_subtotal(const date_interval_t& interval);
+
+ virtual void flush() {
+ if (last_post && interval.duration) {
+ if (interval.is_valid())
+ report_subtotal(interval);
+ subtotal_posts::flush();
+ }
+ }
+ virtual void operator()(post_t& post);
+};
+
+class posts_as_equity : public subtotal_posts
+{
+ post_t * last_post;
+ account_t& equity_account;
+ account_t * balance_account;
+
+ posts_as_equity();
+
+public:
+ posts_as_equity(post_handler_ptr _handler, expr_t& amount_expr)
+ : subtotal_posts(_handler, amount_expr),
+ equity_account(temps.create_account(_("Equity"))) {
+ TRACE_CTOR(posts_as_equity, "post_handler_ptr, expr_t&");
+ balance_account = equity_account.find_account(_("Opening Balances"));
+ }
+ virtual ~posts_as_equity() throw() {
+ TRACE_DTOR(posts_as_equity);
+ }
+
+ void report_subtotal();
+
+ virtual void flush() {
+ report_subtotal();
+ subtotal_posts::flush();
+ }
+};
+
+class by_payee_posts : public item_handler<post_t>
+{
+ typedef std::map<string, shared_ptr<subtotal_posts> > payee_subtotals_map;
+ typedef std::pair<string, shared_ptr<subtotal_posts> > payee_subtotals_pair;
+
+ expr_t& amount_expr;
+ payee_subtotals_map payee_subtotals;
+
+ by_payee_posts();
+
+ public:
+ by_payee_posts(post_handler_ptr handler, expr_t& _amount_expr)
+ : item_handler<post_t>(handler), amount_expr(_amount_expr) {
+ TRACE_CTOR(by_payee_posts, "post_handler_ptr, expr_t&");
+ }
+ virtual ~by_payee_posts() {
+ TRACE_DTOR(by_payee_posts);
+ }
+
+ virtual void flush();
+ virtual void operator()(post_t& post);
+};
+
+class transfer_details : public item_handler<post_t>
+{
+ account_t * master;
+ expr_t expr;
+ scope_t& scope;
+ temporaries_t temps;
+
+ transfer_details();
+
+public:
+ enum element_t {
+ SET_DATE,
+ SET_ACCOUNT,
+ SET_PAYEE
+ } which_element;
+
+ transfer_details(post_handler_ptr handler,
+ element_t _which_element,
+ account_t * _master,
+ const expr_t& _expr,
+ scope_t& _scope)
+ : item_handler<post_t>(handler), master(_master),
+ expr(_expr), scope(_scope), which_element(_which_element) {
+ TRACE_CTOR(transfer_details,
+ "post_handler_ptr, element_t, account_t *, expr_t, scope_t&");
+ }
+ virtual ~transfer_details() {
+ TRACE_DTOR(transfer_details);
+ }
+
+ virtual void operator()(post_t& post);
+};
+
+class dow_posts : public subtotal_posts
+{
+ posts_list days_of_the_week[7];
+
+ dow_posts();
+
+public:
+ dow_posts(post_handler_ptr handler, expr_t& amount_expr)
+ : subtotal_posts(handler, amount_expr) {
+ TRACE_CTOR(dow_posts, "post_handler_ptr, bool");
+ }
+ virtual ~dow_posts() throw() {
+ TRACE_DTOR(dow_posts);
+ }
+
+ virtual void flush();
+ virtual void operator()(post_t& post) {
+ days_of_the_week[post.date().day_of_week()].push_back(&post);
+ }
+};
+
+class generate_posts : public item_handler<post_t>
+{
+ generate_posts();
+
+protected:
+ typedef std::pair<date_interval_t, post_t *> pending_posts_pair;
+ typedef std::list<pending_posts_pair> pending_posts_list;
+
+ pending_posts_list pending_posts;
+ temporaries_t temps;
+
+public:
+ generate_posts(post_handler_ptr handler)
+ : item_handler<post_t>(handler) {
+ TRACE_CTOR(generate_posts, "post_handler_ptr");
+ }
+
+ virtual ~generate_posts() {
+ TRACE_DTOR(generate_posts);
+ }
+
+ void add_period_xacts(period_xacts_list& period_xacts);
+
+ virtual void add_post(const date_interval_t& period, post_t& post);
+};
+
+class budget_posts : public generate_posts
+{
+#define BUDGET_NO_BUDGET 0x00
+#define BUDGET_BUDGETED 0x01
+#define BUDGET_UNBUDGETED 0x02
+#define BUDGET_WRAP_VALUES 0x04
+
+ uint_least8_t flags;
+
+ budget_posts();
+
+public:
+ budget_posts(post_handler_ptr handler,
+ uint_least8_t _flags = BUDGET_BUDGETED)
+ : generate_posts(handler), flags(_flags) {
+ TRACE_CTOR(budget_posts, "post_handler_ptr, uint_least8_t");
+ }
+ virtual ~budget_posts() throw() {
+ TRACE_DTOR(budget_posts);
+ }
+
+ void report_budget_items(const date_t& date);
+
+ virtual void operator()(post_t& post);
+};
+
+class forecast_posts : public generate_posts
+{
+ predicate_t pred;
+ scope_t& context;
+ const std::size_t forecast_years;
+
+ public:
+ forecast_posts(post_handler_ptr handler,
+ const predicate_t& predicate,
+ scope_t& _context,
+ const std::size_t _forecast_years)
+ : generate_posts(handler), pred(predicate), context(_context),
+ forecast_years(_forecast_years) {
+ TRACE_CTOR(forecast_posts,
+ "post_handler_ptr, predicate_t, scope_t&, std::size_t");
+ }
+ virtual ~forecast_posts() throw() {
+ TRACE_DTOR(forecast_posts);
+ }
+
+ virtual void add_post(const date_interval_t& period, post_t& post);
+ virtual void flush();
+};
+
+//////////////////////////////////////////////////////////////////////
+//
+// Account filters
+//
+
+class accounts_iterator;
+
+class pass_down_accounts : public item_handler<account_t>
+{
+ pass_down_accounts();
+
+ optional<predicate_t> pred;
+ optional<scope_t&> context;
+
+public:
+ pass_down_accounts(acct_handler_ptr handler,
+ accounts_iterator& iter,
+ const optional<predicate_t>& _pred = none,
+ const optional<scope_t&>& _context = none);
+
+ 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..69e40e4b
--- /dev/null
+++ b/src/flags.h
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup util
+ */
+
+/**
+ * @file flags.h
+ * @author John Wiegley
+ *
+ * @ingroup util
+ */
+#ifndef _FLAGS_H
+#define _FLAGS_H
+
+
+template <typename T = boost::uint_least8_t, typename U = T>
+class supports_flags
+{
+public:
+ typedef T flags_t;
+
+protected:
+ flags_t _flags;
+
+public:
+ supports_flags() : _flags(static_cast<T>(0)) {
+ TRACE_CTOR(supports_flags, "");
+ }
+ supports_flags(const supports_flags& arg) : _flags(arg._flags) {
+ TRACE_CTOR(supports_flags, "copy");
+ }
+ supports_flags(const flags_t& arg) : _flags(arg) {
+ TRACE_CTOR(supports_flags, "const flags_t&");
+ }
+ ~supports_flags() throw() {
+ TRACE_DTOR(supports_flags);
+ }
+
+ supports_flags& operator=(const supports_flags& other) {
+ _flags = other._flags;
+ return *this;
+ }
+
+ 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 = static_cast<T>(0);
+ }
+ void add_flags(const flags_t arg) {
+ _flags = static_cast<T>(static_cast<U>(_flags) | static_cast<U>(arg));
+ }
+ void drop_flags(const flags_t arg) {
+ _flags = static_cast<T>(static_cast<U>(_flags) & static_cast<U>(~arg));
+ }
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */)
+ {
+ ar & _flags;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+template <typename T = boost::uint_least8_t, typename U = T>
+class basic_flags_t : public supports_flags<T, U>
+{
+public:
+ basic_flags_t() {
+ TRACE_CTOR(basic_flags_t, "");
+ }
+ basic_flags_t(const T& bits) {
+ TRACE_CTOR(basic_flags_t, "const T&");
+ set_flags(bits);
+ }
+ basic_flags_t(const U& bits) {
+ TRACE_CTOR(basic_flags_t, "const U&");
+ set_flags(static_cast<T>(bits));
+ }
+ ~basic_flags_t() throw() {
+ TRACE_DTOR(basic_flags_t);
+ }
+
+ basic_flags_t(const basic_flags_t& other)
+ : supports_flags<T, U>(other) {
+ TRACE_CTOR(basic_flags_t, "copy");
+ }
+ basic_flags_t& operator=(const basic_flags_t& other) {
+ set_flags(other.flags());
+ return *this;
+ }
+ basic_flags_t& operator=(const T& bits) {
+ set_flags(bits);
+ return *this;
+ }
+
+ operator T() const {
+ return supports_flags<T, U>::flags();
+ }
+ operator U() const {
+ return supports_flags<T, U>::flags();
+ }
+
+ basic_flags_t plus_flags(const T& arg) const {
+ basic_flags_t temp(*this);
+ temp.add_flags(arg);
+ return temp;
+ }
+ basic_flags_t minus_flags(const T& arg) const {
+ basic_flags_t temp(*this);
+ temp.drop_flags(arg);
+ return temp;
+ }
+};
+
+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);
+ }
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */)
+ {
+ ar & _flags;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+#endif // _FLAGS_H
diff --git a/src/format.cc b/src/format.cc
new file mode 100644
index 00000000..f26a86a1
--- /dev/null
+++ b/src/format.cc
@@ -0,0 +1,488 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "format.h"
+#include "scope.h"
+#include "pstream.h"
+
+namespace ledger {
+
+format_t::elision_style_t format_t::default_style = TRUNCATE_TRAILING;
+bool format_t::default_style_changed = false;
+
+void format_t::element_t::dump(std::ostream& out) const
+{
+ out << "Element: ";
+
+ switch (type) {
+ case STRING: out << " STRING"; break;
+ case EXPR: out << " EXPR"; break;
+ }
+
+ out << " flags: 0x" << std::hex << int(flags());
+ out << " min: ";
+ out << std::right;
+ out.width(2);
+ out << std::dec << int(min_width);
+ out << " max: ";
+ out << std::right;
+ out.width(2);
+ out << std::dec << int(max_width);
+
+ switch (type) {
+ case STRING:
+ out << " str: '" << boost::get<string>(data) << "'" << std::endl;
+ break;
+ case EXPR:
+ out << " expr: " << boost::get<expr_t>(data) << std::endl;
+ break;
+ }
+}
+
+namespace {
+ expr_t parse_single_expression(const char *& p, bool single_expr = true)
+ {
+ string temp(p);
+ ptristream str(const_cast<char *&>(p));
+ expr_t expr;
+ expr.parse(str, single_expr ? PARSE_SINGLE : PARSE_PARTIAL, temp);
+ if (str.eof()) {
+ expr.set_text(p);
+ p += std::strlen(p);
+ } else {
+ assert(str.good());
+ istream_pos_type pos = str.tellg();
+ expr.set_text(string(p, p + long(pos)));
+ p += long(pos) - 1;
+
+ // Don't gobble up any whitespace
+ const char * base = p;
+ while (p >= base && std::isspace(*p))
+ p--;
+ }
+ return expr;
+ }
+}
+
+format_t::element_t * format_t::parse_elements(const string& fmt,
+ const optional<format_t&>& tmpl)
+{
+ std::auto_ptr<element_t> result;
+
+ element_t * current = NULL;
+
+ char buf[1024];
+ char * q = buf;
+
+ 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->data = 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->data = string("\b"); break;
+ case 'f': current->data = string("\f"); break;
+ case 'n': current->data = string("\n"); break;
+ case 'r': current->data = string("\r"); break;
+ case 't': current->data = string("\t"); break;
+ case 'v': current->data = string("\v"); break;
+ case '\\': current->data = string("\\"); break;
+ default: current->data = string(1, *p); break;
+ }
+ continue;
+ }
+
+ ++p;
+ while (*p == '-') {
+ switch (*p) {
+ case '-':
+ current->add_flags(ELEMENT_ALIGN_LEFT);
+ break;
+ }
+ ++p;
+ }
+
+ std::size_t 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->data = string("%");
+ break;
+
+ case '$': {
+ if (! tmpl)
+ throw_(format_error, _("Prior field reference, but no template"));
+
+ p++;
+ if (*p == '0' || (! std::isdigit(*p) &&
+ *p != 'A' && *p != 'B' && *p != 'C' &&
+ *p != 'D' && *p != 'E' && *p != 'F'))
+ throw_(format_error, _("%$ field reference must be a digit from 1-9"));
+
+ unsigned int index = std::isdigit(*p) ? *p - '0' : (*p - 'A' + 10);
+ element_t * tmpl_elem = tmpl->elements.get();
+
+ for (unsigned int i = 1; i < index && tmpl_elem; i++) {
+ tmpl_elem = tmpl_elem->next.get();
+ while (tmpl_elem && tmpl_elem->type != element_t::EXPR)
+ tmpl_elem = tmpl_elem->next.get();
+ }
+
+ if (! tmpl_elem)
+ throw_(format_error, _("%$ reference to a non-existent prior field"));
+
+ *current = *tmpl_elem;
+ break;
+ }
+
+ case '(':
+ case '{': {
+ bool format_amount = *p == '{';
+ if (format_amount) p++;
+
+ current->type = element_t::EXPR;
+ current->data = parse_single_expression(p, ! format_amount);
+
+ // Wrap the subexpression in calls to justify and scrub
+ if (format_amount) {
+ if (! *p || *(p + 1) != '}')
+ throw_(format_error, _("Expected closing brace"));
+ else
+ p++;
+
+ expr_t::ptr_op_t op = boost::get<expr_t>(current->data).get_op();
+
+ expr_t::ptr_op_t amount_op;
+ expr_t::ptr_op_t colorize_op;
+ if (op->kind == expr_t::op_t::O_CONS) {
+ amount_op = op->left();
+ colorize_op = op->right();
+ } else {
+ amount_op = op;
+ }
+
+ expr_t::ptr_op_t scrub_node(new expr_t::op_t(expr_t::op_t::IDENT));
+ scrub_node->set_ident("scrub");
+
+ expr_t::ptr_op_t call1_node(new expr_t::op_t(expr_t::op_t::O_CALL));
+ call1_node->set_left(scrub_node);
+ call1_node->set_right(amount_op);
+
+ expr_t::ptr_op_t arg1_node(new expr_t::op_t(expr_t::op_t::VALUE));
+ expr_t::ptr_op_t arg2_node(new expr_t::op_t(expr_t::op_t::VALUE));
+ expr_t::ptr_op_t arg3_node(new expr_t::op_t(expr_t::op_t::VALUE));
+
+ arg1_node->set_value(current->min_width > 0 ?
+ long(current->min_width) : -1);
+ arg2_node->set_value(current->max_width > 0 ?
+ long(current->max_width) : -1);
+ arg3_node->set_value(! current->has_flags(ELEMENT_ALIGN_LEFT));
+
+ current->min_width = 0;
+ current->max_width = 0;
+
+ expr_t::ptr_op_t args1_node(new expr_t::op_t(expr_t::op_t::O_CONS));
+ args1_node->set_left(arg2_node);
+ args1_node->set_right(arg3_node);
+
+ expr_t::ptr_op_t args2_node(new expr_t::op_t(expr_t::op_t::O_CONS));
+ args2_node->set_left(arg1_node);
+ args2_node->set_right(args1_node);
+
+ expr_t::ptr_op_t args3_node(new expr_t::op_t(expr_t::op_t::O_CONS));
+ args3_node->set_left(call1_node);
+ args3_node->set_right(args2_node);
+
+ expr_t::ptr_op_t seq1_node(new expr_t::op_t(expr_t::op_t::O_SEQ));
+ seq1_node->set_left(args3_node);
+
+ expr_t::ptr_op_t justify_node(new expr_t::op_t(expr_t::op_t::IDENT));
+ justify_node->set_ident("justify");
+
+ expr_t::ptr_op_t call2_node(new expr_t::op_t(expr_t::op_t::O_CALL));
+ call2_node->set_left(justify_node);
+ call2_node->set_right(seq1_node);
+
+ string prev_expr = boost::get<expr_t>(current->data).text();
+
+ if (colorize_op) {
+ expr_t::ptr_op_t ansify_if_node(new expr_t::op_t(expr_t::op_t::IDENT));
+ ansify_if_node->set_ident("ansify_if");
+
+ expr_t::ptr_op_t args4_node(new expr_t::op_t(expr_t::op_t::O_CONS));
+ args4_node->set_left(call2_node);
+ args4_node->set_right(colorize_op);
+
+ expr_t::ptr_op_t seq2_node(new expr_t::op_t(expr_t::op_t::O_SEQ));
+ seq2_node->set_left(args4_node);
+
+ expr_t::ptr_op_t call3_node(new expr_t::op_t(expr_t::op_t::O_CALL));
+ call3_node->set_left(ansify_if_node);
+ call3_node->set_right(seq2_node);
+
+ current->data = expr_t(call3_node);
+ } else {
+ current->data = expr_t(call2_node);
+ }
+
+ boost::get<expr_t>(current->data).set_text(prev_expr);
+ }
+ break;
+ }
+
+ default:
+ throw_(format_error, _("Unrecognized formatting character: %1") << *p);
+ }
+ }
+
+ 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->data = string(buf, q);
+ }
+
+ return result.release();
+}
+
+string format_t::real_calc(scope_t& scope)
+{
+ std::ostringstream out_str;
+
+ for (element_t * elem = elements.get(); elem; elem = elem->next.get()) {
+ std::ostringstream out;
+ string name;
+
+ if (elem->has_flags(ELEMENT_ALIGN_LEFT))
+ out << std::left;
+ else
+ out << std::right;
+
+ switch (elem->type) {
+ case element_t::STRING:
+ if (elem->min_width > 0)
+ out.width(elem->min_width);
+ out << boost::get<string>(elem->data);
+ break;
+
+ case element_t::EXPR: {
+ expr_t& expr(boost::get<expr_t>(elem->data));
+ try {
+
+ expr.compile(scope);
+
+ value_t value;
+ if (expr.is_function()) {
+ call_scope_t args(scope);
+ args.push_back(long(elem->max_width));
+ value = expr.get_function()(args);
+ } else {
+ value = expr.calc(scope);
+ }
+ DEBUG("format.expr", "value = (" << value << ")");
+
+ if (elem->min_width > 0)
+ value.print(out, static_cast<int>(elem->min_width), -1,
+ ! elem->has_flags(ELEMENT_ALIGN_LEFT));
+ else
+ out << value.to_string();
+ }
+ catch (const calc_error&) {
+ add_error_context(_("While calculating format expression:"));
+ add_error_context(expr.context_to_str());
+ throw;
+ }
+ break;
+ }
+
+ default:
+ assert(false);
+ break;
+ }
+
+ if (elem->max_width > 0 || elem->min_width > 0) {
+ unistring temp(out.str());
+ string result;
+
+ if (elem->max_width > 0 && elem->max_width < temp.length()) {
+ result = truncate(temp, elem->max_width);
+ } else {
+ result = temp.extract();
+ if (elem->min_width > temp.length())
+ for (std::size_t i = 0; i < elem->min_width - temp.length(); i++)
+ result += " ";
+ }
+ out_str << result;
+ } else {
+ out_str << out.str();
+ }
+ }
+
+ return out_str.str();
+}
+
+string format_t::truncate(const unistring& ustr,
+ const std::size_t width,
+ const std::size_t account_abbrev_length)
+{
+ assert(width < 4095);
+
+ const std::size_t len = ustr.length();
+ if (width == 0 || len <= width)
+ return ustr.extract();
+
+ std::ostringstream buf;
+
+ elision_style_t style = default_style;
+ if (account_abbrev_length > 0 && ! default_style_changed)
+ style = ABBREVIATE;
+
+ switch (style) {
+ case TRUNCATE_LEADING:
+ // This method truncates at the beginning.
+ buf << ".." << ustr.extract(len - (width - 2), width - 2);
+ break;
+
+ case TRUNCATE_MIDDLE:
+ // This method truncates in the middle.
+ buf << ustr.extract(0, (width - 2) / 2)
+ << ".."
+ << ustr.extract(len - ((width - 2) / 2 + (width - 2) % 2),
+ (width - 2) / 2 + (width - 2) % 2);
+ break;
+
+ case ABBREVIATE:
+ if (account_abbrev_length > 0) {
+ std::list<string> parts;
+ string::size_type beg = 0;
+ string strcopy(ustr.extract());
+ for (string::size_type pos = strcopy.find(':');
+ pos != string::npos;
+ beg = pos + 1, pos = strcopy.find(':', beg))
+ parts.push_back(string(strcopy, beg, pos - beg));
+ parts.push_back(string(strcopy, beg));
+
+ std::ostringstream result;
+
+ std::size_t 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) {
+ unistring temp(*i);
+ if (temp.length() > account_abbrev_length) {
+ result << temp.extract(0, account_abbrev_length) << ":";
+ newlen -= temp.length() - account_abbrev_length;
+ } else {
+ result << temp.extract() << ":";
+ newlen -= temp.length();
+ }
+ } else {
+ result << *i << ":";
+ }
+ }
+
+ if (newlen > width) {
+ // Even abbreviated its too big to show the last account, so
+ // abbreviate all but the last and truncate at the beginning.
+ unistring temp(result.str());
+ assert(temp.length() > width - 2);
+ buf << ".." << temp.extract(temp.length() - (width - 2), width - 2);
+ } else {
+ buf << result.str();
+ }
+ break;
+ }
+ // fall through...
+
+ case TRUNCATE_TRAILING:
+ // This method truncates at the end (the default).
+ buf << ustr.extract(0, width - 2) << "..";
+ break;
+ }
+
+ return buf.str();
+}
+
+} // namespace ledger
diff --git a/src/format.h b/src/format.h
new file mode 100644
index 00000000..a2bf1015
--- /dev/null
+++ b/src/format.h
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup expr
+ */
+
+/**
+ * @file format.h
+ * @author John Wiegley
+ *
+ * @ingroup expr
+ */
+#ifndef _FORMAT_H
+#define _FORMAT_H
+
+#include "expr.h"
+#include "unistring.h"
+
+namespace ledger {
+
+class unistring;
+
+DECLARE_EXCEPTION(format_error, std::runtime_error);
+
+class format_t : public expr_base_t<string>
+{
+ typedef expr_base_t<string> base_type;
+
+ struct element_t : public supports_flags<>
+ {
+#define ELEMENT_ALIGN_LEFT 0x01
+
+ enum kind_t { STRING, EXPR };
+
+ kind_t type;
+ std::size_t min_width;
+ std::size_t max_width;
+ variant<string, expr_t> data;
+ scoped_ptr<struct element_t> next;
+
+ element_t() throw()
+ : supports_flags<>(), type(STRING), min_width(0), max_width(0) {
+ TRACE_CTOR(element_t, "");
+ }
+ ~element_t() throw() {
+ TRACE_DTOR(element_t);
+ }
+ element_t(const element_t& elem) : supports_flags<>() {
+ *this = elem;
+ }
+
+ element_t& operator=(const element_t& elem) {
+ if (this != &elem) {
+ supports_flags<>::operator=(elem);
+ type = elem.type;
+ min_width = elem.min_width;
+ max_width = elem.max_width;
+ data = elem.data;
+ }
+ return *this;
+ }
+
+ friend inline void mark_red(std::ostream& out, const element_t * elem) {
+ out.setf(std::ios::left);
+ out.width(0);
+ out << "\033[31m";
+
+ if (elem->has_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;
+ };
+
+ scoped_ptr<element_t> elements;
+
+public:
+ static enum elision_style_t {
+ TRUNCATE_TRAILING,
+ TRUNCATE_MIDDLE,
+ TRUNCATE_LEADING,
+ ABBREVIATE
+ } default_style;
+
+ static bool default_style_changed;
+
+private:
+ static element_t * parse_elements(const string& fmt,
+ const optional<format_t&>& tmpl);
+
+public:
+ format_t() : base_type() {
+ TRACE_CTOR(format_t, "");
+ }
+ format_t(const string& _str, scope_t * context = NULL)
+ : base_type(context) {
+ TRACE_CTOR(format_t, "const string&");
+ if (! _str.empty())
+ parse_format(_str);
+ }
+ virtual ~format_t() {
+ TRACE_DTOR(format_t);
+ }
+
+ void parse_format(const string& _format,
+ const optional<format_t&>& tmpl = none) {
+ elements.reset(parse_elements(_format, tmpl));
+ set_text(_format);
+ }
+
+ virtual result_type real_calc(scope_t& scope);
+
+ virtual 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 unistring& str,
+ const std::size_t width,
+ const std::size_t account_abbrev_length = 0);
+};
+
+} // namespace ledger
+
+#endif // _FORMAT_H
diff --git a/src/generate.cc b/src/generate.cc
new file mode 100644
index 00000000..3549adc8
--- /dev/null
+++ b/src/generate.cc
@@ -0,0 +1,385 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "generate.h"
+#include "session.h"
+
+namespace ledger {
+
+generate_posts_iterator::generate_posts_iterator
+ (session_t& _session,
+ unsigned int _seed,
+ std::size_t _quantity,
+ bool _allow_invalid)
+ : session(_session), seed(_seed), quantity(_quantity),
+ allow_invalid(_allow_invalid),
+
+ rnd_gen(seed == 0 ? static_cast<unsigned int>(std::time(0)) : seed),
+
+ year_range(1900, 2300), year_gen(rnd_gen, year_range),
+ mon_range(1, 12), mon_gen(rnd_gen, mon_range),
+ day_range(1, 28), day_gen(rnd_gen, day_range),
+
+ upchar_range('A', 'Z'), upchar_gen(rnd_gen, upchar_range),
+ downchar_range('a', 'z'), downchar_gen(rnd_gen, downchar_range),
+ numchar_range('0', '9'), numchar_gen(rnd_gen, numchar_range),
+
+ truth_range(0, 1), truth_gen(rnd_gen, truth_range),
+ three_range(1, 3), three_gen(rnd_gen, three_range),
+ six_range(1, 6), six_gen(rnd_gen, six_range),
+ two_six_range(2, 6), two_six_gen(rnd_gen, two_six_range),
+ strlen_range(1, 40), strlen_gen(rnd_gen, strlen_range),
+
+ neg_number_range(-1000000, -1), neg_number_gen(rnd_gen, neg_number_range),
+ pos_number_range(1, 1000000), pos_number_gen(rnd_gen, pos_number_range)
+{
+ TRACE_CTOR(generate_posts_iterator, "bool");
+
+ std::ostringstream next_date_buf;
+ generate_date(next_date_buf);
+ next_date = parse_date(next_date_buf.str());
+
+ std::ostringstream next_eff_date_buf;
+ generate_date(next_eff_date_buf);
+ next_eff_date = parse_date(next_eff_date_buf.str());
+
+}
+
+void generate_posts_iterator::generate_string(std::ostream& out, int len,
+ bool only_alpha)
+{
+ DEBUG("generate.post.string",
+ "Generating string of length " << len << ", only alpha " << only_alpha);
+
+ int last = -1;
+ bool first = true;
+ for (int i = 0; i < len; i++) {
+ int next = only_alpha ? 3 : three_gen();
+ bool output = true;
+ switch (next) {
+ case 1: // colon
+ if (! first && last == 3 && strlen_gen() % 10 == 0 && i + 1 != len)
+ out << ':';
+ else {
+ i--;
+ output = false;
+ }
+ break;
+ case 2: // space
+ if (! first && last == 3 && strlen_gen() % 20 == 0 && i + 1 != len)
+ out << ' ';
+ else {
+ i--;
+ output = false;
+ }
+ break;
+ case 3: // character
+ switch (three_gen()) {
+ case 1: // uppercase
+ out << char(upchar_gen());
+ break;
+ case 2: // lowercase
+ out << char(downchar_gen());
+ break;
+ case 3: // number
+ if (! only_alpha && ! first)
+ out << char(numchar_gen());
+ else {
+ i--;
+ output = false;
+ }
+ break;
+ }
+ break;
+ }
+ if (output) {
+ last = next;
+ first = false;
+ }
+ }
+}
+
+bool generate_posts_iterator::generate_account(std::ostream& out,
+ bool no_virtual)
+{
+ bool must_balance = true;
+ bool is_virtual = false;
+
+ if (! no_virtual) {
+ switch (three_gen()) {
+ case 1:
+ out << '[';
+ is_virtual = true;
+ break;
+ case 2:
+ out << '(';
+ must_balance = false;
+ is_virtual = true;
+ break;
+ case 3:
+ break;
+ }
+ }
+
+ generate_string(out, strlen_gen());
+
+ if (is_virtual) {
+ if (must_balance)
+ out << ']';
+ else
+ out << ')';
+ }
+
+ return must_balance;
+}
+
+void generate_posts_iterator::generate_commodity(std::ostream& out)
+{
+ string comm;
+ do {
+ std::ostringstream buf;
+ generate_string(buf, six_gen(), true);
+ comm = buf.str();
+ }
+ while (comm == "h" || comm == "m" || comm == "s" ||
+ comm == "and" || comm == "div" || comm == "false" ||
+ comm == "or" || comm == "not" || comm == "true" ||
+ comm == "if" || comm == "else");
+
+ out << comm;
+}
+
+string generate_posts_iterator::generate_amount(std::ostream& out,
+ value_t not_this_amount,
+ bool no_negative)
+{
+ std::ostringstream buf;
+
+ if (truth_gen()) { // commodity goes in front
+ generate_commodity(buf);
+ if (truth_gen())
+ buf << ' ';
+ if (no_negative || truth_gen())
+ buf << pos_number_gen();
+ else
+ buf << neg_number_gen();
+ } else {
+ if (no_negative || truth_gen())
+ buf << pos_number_gen();
+ else
+ buf << neg_number_gen();
+ if (truth_gen())
+ buf << ' ';
+ generate_commodity(buf);
+ }
+
+ // Possibly generate an annotized commodity, but make it rarer
+ if (! no_negative && three_gen() == 1) {
+ if (three_gen() == 1) {
+ buf << " {";
+ generate_amount(buf, value_t(), true);
+ buf << '}';
+ }
+ if (six_gen() == 1) {
+ buf << " [";
+ generate_date(buf);
+ buf << ']';
+ }
+ if (six_gen() == 1) {
+ buf << " (";
+ generate_string(buf, six_gen());
+ buf << ')';
+ }
+ }
+
+ if (! not_this_amount.is_null() &&
+ value_t(buf.str()).as_amount().commodity() ==
+ not_this_amount.as_amount().commodity())
+ return "";
+
+ out << buf.str();
+
+ return buf.str();
+}
+
+bool generate_posts_iterator::generate_post(std::ostream& out, bool no_amount)
+{
+ out << " ";
+ bool must_balance = generate_account(out, no_amount);
+ out << " ";
+
+ if (! no_amount) {
+ value_t amount(generate_amount(out));
+ if (truth_gen())
+ generate_cost(out, amount);
+ }
+ if (truth_gen())
+ generate_note(out);
+ out << '\n';
+
+ return must_balance;
+}
+
+void generate_posts_iterator::generate_cost(std::ostream& out, value_t amount)
+{
+ std::ostringstream buf;
+
+ if (truth_gen())
+ buf << " @ ";
+ else
+ buf << " @@ ";
+
+ if (! generate_amount(buf, amount, true).empty())
+ out << buf.str();
+}
+
+void generate_posts_iterator::generate_date(std::ostream& out)
+{
+ out.width(4);
+ out.fill('0');
+ out << year_gen();
+
+ out.width(1);
+ out << '/';
+
+ out.width(2);
+ out.fill('0');
+ out << mon_gen();
+
+ out.width(1);
+ out << '/';
+
+ out.width(2);
+ out.fill('0');
+ out << day_gen();
+}
+
+void generate_posts_iterator::generate_state(std::ostream& out)
+{
+ switch (three_gen()) {
+ case 1:
+ out << "* ";
+ break;
+ case 2:
+ out << "! ";
+ break;
+ case 3:
+ out << "";
+ break;
+ }
+}
+
+void generate_posts_iterator::generate_code(std::ostream& out)
+{
+ out << '(';
+ generate_string(out, six_gen());
+ out << ") ";
+}
+
+void generate_posts_iterator::generate_payee(std::ostream& out)
+{
+ generate_string(out, strlen_gen());
+}
+
+void generate_posts_iterator::generate_note(std::ostream& out)
+{
+ out << "\n ; ";
+ generate_string(out, strlen_gen());
+}
+
+void generate_posts_iterator::generate_xact(std::ostream& out)
+{
+ out << format_date(next_date, FMT_WRITTEN);
+ next_date += gregorian::days(six_gen());
+ if (truth_gen()) {
+ out << '=';
+ out << format_date(next_eff_date, FMT_WRITTEN);
+ next_eff_date += gregorian::days(six_gen());
+ }
+ out << ' ';
+
+ generate_state(out);
+ generate_code(out);
+ generate_payee(out);
+ if (truth_gen())
+ generate_note(out);
+ out << '\n';
+
+ int count = three_gen() * 2;
+ bool has_must_balance = false;
+ for (int i = 0; i < count; i++) {
+ if (generate_post(out))
+ has_must_balance = true;
+ }
+ if (has_must_balance)
+ generate_post(out, true);
+
+ out << '\n';
+}
+
+post_t * generate_posts_iterator::operator()()
+{
+ post_t * post = posts();
+ if (post == NULL && quantity > 0) {
+ std::ostringstream buf;
+ generate_xact(buf);
+
+ DEBUG("generate.post", "The post we intend to parse:\n" << buf.str());
+
+ std::istringstream in(buf.str());
+ try {
+ if (session.journal->parse(in, session) != 0) {
+ VERIFY(session.journal->xacts.back()->valid());
+ posts.reset(*session.journal->xacts.back());
+ post = posts();
+ }
+ }
+ catch (std::exception& err) {
+ add_error_context(_("While parsing generated transaction (seed %1):")
+ << seed);
+ add_error_context(buf.str());
+ throw;
+ }
+ catch (int status) {
+ add_error_context(_("While parsing generated transaction (seed %1):")
+ << seed);
+ add_error_context(buf.str());
+ throw;
+ }
+
+ quantity--;
+ }
+ return post;
+}
+
+} // namespace ledger
diff --git a/src/generate.h b/src/generate.h
new file mode 100644
index 00000000..66513fc8
--- /dev/null
+++ b/src/generate.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup generate
+ */
+
+/**
+ * @file generate.h
+ * @author John Wiegley
+ *
+ * @ingroup report
+ */
+#ifndef _GENERATE_H
+#define _GENERATE_H
+
+#include "iterators.h"
+
+namespace ledger {
+
+class session_t;
+
+class generate_posts_iterator : public posts_iterator
+{
+ session_t& session;
+ unsigned int seed;
+ std::size_t quantity;
+ bool allow_invalid;
+ date_t next_date;
+ date_t next_eff_date;
+
+ mt19937 rnd_gen;
+
+ typedef variate_generator<mt19937&, uniform_int<> > int_generator_t;
+ typedef variate_generator<mt19937&, uniform_real<> > real_generator_t;
+
+ uniform_int<> year_range;
+ int_generator_t year_gen;
+ uniform_int<> mon_range;
+ int_generator_t mon_gen;
+ uniform_int<> day_range;
+ int_generator_t day_gen;
+
+ uniform_int<> upchar_range;
+ int_generator_t upchar_gen;
+ uniform_int<> downchar_range;
+ int_generator_t downchar_gen;
+ uniform_int<> numchar_range;
+ int_generator_t numchar_gen;
+
+ uniform_int<> truth_range;
+ int_generator_t truth_gen;
+ uniform_int<> three_range;
+ int_generator_t three_gen;
+ uniform_int<> six_range;
+ int_generator_t six_gen;
+ uniform_int<> two_six_range;
+ int_generator_t two_six_gen;
+
+ uniform_int<> strlen_range;
+ int_generator_t strlen_gen;
+
+ uniform_real<> neg_number_range;
+ real_generator_t neg_number_gen;
+ uniform_real<> pos_number_range;
+ real_generator_t pos_number_gen;
+
+ xact_posts_iterator posts;
+
+public:
+ generate_posts_iterator(session_t& _session,
+ unsigned int _seed = 0,
+ std::size_t _quantity = 100,
+ bool _allow_invalid = false);
+
+ virtual ~generate_posts_iterator() throw() {
+ TRACE_DTOR(generate_posts_iterator);
+ }
+
+ virtual post_t * operator()();
+
+protected:
+ void generate_string(std::ostream& out, int len, bool only_alpha = false);
+ bool generate_account(std::ostream& out, bool no_virtual = false);
+ void generate_commodity(std::ostream& out);
+ string generate_amount(std::ostream& out,
+ value_t not_this_amount = NULL_VALUE,
+ bool no_negative = false);
+ bool generate_post(std::ostream& out, bool no_amount = false);
+ void generate_cost(std::ostream& out, value_t amount);
+ void generate_date(std::ostream& out);
+ void generate_state(std::ostream& out);
+ void generate_code(std::ostream& out);
+ void generate_payee(std::ostream& out);
+ void generate_note(std::ostream& out);
+ void generate_xact(std::ostream& out);
+};
+
+} // namespace ledger
+
+#endif // _GENERATE_H
diff --git a/src/global.cc b/src/global.cc
new file mode 100644
index 00000000..e120e5d5
--- /dev/null
+++ b/src/global.cc
@@ -0,0 +1,480 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "global.h"
+#if defined(HAVE_BOOST_PYTHON)
+#include "pyinterp.h"
+#else
+#include "session.h"
+#endif
+#include "item.h"
+#include "journal.h"
+#include "pool.h"
+
+namespace ledger {
+
+static bool args_only = false;
+
+global_scope_t::global_scope_t(char ** envp)
+{
+ TRACE_CTOR(global_scope_t, "");
+
+#if defined(HAVE_BOOST_PYTHON)
+ if (! python_session.get()) {
+ python_session.reset(new ledger::python_interpreter_t);
+ session_ptr = python_session;
+ }
+#else
+ session_ptr.reset(new session_t);
+#endif
+
+ set_session_context(session_ptr.get());
+
+ // Create the report object, which maintains state relating to each
+ // command invocation. Because we're running from main(), the
+ // distinction between session and report doesn't really matter, but if
+ // a GUI were calling into Ledger it would have one session object per
+ // open document, with a separate report_t object for each report it
+ // generated.
+ report_stack.push_front(new report_t(session()));
+ scope_t::default_scope = &report();
+
+ // Read the user's options, in the following order:
+ //
+ // 1. environment variables (LEDGER_<option>)
+ // 2. initialization file (~/.ledgerrc)
+ // 3. command-line (--option or -o)
+ //
+ // Before processing command-line options, we must notify the session object
+ // that such options are beginning, since options like -f cause a complete
+ // override of files found anywhere else.
+ if (! args_only) {
+ session().set_flush_on_next_data_file(true);
+ read_environment_settings(envp);
+ session().set_flush_on_next_data_file(true);
+ read_init();
+ } else {
+ session().HANDLER(price_db_).off();
+ }
+}
+
+global_scope_t::~global_scope_t()
+{
+ TRACE_DTOR(global_scope_t);
+
+ // If memory verification is being performed (which can be very slow),
+ // clean up everything by closing the session and deleting the session
+ // object, and then shutting down the memory tracing subsystem.
+ // Otherwise, let it all leak because we're about to exit anyway.
+ IF_VERIFY() set_session_context(NULL);
+
+#if defined(HAVE_BOOST_PYTHON)
+ python_session.reset();
+#endif
+}
+
+void global_scope_t::read_init()
+{
+ if (HANDLED(init_file_)) {
+ path init_file(HANDLER(init_file_).str());
+ if (exists(init_file)) {
+ TRACE_START(init, 1, "Read initialization file");
+
+ ifstream init(init_file);
+
+ if (session().journal->read(init_file, NULL, &report()) > 0 ||
+ session().journal->auto_xacts.size() > 0 ||
+ session().journal->period_xacts.size() > 0) {
+ throw_(parse_error, _("Transactions found in initialization file '%1'")
+ << init_file);
+ }
+
+ TRACE_FINISH(init, 1);
+ }
+ }
+}
+
+char * global_scope_t::prompt_string()
+{
+ static char prompt[32];
+ std::size_t i;
+ for (i = 0; i < report_stack.size(); i++)
+ prompt[i] = ']';
+ prompt[i++] = ' ';
+ prompt[i] = '\0';
+ return prompt;
+}
+
+void global_scope_t::report_error(const std::exception& err)
+{
+ std::cout.flush(); // first display anything that was pending
+
+ if (caught_signal == NONE_CAUGHT) {
+ // Display any pending error context information
+ string context = error_context();
+ if (! context.empty())
+ std::cerr << context << std::endl;
+
+ std::cerr << _("Error: ") << err.what() << std::endl;
+ } else {
+ caught_signal = NONE_CAUGHT;
+ }
+}
+
+void global_scope_t::execute_command(strings_list args, bool at_repl)
+{
+ session().set_flush_on_next_data_file(true);
+
+ // Process the command verb, arguments and options
+ if (at_repl) {
+ args = read_command_arguments(report(), args);
+ if (args.empty())
+ return;
+ }
+
+ strings_list::iterator arg = args.begin();
+ string verb = *arg++;
+
+ // Look for a precommand first, which is defined as any defined function
+ // whose name starts with "ledger_precmd_". The difference between a
+ // precommand and a regular command is that precommands ignore the journal
+ // data file completely, nor is the user's init file read.
+ //
+ // Here are some examples of pre-commands:
+ //
+ // parse STRING ; show how a value expression is parsed
+ // eval STRING ; simply evaluate a value expression
+ // format STRING ; show how a format string is parsed
+ //
+ // If such a command is found, create the output stream for the result and
+ // then invoke the command.
+
+ expr_t::func_t command;
+ bool is_precommand = false;
+ bind_scope_t bound_scope(*this, report());
+
+ if (bool(command = look_for_precommand(bound_scope, verb)))
+ is_precommand = true;
+
+ // If it is not a pre-command, then parse the user's ledger data at this
+ // time if not done alreday (i.e., if not at a REPL). Then patch up the
+ // report options based on the command verb.
+
+ if (! is_precommand) {
+ if (! at_repl)
+ session().read_journal_files();
+
+ report().normalize_options(verb);
+
+ if (! bool(command = look_for_command(bound_scope, verb)))
+ throw_(std::logic_error, _("Unrecognized command '%1'") << verb);
+ }
+
+ // Create the output stream (it might be a file, the console or a PAGER
+ // subprocess) and invoke the report command. The output stream is closed
+ // by the caller of this function.
+
+ report().output_stream
+ .initialize(report().HANDLED(output_) ?
+ optional<path>(path(report().HANDLER(output_).str())) :
+ optional<path>(),
+ report().HANDLED(pager_) ?
+ optional<path>(path(report().HANDLER(pager_).str())) :
+ optional<path>());
+
+ // Now that the output stream is initialized, report the options that will
+ // participate in this report, if the user specified --options
+
+ if (HANDLED(options))
+ report_options(report(), report().output_stream);
+
+ // Create an argument scope containing the report command's arguments, and
+ // then invoke the command. The bound scope causes lookups to happen
+ // first in the global scope, and then in the report scope.
+
+ call_scope_t command_args(bound_scope);
+ for (strings_list::iterator i = arg; i != args.end(); i++)
+ command_args.push_back(string_value(*i));
+
+ INFO_START(command, "Finished executing command");
+ command(command_args);
+ INFO_FINISH(command);
+}
+
+int global_scope_t::execute_command_wrapper(strings_list args, bool at_repl)
+{
+ int status = 1;
+
+ try {
+ if (at_repl) push_report();
+ execute_command(args, at_repl);
+ if (at_repl) pop_report();
+
+ // If we've reached this point, everything succeeded fine. Ledger uses
+ // exceptions to notify of error conditions, so if you're using gdb,
+ // just type "catch throw" to find the source point of any error.
+ status = 0;
+ }
+ catch (const std::exception& err) {
+ if (at_repl) pop_report();
+ report_error(err);
+ }
+
+ return status;
+}
+
+void global_scope_t::report_options(report_t& report, std::ostream& out)
+{
+ out << "==============================================================================="
+ << std::endl;
+ out << "[Global scope options]" << std::endl;
+
+ HANDLER(args_only).report(out);
+ HANDLER(debug_).report(out);
+ HANDLER(init_file_).report(out);
+ HANDLER(script_).report(out);
+ HANDLER(trace_).report(out);
+ HANDLER(verbose).report(out);
+ HANDLER(verify).report(out);
+
+ out << std::endl << "[Session scope options]" << std::endl;
+ report.session.report_options(out);
+
+ out << std::endl << "[Report scope options]" << std::endl;
+ report.report_options(out);
+ out << "==============================================================================="
+ << std::endl;
+}
+
+option_t<global_scope_t> * global_scope_t::lookup_option(const char * p)
+{
+ switch (*p) {
+ case 'a':
+ OPT(args_only);
+ break;
+ case 'd':
+ OPT(debug_);
+ break;
+ case 'f':
+ OPT(full_help);
+ break;
+ case 'h':
+ OPT_(help);
+ else OPT(help_calc);
+ else OPT(help_comm);
+ else OPT(help_disp);
+ break;
+ case 'i':
+ OPT(init_file_);
+ break;
+ case 'o':
+ OPT(options);
+ break;
+ case 's':
+ OPT(script_);
+ break;
+ case 't':
+ OPT(trace_);
+ break;
+ case 'v':
+ OPT_(verbose);
+ else OPT(verify);
+ else OPT(version);
+ break;
+ }
+ return NULL;
+}
+
+expr_t::ptr_op_t global_scope_t::lookup(const symbol_t::kind_t kind,
+ const string& name)
+{
+ switch (kind) {
+ case symbol_t::FUNCTION:
+ if (option_t<global_scope_t> * handler = lookup_option(name.c_str()))
+ return MAKE_OPT_FUNCTOR(global_scope_t, handler);
+ break;
+
+ case symbol_t::OPTION:
+ if (option_t<global_scope_t> * handler = lookup_option(name.c_str()))
+ return MAKE_OPT_HANDLER(global_scope_t, handler);
+ break;
+
+ case symbol_t::PRECOMMAND: {
+ const char * p = name.c_str();
+ switch (*p) {
+ case 'p':
+ if (is_eq(p, "push"))
+ return MAKE_FUNCTOR(global_scope_t::push_command);
+ else if (is_eq(p, "pop"))
+ return MAKE_FUNCTOR(global_scope_t::pop_command);
+ break;
+ }
+ }
+ default:
+ break;
+ }
+
+ // If you're wondering how symbols from report() will be found, it's
+ // because of the bind_scope_t object in execute_command() below.
+ return NULL;
+}
+
+void global_scope_t::read_environment_settings(char * envp[])
+{
+ TRACE_START(environment, 1, "Processed environment variables");
+
+ process_environment(const_cast<const char **>(envp), "LEDGER_", report());
+
+#if 1
+ // These are here for backwards compatability, but are deprecated.
+
+ if (const char * p = std::getenv("LEDGER")) {
+ if (! std::getenv("LEDGER_FILE"))
+ process_option("environ", "file", report(), p, "LEDGER");
+ }
+ if (const char * p = std::getenv("LEDGER_INIT")) {
+ if (! std::getenv("LEDGER_INIT_FILE"))
+ process_option("environ", "init-file", report(), p, "LEDGER_INIT");
+ }
+ if (const char * p = std::getenv("PRICE_HIST")) {
+ if (! std::getenv("LEDGER_PRICEDB"))
+ process_option("environ", "price-db", report(), p, "PRICE_HIST");
+ }
+ if (const char * p = std::getenv("PRICE_EXP"))
+ process_option("environ", "price-exp", report(), p, "PRICE_EXP");
+#endif
+
+ TRACE_FINISH(environment, 1);
+}
+
+strings_list
+global_scope_t::read_command_arguments(scope_t& scope, strings_list args)
+{
+ TRACE_START(arguments, 1, "Processed command-line arguments");
+
+ strings_list remaining = process_arguments(args, scope);
+
+ TRACE_FINISH(arguments, 1);
+
+ return remaining;
+}
+
+void global_scope_t::normalize_session_options()
+{
+ INFO("Initialization file is " << HANDLER(init_file_).str());
+ INFO("Price database is " << session().HANDLER(price_db_).str());
+
+ foreach (const path& pathname, session().HANDLER(file_).data_files)
+ INFO("Journal file is " << pathname.string());
+}
+
+expr_t::func_t global_scope_t::look_for_precommand(scope_t& scope,
+ const string& verb)
+{
+ if (expr_t::ptr_op_t def = scope.lookup(symbol_t::PRECOMMAND, verb))
+ return def->as_function();
+ else
+ return expr_t::func_t();
+}
+
+expr_t::func_t global_scope_t::look_for_command(scope_t& scope,
+ const string& verb)
+{
+ if (expr_t::ptr_op_t def = scope.lookup(symbol_t::COMMAND, verb))
+ return def->as_function();
+ else
+ return expr_t::func_t();
+}
+
+void global_scope_t::visit_man_page() const
+{
+ int pid = fork();
+ if (pid < 0) {
+ throw std::logic_error(_("Failed to fork child process"));
+ }
+ else if (pid == 0) { // child
+ execlp("man", "man", "1", "ledger", NULL);
+
+ // We should never, ever reach here
+ perror("execlp: man");
+ exit(1);
+ }
+
+ int status = -1;
+ wait(&status);
+ exit(0); // parent
+}
+
+void handle_debug_options(int argc, char * argv[])
+{
+ for (int i = 1; i < argc; i++) {
+ if (argv[i][0] == '-') {
+ if (std::strcmp(argv[i], "--args-only") == 0) {
+ args_only = true;
+ }
+ else if (std::strcmp(argv[i], "--verify") == 0) {
+#if defined(VERIFY_ON)
+ verify_enabled = true; // global in utils.h
+#endif
+ }
+ else if (std::strcmp(argv[i], "--verbose") == 0 ||
+ std::strcmp(argv[i], "-v") == 0) {
+#if defined(LOGGING_ON)
+ _log_level = LOG_INFO; // global in utils.h
+#endif
+ }
+ else if (i + 1 < argc && std::strcmp(argv[i], "--debug") == 0) {
+#if defined(DEBUG_ON)
+ _log_level = LOG_DEBUG; // global in utils.h
+ _log_category = argv[i + 1]; // global in utils.h
+ i++;
+#endif
+ }
+ else if (i + 1 < argc && std::strcmp(argv[i], "--trace") == 0) {
+#if defined(TRACING_ON)
+ _log_level = LOG_TRACE; // global in utils.h
+ try {
+ // global in utils.h
+ _trace_level = boost::lexical_cast<uint8_t>(argv[i + 1]);
+ }
+ catch (const boost::bad_lexical_cast& e) {
+ throw std::logic_error(_("Argument to --trace must be an integer"));
+ }
+ i++;
+#endif
+ }
+ }
+ }
+}
+
+} // namespace ledger
diff --git a/src/global.h b/src/global.h
new file mode 100644
index 00000000..ab3afed4
--- /dev/null
+++ b/src/global.h
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file global.h
+ * @author John Wiegley
+ *
+ * @brief Contains the top-level functions used by main.cc
+ */
+#ifndef _GLOBAL_H
+#define _GLOBAL_H
+
+#include "interactive.h"
+#include "option.h"
+#include "report.h"
+
+namespace ledger {
+
+class session_t;
+class report_t;
+
+class global_scope_t : public noncopyable, public scope_t
+{
+ shared_ptr<session_t> session_ptr;
+ ptr_list<report_t> report_stack;
+
+public:
+ global_scope_t(char ** envp);
+ ~global_scope_t();
+
+ void read_init();
+ void read_environment_settings(char * envp[]);
+ strings_list read_command_arguments(scope_t& scope, strings_list args);
+ void normalize_session_options();
+ expr_t::func_t look_for_precommand(scope_t& scope, const string& verb);
+ expr_t::func_t look_for_command(scope_t& scope, const string& verb);
+
+ char * prompt_string();
+
+ session_t& session() {
+ return *session_ptr.get();
+ }
+ report_t& report() {
+ return report_stack.front();
+ }
+
+ void push_report() {
+ report_stack.push_front(new report_t(report_stack.front()));
+ scope_t::default_scope = &report();
+ }
+ void pop_report() {
+ assert(! report_stack.empty());
+ report_stack.pop_front();
+ // There should always be the "default report" waiting on the stack.
+ assert(! report_stack.empty());
+ scope_t::default_scope = &report();
+ }
+
+ void report_error(const std::exception& err);
+
+ /**
+ * @return \c true if a command was actually executed; otherwise, it probably
+ * just resulted in setting some options.
+ */
+ void execute_command(strings_list args, bool at_repl);
+ int execute_command_wrapper(strings_list args, bool at_repl);
+
+ value_t push_command(call_scope_t&) {
+ // Make a copy at position 2, because the topmost report object has an
+ // open output stream at this point. We want it to get popped off as
+ // soon as this command terminate so that the stream is closed cleanly.
+ report_stack.insert(++report_stack.begin(),
+ new report_t(report_stack.front()));
+ return true;
+ }
+ value_t pop_command(call_scope_t&) {
+ pop_report();
+ return true;
+ }
+
+ void show_version_info(std::ostream& out) {
+ out <<
+ "Ledger " << ledger::version << _(", the command-line accounting tool");
+ out <<
+ _("\n\nCopyright (c) 2003-2009, John Wiegley. All rights reserved.\n\n\
+This program is made available under the terms of the BSD Public License.\n\
+See LICENSE file included with the distribution for details and disclaimer.");
+ out << std::endl;
+ }
+
+ void report_options(report_t& report, std::ostream& out);
+
+ option_t<global_scope_t> * lookup_option(const char * p);
+
+ virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind,
+ const string& name);
+
+ OPTION(global_scope_t, args_only);
+ OPTION(global_scope_t, debug_);
+
+ void visit_man_page() const;
+
+ OPTION_(global_scope_t, full_help, DO() { parent->visit_man_page(); }); // -H
+ OPTION_(global_scope_t, help, DO() { parent->visit_man_page(); }); // -h
+ OPTION_(global_scope_t, help_calc, DO() { parent->visit_man_page(); });
+ OPTION_(global_scope_t, help_comm, DO() { parent->visit_man_page(); });
+ OPTION_(global_scope_t, help_disp, DO() { parent->visit_man_page(); });
+
+ OPTION__
+ (global_scope_t, init_file_, // -i
+
+ CTOR(global_scope_t, init_file_) {
+ if (const char * home_var = std::getenv("HOME"))
+ on(none, (path(home_var) / ".ledgerrc").string());
+ else
+ on(none, path("./.ledgerrc").string());
+ });
+
+ OPTION(global_scope_t, options);
+ OPTION(global_scope_t, script_);
+ OPTION(global_scope_t, trace_);
+ OPTION(global_scope_t, verbose);
+ OPTION(global_scope_t, verify);
+
+ OPTION_(global_scope_t, version, DO() { // -v
+ parent->show_version_info(std::cout);
+ throw int(0); // exit immediately
+ });
+};
+
+void handle_debug_options(int argc, char * argv[]);
+
+} // namespace ledger
+
+#endif // _GLOBAL_H
diff --git a/src/interactive.cc b/src/interactive.cc
new file mode 100644
index 00000000..35382e8f
--- /dev/null
+++ b/src/interactive.cc
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "interactive.h"
+
+namespace ledger {
+
+void interactive_t::verify_arguments() const
+{
+ value_t::sequence_t::const_iterator i;
+
+ const char * p = spec.c_str();
+ const char * label = _("unknown");
+ bool wrong_arg = false;
+ bool dont_skip = false;
+ bool optional = *p == '&';
+ bool exit_loop = *p == '*';
+ std::size_t offset = 1;
+ bool is_seq = args.value().is_sequence();
+ const value_t * next_arg = NULL;
+ string vlabel;
+
+ if (is_seq) {
+ i = args.begin();
+ if (i != args.end())
+ next_arg = &(*i);
+ }
+ else if (! args.value().is_null()) {
+ next_arg = &args.value();
+ }
+
+ for (; ! wrong_arg && ! exit_loop && *p && next_arg; p++) {
+ DEBUG("interactive.verify",
+ "Want " << *p << " got: " << next_arg->label());
+
+ wrong_arg = false;
+ switch (*p) {
+ case 'a':
+ label = _("an amount");
+ wrong_arg = (! next_arg->is_long() &&
+ ! next_arg->is_amount() &&
+ ! next_arg->is_balance());
+ break;
+ case 'b':
+ label = _("a boolean");
+ wrong_arg = false; // booleans are converted
+ break;
+ case 'd':
+ label = _("a date");
+ wrong_arg = (! next_arg->is_date() &&
+ ! next_arg->is_datetime());
+ break;
+ case 't':
+ label = _("a date/time");
+ wrong_arg = (! next_arg->is_date() &&
+ ! next_arg->is_datetime());
+ break;
+ case 'i':
+ case 'l':
+ label = _("an integer");
+ if (next_arg->is_long() ||
+ (next_arg->is_amount() &&
+ ! next_arg->as_amount().has_commodity())) {
+ wrong_arg = false;
+ }
+ else if (next_arg->is_string()) {
+ wrong_arg = false;
+ for (const char * q = next_arg->as_string().c_str(); *q; q++) {
+ if (! std::isdigit(*q) && *q != '-') {
+ wrong_arg = true;
+ break;
+ }
+ }
+ }
+ else {
+ wrong_arg = true;
+ }
+ break;
+ case 'm':
+ label = _("a regex");
+ wrong_arg = ! next_arg->is_mask();
+ break;
+ case 's':
+ label = _("a string");
+ wrong_arg = ! next_arg->is_string();
+ break;
+ case 'v':
+ label = _("any value");
+ wrong_arg = false;
+ break;
+ case '^':
+ label = _("a scope");
+ wrong_arg = ! next_arg->is_scope();
+ break;
+ case 'S':
+ label = _("a sequence");
+ wrong_arg = false;
+ break;
+ case '&':
+ optional = true;
+ dont_skip = true;
+ break;
+ case '*':
+ optional = true;
+ exit_loop = true;
+ dont_skip = true;
+ break;
+ }
+ if (wrong_arg && optional && next_arg->is_null())
+ wrong_arg = false;
+
+ if (wrong_arg)
+ vlabel = next_arg->label();
+
+ if (! dont_skip) {
+ if (is_seq) {
+ if (++i != args.end()) {
+ next_arg = &(*i);
+ offset++;
+ } else {
+ next_arg = NULL;
+ }
+ } else {
+ next_arg = NULL;
+ }
+ }
+ dont_skip = false;
+ }
+
+ if (*p == '&' || *p == '*')
+ optional = true;
+
+ DEBUG("interactive.verify", "Remaining args are optional");
+
+ if (wrong_arg) {
+ throw_(std::logic_error,
+ _("Expected %1 for argument %2, but received %3")
+ << label << offset << vlabel);
+ }
+ else if (*p && ! optional && ! next_arg) {
+ throw_(std::logic_error, _("Too few arguments to function"));
+ }
+ else if (! *p && next_arg) {
+ throw_(std::logic_error, _("Too many arguments to function"));
+ }
+}
+
+string join_args(call_scope_t& args)
+{
+ std::ostringstream buf;
+ bool first = true;
+
+ for (std::size_t i = 0; i < args.size(); i++) {
+ if (first)
+ first = false;
+ else
+ buf << ' ';
+ buf << args[i];
+ }
+
+ return buf.str();
+}
+
+} // namespace ledger
diff --git a/src/interactive.h b/src/interactive.h
new file mode 100644
index 00000000..199b7b71
--- /dev/null
+++ b/src/interactive.h
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup expr
+ */
+
+/**
+ * @file interactive.h
+ * @author John Wiegley
+ *
+ * @ingroup expr
+ */
+#ifndef _INTERACTIVE_H
+#define _INTERACTIVE_H
+
+#include "scope.h"
+
+namespace ledger {
+
+class interactive_t : public noncopyable
+{
+ call_scope_t& args;
+ string spec;
+
+public:
+ explicit interactive_t(call_scope_t& _args, const string& _spec = "")
+ : args(_args), spec(_spec) {
+ TRACE_CTOR(interactive_t, "call_scope_t&, const string&");
+ verify_arguments();
+ }
+ virtual ~interactive_t() {
+ TRACE_DTOR(interactive_t);
+ }
+
+ void verify_arguments() const;
+
+ bool has(std::size_t index) const {
+ if (index < args.size() && ! args[index].is_null())
+ return true;
+ return false;
+ }
+
+ value_t& value_at(std::size_t index) {
+ assert(has(index));
+ return args[index];
+ }
+
+ template <typename T>
+ T get(std::size_t index);
+};
+
+template <>
+inline bool interactive_t::get<bool>(std::size_t index) {
+ return value_at(index).to_boolean();
+}
+template <>
+inline int interactive_t::get<int>(std::size_t index) {
+ return value_at(index).to_int();
+}
+template <>
+inline long interactive_t::get<long>(std::size_t index) {
+ return value_at(index).to_long();
+}
+template <>
+inline amount_t interactive_t::get<amount_t>(std::size_t index) {
+ return value_at(index).to_amount();
+}
+template <>
+inline string interactive_t::get<string>(std::size_t index) {
+ return value_at(index).to_string();
+}
+template <>
+inline mask_t interactive_t::get<mask_t>(std::size_t index) {
+ return value_at(index).to_mask();
+}
+template <>
+inline date_t interactive_t::get<date_t>(std::size_t index) {
+ return value_at(index).to_date();
+}
+template <>
+inline datetime_t interactive_t::get<datetime_t>(std::size_t index) {
+ return value_at(index).to_datetime();
+}
+template <>
+inline value_t::sequence_t&
+interactive_t::get<value_t::sequence_t&>(std::size_t index) {
+ return value_at(index).as_sequence_lval();
+}
+template <>
+inline const value_t::sequence_t&
+interactive_t::get<const value_t::sequence_t&>(std::size_t index) {
+ return value_at(index).as_sequence();
+}
+
+template <typename T>
+class in_context_t : public interactive_t
+{
+ T& context;
+
+public:
+ explicit in_context_t(call_scope_t& args, const string& spec)
+ : interactive_t(args, spec), context(find_scope<T>(args)) {
+ TRACE_CTOR(in_context_t, "call_scope_t&, const string&");
+ }
+ virtual ~in_context_t() {
+ TRACE_DTOR(in_context_t);
+ }
+
+ T& operator *() {
+ return context;
+ }
+ T * operator->() {
+ return &context;
+ }
+};
+
+string join_args(call_scope_t& args);
+
+} // namespace ledger
+
+#endif // _INTERACTIVE_H
+
diff --git a/src/item.cc b/src/item.cc
new file mode 100644
index 00000000..99d1d835
--- /dev/null
+++ b/src/item.cc
@@ -0,0 +1,467 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "item.h"
+#include "interactive.h"
+
+namespace ledger {
+
+bool item_t::use_effective_date = false;
+
+bool item_t::has_tag(const string& tag) const
+{
+ DEBUG("item.meta", "Checking if item has tag: " << tag);
+ if (! metadata) {
+ DEBUG("item.meta", "Item has no metadata at all");
+ return false;
+ }
+ string_map::const_iterator i = metadata->find(tag);
+#if defined(DEBUG_ON)
+ if (SHOW_DEBUG("item.meta")) {
+ if (i == metadata->end())
+ DEBUG("item.meta", "Item does not have this tag");
+ else
+ DEBUG("item.meta", "Item has the tag!");
+ }
+#endif
+ return i != metadata->end();
+}
+
+bool item_t::has_tag(const mask_t& tag_mask,
+ const optional<mask_t>& value_mask) const
+{
+ if (metadata) {
+ foreach (const string_map::value_type& data, *metadata) {
+ if (tag_mask.match(data.first)) {
+ if (! value_mask)
+ return true;
+ else if (data.second)
+ return value_mask->match(*data.second);
+ }
+ }
+ }
+ return false;
+}
+
+optional<string> item_t::get_tag(const string& tag) const
+{
+ DEBUG("item.meta", "Getting item tag: " << tag);
+ if (metadata) {
+ DEBUG("item.meta", "Item has metadata");
+ string_map::const_iterator i = metadata->find(tag);
+ if (i != metadata->end()) {
+ DEBUG("item.meta", "Found the item!");
+ return (*i).second;
+ }
+ }
+ return none;
+}
+
+optional<string> item_t::get_tag(const mask_t& tag_mask,
+ const optional<mask_t>& value_mask) const
+{
+ if (metadata) {
+ foreach (const string_map::value_type& data, *metadata) {
+ if (tag_mask.match(data.first) &&
+ (! value_mask ||
+ (data.second && value_mask->match(*data.second))))
+ return data.second;
+ }
+ }
+ return none;
+}
+
+void item_t::set_tag(const string& tag,
+ const optional<string>& value)
+{
+ if (! metadata)
+ metadata = string_map();
+
+ DEBUG("item.meta", "Setting tag '" << tag << "' to value '"
+ << (value ? *value : string("<none>")) << "'");
+
+ std::pair<string_map::iterator, bool> result
+ = metadata->insert(string_map::value_type(tag, value));
+ assert(result.second);
+}
+
+void item_t::parse_tags(const char * p,
+ optional<date_t::year_type> current_year)
+{
+ if (const char * b = std::strchr(p, '[')) {
+ if (*(b + 1) != '\0' &&
+ (std::isdigit(*(b + 1)) || *(b + 1) == '=')) {
+ if (const char * e = std::strchr(p, ']')) {
+ char buf[256];
+ std::strncpy(buf, b + 1, e - b - 1);
+ buf[e - b - 1] = '\0';
+
+ if (char * p = std::strchr(buf, '=')) {
+ *p++ = '\0';
+ _date_eff = parse_date(p, current_year);
+ }
+ if (buf[0])
+ _date = parse_date(buf, current_year);
+ }
+ }
+ }
+
+ if (! std::strchr(p, ':'))
+ return;
+
+ scoped_array<char> buf(new char[std::strlen(p) + 1]);
+
+ std::strcpy(buf.get(), p);
+
+ string tag;
+ for (char * q = std::strtok(buf.get(), " \t");
+ q;
+ q = std::strtok(NULL, " \t")) {
+ const string::size_type len = std::strlen(q);
+ if (! tag.empty()) {
+ if (! has_tag(tag))
+ set_tag(tag, string(p + (q - buf.get())));
+ break;
+ }
+ else if (q[0] == ':' && q[len - 1] == ':') { // a series of tags
+ for (char * r = std::strtok(q + 1, ":");
+ r;
+ r = std::strtok(NULL, ":"))
+ set_tag(r);
+ }
+ else if (q[len - 1] == ':') { // a metadata setting
+ tag = string(q, len - 1);
+ }
+ }
+}
+
+void item_t::append_note(const char * p,
+ optional<date_t::year_type> current_year)
+{
+ if (note) {
+ *note += '\n';
+ *note += p;
+ } else {
+ note = p;
+ }
+
+ parse_tags(p, current_year);
+}
+
+namespace {
+ value_t get_status(item_t& item) {
+ return long(item.state());
+ }
+ value_t get_uncleared(item_t& item) {
+ return item.state() == item_t::UNCLEARED;
+ }
+ value_t get_cleared(item_t& item) {
+ return item.state() == item_t::CLEARED;
+ }
+ value_t get_pending(item_t& item) {
+ return item.state() == item_t::PENDING;
+ }
+
+ value_t get_actual(item_t& item) {
+ return ! item.has_flags(ITEM_GENERATED | ITEM_TEMP);
+ }
+
+ value_t get_date(item_t& item) {
+ return item.date();
+ }
+ value_t get_effective_date(item_t& item) {
+ if (optional<date_t> effective = item.effective_date())
+ return *effective;
+ return NULL_VALUE;
+ }
+ value_t get_note(item_t& item) {
+ return string_value(item.note ? *item.note : empty_string);
+ }
+
+ value_t has_tag(call_scope_t& args) {
+ item_t& item(find_scope<item_t>(args));
+
+ if (args.size() == 1) {
+ if (args[0].is_string())
+ return item.has_tag(args[0].as_string());
+ else if (args[0].is_mask())
+ return item.has_tag(args[0].as_mask());
+ else
+ throw_(std::runtime_error,
+ _("Expected string or mask for argument 1, but received %1")
+ << args[0].label());
+ }
+ else if (args.size() == 2) {
+ if (args[0].is_mask() && args[1].is_mask())
+ return item.has_tag(args[0].to_mask(), args[1].to_mask());
+ else
+ throw_(std::runtime_error,
+ _("Expected masks for arguments 1 and 2, but received %1 and %2")
+ << args[0].label() << args[1].label());
+ }
+ else if (args.size() == 0) {
+ throw_(std::runtime_error, _("Too few arguments to function"));
+ }
+ else {
+ throw_(std::runtime_error, _("Too many arguments to function"));
+ }
+ return false;
+ }
+
+ value_t get_tag(call_scope_t& scope) {
+ in_context_t<item_t> env(scope, "s");
+ if (optional<string> value = env->get_tag(env.get<string>(0)))
+ return string_value(*value);
+ return string_value(empty_string);
+ }
+
+ value_t get_pathname(item_t& item) {
+ if (item.pos)
+ return string_value(item.pos->pathname.string());
+ else
+ return string_value(empty_string);
+ }
+
+ value_t get_beg_pos(item_t& item) {
+ return item.pos ? long(item.pos->beg_pos) : 0L;
+ }
+
+ value_t get_beg_line(item_t& item) {
+ return item.pos ? long(item.pos->beg_line) : 0L;
+ }
+
+ value_t get_end_pos(item_t& item) {
+ return item.pos ? long(item.pos->end_pos) : 0L;
+ }
+
+ value_t get_end_line(item_t& item) {
+ return item.pos ? long(item.pos->end_line) : 0L;
+ }
+
+ value_t get_depth(item_t&) {
+ return 0L;
+ }
+
+ value_t ignore(item_t&) {
+ return false;
+ }
+
+ template <value_t (*Func)(item_t&)>
+ value_t get_wrapper(call_scope_t& scope) {
+ return (*Func)(find_scope<item_t>(scope));
+ }
+}
+
+value_t get_comment(item_t& item)
+{
+ if (! item.note) {
+ return string_value("");
+ } else {
+ std::ostringstream buf;
+ if (item.note->length() > 15)
+ buf << "\n ;";
+ else
+ buf << " ;";
+
+ bool need_separator = false;
+ for (const char * p = item.note->c_str(); *p; p++) {
+ if (*p == '\n') {
+ need_separator = true;
+ } else {
+ if (need_separator) {
+ buf << "\n ;";
+ need_separator = false;
+ }
+ buf << *p;
+ }
+ }
+ return string_value(buf.str());
+ }
+}
+
+expr_t::ptr_op_t item_t::lookup(const symbol_t::kind_t kind,
+ const string& name)
+{
+ if (kind != symbol_t::FUNCTION)
+ return NULL;
+
+ switch (name[0]) {
+ case 'a':
+ if (name == "actual")
+ return WRAP_FUNCTOR(get_wrapper<&get_actual>);
+ break;
+
+ case 'b':
+ if (name == "beg_line")
+ return WRAP_FUNCTOR(get_wrapper<&get_beg_line>);
+ else if (name == "beg_pos")
+ return WRAP_FUNCTOR(get_wrapper<&get_beg_pos>);
+ break;
+
+ case 'c':
+ if (name == "cleared")
+ return WRAP_FUNCTOR(get_wrapper<&get_cleared>);
+ else if (name == "comment")
+ return WRAP_FUNCTOR(get_wrapper<&get_comment>);
+ break;
+
+ case 'd':
+ if (name[1] == '\0' || name == "date")
+ return WRAP_FUNCTOR(get_wrapper<&get_date>);
+ else if (name == "depth")
+ return WRAP_FUNCTOR(get_wrapper<&get_depth>);
+ break;
+
+ case 'e':
+ if (name == "end_line")
+ return WRAP_FUNCTOR(get_wrapper<&get_end_line>);
+ else if (name == "end_pos")
+ return WRAP_FUNCTOR(get_wrapper<&get_end_pos>);
+ else if (name == "effective_date")
+ return WRAP_FUNCTOR(get_wrapper<&get_effective_date>);
+ break;
+
+ case 'f':
+ if (name == "filename")
+ return WRAP_FUNCTOR(get_wrapper<&get_pathname>);
+ break;
+
+ case 'h':
+ if (name == "has_tag")
+ return WRAP_FUNCTOR(ledger::has_tag);
+ else if (name == "has_meta")
+ return WRAP_FUNCTOR(ledger::has_tag);
+ break;
+
+ case 'i':
+ if (name == "is_account")
+ return WRAP_FUNCTOR(get_wrapper<&ignore>);
+ break;
+
+ case 'm':
+ if (name == "meta")
+ return WRAP_FUNCTOR(ledger::get_tag);
+ break;
+
+ case 'n':
+ if (name == "note")
+ return WRAP_FUNCTOR(get_wrapper<&get_note>);
+ break;
+
+ case 'p':
+ if (name == "pending")
+ return WRAP_FUNCTOR(get_wrapper<&get_pending>);
+ else if (name == "parent")
+ return WRAP_FUNCTOR(get_wrapper<&ignore>);
+ break;
+
+ case 's':
+ if (name == "status")
+ return WRAP_FUNCTOR(get_wrapper<&get_status>);
+ break;
+
+ case 't':
+ if (name == "tag")
+ return WRAP_FUNCTOR(ledger::get_tag);
+ break;
+
+ case 'u':
+ if (name == "uncleared")
+ return WRAP_FUNCTOR(get_wrapper<&get_uncleared>);
+ break;
+
+ case 'L':
+ if (name[1] == '\0')
+ return WRAP_FUNCTOR(get_wrapper<&get_actual>);
+ break;
+
+ case 'X':
+ if (name[1] == '\0')
+ return WRAP_FUNCTOR(get_wrapper<&get_cleared>);
+ break;
+
+ case 'Y':
+ if (name[1] == '\0')
+ return WRAP_FUNCTOR(get_wrapper<&get_pending>);
+ break;
+ }
+
+ return NULL;
+}
+
+bool item_t::valid() const
+{
+ if (_state != UNCLEARED && _state != CLEARED && _state != PENDING) {
+ DEBUG("ledger.validate", "item_t: state is bad");
+ return false;
+ }
+
+ return true;
+}
+
+void print_item(std::ostream& out, const item_t& item, const string& prefix)
+{
+ out << source_context(item.pos->pathname, item.pos->beg_pos,
+ item.pos->end_pos, prefix);
+}
+
+string item_context(const item_t& item, const string& desc)
+{
+ std::streamoff len = item.pos->end_pos - item.pos->beg_pos;
+ if (! len)
+ return _("<no item context>");
+
+ assert(len > 0);
+ assert(len < 2048);
+
+ std::ostringstream out;
+
+ if (item.pos->pathname == path("/dev/stdin")) {
+ out << desc << _(" from standard input:");
+ return out.str();
+ }
+
+ out << desc << _(" from \"") << item.pos->pathname.string() << "\"";
+
+ if (item.pos->beg_line != item.pos->end_line)
+ out << _(", lines ") << item.pos->beg_line << "-"
+ << item.pos->end_line << ":\n";
+ else
+ out << _(", line ") << item.pos->beg_line << ":\n";
+
+ print_item(out, item, "> ");
+
+ return out.str();
+}
+
+} // namespace ledger
diff --git a/src/item.h b/src/item.h
new file mode 100644
index 00000000..84385eb7
--- /dev/null
+++ b/src/item.h
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @defgroup data Data representation
+ */
+
+/**
+ * @file item.h
+ * @author John Wiegley
+ *
+ * @ingroup data
+ */
+#ifndef _ITEM_H
+#define _ITEM_H
+
+#include "scope.h"
+
+namespace ledger {
+
+struct position_t
+{
+ path pathname;
+ istream_pos_type beg_pos;
+ std::size_t beg_line;
+ istream_pos_type end_pos;
+ std::size_t end_line;
+
+ position_t() : beg_pos(0), beg_line(0), end_pos(0), end_line(0) {
+ TRACE_CTOR(position_t, "");
+ }
+ position_t(const position_t& pos) {
+ TRACE_CTOR(position_t, "copy");
+ *this = pos;
+ }
+ ~position_t() throw() {
+ TRACE_DTOR(position_t);
+ }
+
+ position_t& operator=(const position_t& pos) {
+ if (this != &pos) {
+ pathname = pos.pathname;
+ beg_pos = pos.beg_pos;
+ beg_line = pos.beg_line;
+ end_pos = pos.end_pos;
+ end_line = pos.end_line;
+ }
+ return *this;
+ }
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & pathname;
+ ar & beg_pos;
+ ar & beg_line;
+ ar & end_pos;
+ ar & end_line;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+class item_t : public supports_flags<>, public scope_t
+{
+public:
+#define ITEM_NORMAL 0x00 // no flags at all, a basic posting
+#define ITEM_GENERATED 0x01 // posting was not found in a journal
+#define ITEM_TEMP 0x02 // posting is a managed temporary
+
+ enum state_t { UNCLEARED = 0, CLEARED, PENDING };
+
+ typedef std::map<string, optional<string> > string_map;
+
+ state_t _state;
+ optional<date_t> _date;
+ optional<date_t> _date_eff;
+ optional<string> note;
+ optional<position_t> pos;
+ optional<string_map> metadata;
+
+ item_t(flags_t _flags = ITEM_NORMAL, const optional<string>& _note = none)
+ : supports_flags<>(_flags), _state(UNCLEARED), note(_note)
+ {
+ TRACE_CTOR(item_t, "flags_t, const string&");
+ }
+ item_t(const item_t& item) : supports_flags<>(), scope_t()
+ {
+ TRACE_CTOR(item_t, "copy");
+ copy_details(item);
+ }
+ virtual ~item_t() {
+ TRACE_DTOR(item_t);
+ }
+
+ void copy_details(const item_t& item)
+ {
+ set_flags(item.flags());
+ set_state(item.state());
+
+ _date = item._date;
+ _date_eff = item._date_eff;
+ note = item.note;
+ pos = item.pos;
+ metadata = item.metadata;
+ }
+
+ virtual bool operator==(const item_t& xact) {
+ return this == &xact;
+ }
+ virtual bool operator!=(const item_t& xact) {
+ return ! (*this == xact);
+ }
+
+ virtual bool has_tag(const string& tag) const;
+ virtual bool has_tag(const mask_t& tag_mask,
+ const optional<mask_t>& value_mask = none) const;
+
+ virtual optional<string> get_tag(const string& tag) const;
+ virtual optional<string> get_tag(const mask_t& tag_mask,
+ const optional<mask_t>& value_mask = none) const;
+
+ virtual void set_tag(const string& tag,
+ const optional<string>& value = none);
+
+ virtual void parse_tags(const char * p,
+ optional<date_t::year_type> current_year = none);
+ virtual void append_note(const char * p,
+ optional<date_t::year_type> current_year = none);
+
+ static bool use_effective_date;
+
+ virtual date_t date() const {
+ assert(_date);
+ if (use_effective_date)
+ if (optional<date_t> effective = effective_date())
+ return *effective;
+ return *_date;
+ }
+ virtual optional<date_t> effective_date() const {
+ return _date_eff;
+ }
+
+ void set_state(state_t new_state) {
+ _state = new_state;
+ }
+ virtual state_t state() const {
+ return _state;
+ }
+
+ virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind,
+ const string& name);
+
+ bool valid() const;
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & boost::serialization::base_object<supports_flags<> >(*this);
+ ar & boost::serialization::base_object<scope_t>(*this);
+ ar & _state;
+ ar & _date;
+ ar & _date_eff;
+ ar & note;
+ ar & pos;
+ ar & metadata;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+value_t get_comment(item_t& item);
+void print_item(std::ostream& out, const item_t& item,
+ const string& prefix = "");
+string item_context(const item_t& item, const string& desc);
+
+} // namespace ledger
+
+#endif // _ITEM_H
diff --git a/src/iterators.cc b/src/iterators.cc
new file mode 100644
index 00000000..540ba8ae
--- /dev/null
+++ b/src/iterators.cc
@@ -0,0 +1,258 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "iterators.h"
+#include "journal.h"
+#include "compare.h"
+
+namespace ledger {
+
+void xacts_iterator::reset(journal_t& journal)
+{
+ xacts_i = journal.xacts.begin();
+ xacts_end = journal.xacts.end();
+ xacts_uninitialized = false;
+}
+
+xact_t * xacts_iterator::operator()()
+{
+ if (xacts_i != xacts_end)
+ return *xacts_i++;
+ else
+ return NULL;
+}
+
+void journal_posts_iterator::reset(journal_t& journal)
+{
+ xacts.reset(journal);
+
+ xact_t * xact = xacts();
+ if (xact != NULL)
+ posts.reset(*xact);
+}
+
+post_t * journal_posts_iterator::operator()()
+{
+ post_t * post = posts();
+ if (post == NULL) {
+ xact_t * xact = xacts();
+ if (xact != NULL) {
+ posts.reset(*xact);
+ post = posts();
+ }
+ }
+ return post;
+}
+
+void posts_commodities_iterator::reset(journal_t& journal)
+{
+ journal_posts.reset(journal);
+
+ std::set<commodity_t *> commodities;
+
+ for (post_t * post = journal_posts(); post; post = journal_posts()) {
+ commodity_t& comm(post->amount.commodity());
+ if (comm.flags() & COMMODITY_NOMARKET)
+ continue;
+ commodities.insert(&comm);
+ }
+
+ std::map<string, xact_t *> xacts_by_commodity;
+
+ foreach (commodity_t * comm, commodities) {
+ if (optional<commodity_t::varied_history_t&> history =
+ comm->varied_history()) {
+ account_t * account = journal.master->find_account(comm->symbol());
+
+ foreach (commodity_t::history_by_commodity_map::value_type pair,
+ history->histories) {
+ foreach (commodity_t::history_map::value_type hpair,
+ pair.second.prices) {
+ xact_t * xact;
+ string symbol = hpair.second.commodity().symbol();
+
+ std::map<string, xact_t *>::iterator i =
+ xacts_by_commodity.find(symbol);
+ if (i != xacts_by_commodity.end()) {
+ xact = (*i).second;
+ } else {
+ xact = &temps.create_xact();
+ xact_temps.push_back(xact);
+ xact->payee = symbol;
+ xact->_date = hpair.first.date();
+ xacts_by_commodity.insert
+ (std::pair<string, xact_t *>(symbol, xact));
+ }
+
+ bool post_already_exists = false;
+
+ foreach (post_t * post, xact->posts) {
+ if (post->_date == hpair.first.date() &&
+ post->amount == hpair.second) {
+ post_already_exists = true;
+ break;
+ }
+ }
+
+ if (! post_already_exists) {
+ post_t& temp = temps.create_post(*xact, account);
+ temp._date = hpair.first.date();
+ temp.amount = hpair.second;
+
+ temp.xdata().datetime = hpair.first;
+ }
+ }
+ }
+ }
+ }
+
+ xacts.xacts_i = xact_temps.begin();
+ xacts.xacts_end = xact_temps.end();
+
+ xacts.xacts_uninitialized = false;
+
+ xact_t * xact = xacts();
+ if (xact != NULL)
+ posts.reset(*xact);
+}
+
+post_t * posts_commodities_iterator::operator()()
+{
+ post_t * post = posts();
+ if (post == NULL) {
+ xact_t * xact = xacts();
+ if (xact != NULL) {
+ posts.reset(*xact);
+ post = posts();
+ }
+ }
+ return post;
+}
+
+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::push_back(account_t& account)
+{
+ accounts_list.push_back(accounts_deque_t());
+
+ if (flatten_all) {
+ push_all(account, accounts_list.back());
+
+ std::stable_sort(accounts_list.back().begin(),
+ accounts_list.back().end(),
+ compare_items<account_t>(sort_cmp));
+
+#if defined(DEBUG_ON)
+ if (SHOW_DEBUG("accounts.sorted")) {
+ foreach (account_t * account, accounts_list.back())
+ DEBUG("accounts.sorted",
+ "Account (flat): " << account->fullname());
+ }
+#endif
+ } else {
+ sort_accounts(account, accounts_list.back());
+ }
+
+ sorted_accounts_i.push_back(accounts_list.back().begin());
+ sorted_accounts_end.push_back(accounts_list.back().end());
+}
+
+void sorted_accounts_iterator::push_all(account_t& account,
+ accounts_deque_t& deque)
+{
+ foreach (accounts_map::value_type& pair, account.accounts) {
+ deque.push_back(pair.second);
+ push_all(*pair.second, deque);
+ }
+}
+
+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));
+
+#if defined(DEBUG_ON)
+ if (SHOW_DEBUG("accounts.sorted")) {
+ foreach (account_t * account, deque)
+ DEBUG("accounts.sorted", "Account: " << account->fullname());
+ }
+#endif
+}
+
+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 (! flatten_all && ! account->accounts.empty())
+ push_back(*account);
+
+ // Make sure the sorting value gets recalculated for this account
+ account->xdata().drop_flags(ACCOUNT_EXT_SORT_CALC);
+ return account;
+}
+
+} // namespace ledger
diff --git a/src/iterators.h b/src/iterators.h
new file mode 100644
index 00000000..1cbe4c25
--- /dev/null
+++ b/src/iterators.h
@@ -0,0 +1,230 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup data
+ */
+
+/**
+ * @file iterators.h
+ * @author John Wiegley
+ *
+ * @ingroup data
+ */
+#ifndef _ITERATORS_H
+#define _ITERATORS_H
+
+#include "xact.h"
+#include "post.h"
+#include "account.h"
+#include "temps.h"
+
+namespace ledger {
+
+class journal_t;
+
+class posts_iterator : public noncopyable
+{
+public:
+ virtual ~posts_iterator() throw() {}
+ virtual post_t * operator()() = 0;
+};
+
+class xact_posts_iterator : public posts_iterator
+{
+ posts_list::iterator posts_i;
+ posts_list::iterator posts_end;
+
+ bool posts_uninitialized;
+
+public:
+ xact_posts_iterator() : posts_uninitialized(true) {
+ TRACE_CTOR(xact_posts_iterator, "");
+ }
+ xact_posts_iterator(xact_t& xact)
+ : posts_uninitialized(true) {
+ TRACE_CTOR(xact_posts_iterator, "xact_t&");
+ reset(xact);
+ }
+ virtual ~xact_posts_iterator() throw() {
+ TRACE_DTOR(xact_posts_iterator);
+ }
+
+ void reset(xact_t& xact) {
+ posts_i = xact.posts.begin();
+ posts_end = xact.posts.end();
+
+ posts_uninitialized = false;
+ }
+
+ virtual post_t * operator()() {
+ if (posts_uninitialized || posts_i == posts_end)
+ return NULL;
+ return *posts_i++;
+ }
+};
+
+class xacts_iterator : public noncopyable
+{
+public:
+ xacts_list::iterator xacts_i;
+ xacts_list::iterator xacts_end;
+
+ bool xacts_uninitialized;
+
+ xacts_iterator() : xacts_uninitialized(true) {
+ TRACE_CTOR(xacts_iterator, "");
+ }
+ xacts_iterator(journal_t& journal) : xacts_uninitialized(true) {
+ TRACE_CTOR(xacts_iterator, "journal_t&");
+ reset(journal);
+ }
+ virtual ~xacts_iterator() throw() {
+ TRACE_DTOR(xacts_iterator);
+ }
+
+ void reset(journal_t& journal);
+
+ xact_t * operator()();
+};
+
+class journal_posts_iterator : public posts_iterator
+{
+ xacts_iterator xacts;
+ xact_posts_iterator posts;
+
+public:
+ journal_posts_iterator() {
+ TRACE_CTOR(journal_posts_iterator, "");
+ }
+ journal_posts_iterator(journal_t& journal) {
+ TRACE_CTOR(journal_posts_iterator, "journal_t&");
+ reset(journal);
+ }
+ virtual ~journal_posts_iterator() throw() {
+ TRACE_DTOR(journal_posts_iterator);
+ }
+
+ void reset(journal_t& journal);
+
+ virtual post_t * operator()();
+};
+
+class posts_commodities_iterator : public posts_iterator
+{
+protected:
+ journal_posts_iterator journal_posts;
+ xacts_iterator xacts;
+ xact_posts_iterator posts;
+ temporaries_t temps;
+ xacts_list xact_temps;
+
+public:
+ posts_commodities_iterator() {
+ TRACE_CTOR(posts_commodities_iterator, "");
+ }
+ posts_commodities_iterator(journal_t& journal) {
+ TRACE_CTOR(posts_commodities_iterator, "journal_t&");
+ reset(journal);
+ }
+ virtual ~posts_commodities_iterator() throw() {
+ TRACE_DTOR(posts_commodities_iterator);
+ }
+
+ void reset(journal_t& journal);
+
+ virtual post_t * operator()();
+};
+
+class accounts_iterator : public noncopyable
+{
+public:
+ virtual ~accounts_iterator() throw() {}
+ 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;
+ bool flatten_all;
+
+ 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(account_t& account,
+ const expr_t& _sort_cmp, bool _flatten_all)
+ : sort_cmp(_sort_cmp), flatten_all(_flatten_all) {
+ TRACE_CTOR(sorted_accounts_iterator, "const expr_t&, bool, account_t&");
+ push_back(account);
+ }
+ virtual ~sorted_accounts_iterator() throw() {
+ TRACE_DTOR(sorted_accounts_iterator);
+ }
+
+ void push_back(account_t& account);
+ void push_all(account_t& account, accounts_deque_t& deque);
+ void sort_accounts(account_t& account, accounts_deque_t& deque);
+
+ virtual account_t * operator()();
+};
+
+} // namespace ledger
+
+#endif // _ITERATORS_H
diff --git a/src/journal.cc b/src/journal.cc
new file mode 100644
index 00000000..6ebccd66
--- /dev/null
+++ b/src/journal.cc
@@ -0,0 +1,251 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "journal.h"
+#include "amount.h"
+#include "commodity.h"
+#include "pool.h"
+#include "xact.h"
+#include "account.h"
+
+namespace ledger {
+
+journal_t::journal_t()
+{
+ TRACE_CTOR(journal_t, "");
+ initialize();
+}
+
+journal_t::journal_t(const path& pathname)
+{
+ TRACE_CTOR(journal_t, "path");
+ initialize();
+ read(pathname);
+}
+
+journal_t::journal_t(const string& str)
+{
+ TRACE_CTOR(journal_t, "string");
+ initialize();
+ read(str);
+}
+
+journal_t::~journal_t()
+{
+ TRACE_DTOR(journal_t);
+
+ // Don't bother unhooking each xact's posts from the
+ // accounts they refer to, because all accounts are about to
+ // be deleted.
+ foreach (xact_t * xact, xacts)
+ checked_delete(xact);
+
+ foreach (auto_xact_t * xact, auto_xacts)
+ checked_delete(xact);
+
+ foreach (period_xact_t * xact, period_xacts)
+ checked_delete(xact);
+
+ checked_delete(master);
+}
+
+void journal_t::initialize()
+{
+ master = new account_t;
+ bucket = NULL;
+ was_loaded = false;
+}
+
+void journal_t::add_account(account_t * acct)
+{
+ master->add_account(acct);
+}
+
+bool journal_t::remove_account(account_t * acct)
+{
+ return master->remove_account(acct);
+}
+
+account_t * journal_t::find_account(const string& name, bool auto_create)
+{
+ return master->find_account(name, auto_create);
+}
+
+account_t * journal_t::find_account_re(const string& regexp)
+{
+ return master->find_account_re(regexp);
+}
+
+bool journal_t::add_xact(xact_t * xact)
+{
+ xact->journal = this;
+
+ if (! xact->finalize()) {
+ xact->journal = NULL;
+ return false;
+ }
+
+ extend_xact(xact);
+ xacts.push_back(xact);
+
+ return true;
+}
+
+void journal_t::extend_xact(xact_base_t * xact)
+{
+ foreach (auto_xact_t * auto_xact, auto_xacts)
+ auto_xact->extend_xact(*xact);
+}
+
+bool journal_t::remove_xact(xact_t * xact)
+{
+ bool found = false;
+ xacts_list::iterator i;
+ for (i = xacts.begin(); i != xacts.end(); i++)
+ if (*i == xact) {
+ found = true;
+ break;
+ }
+ if (! found)
+ return false;
+
+ xacts.erase(i);
+ xact->journal = NULL;
+
+ return true;
+}
+
+std::size_t journal_t::read(std::istream& in,
+ const path& pathname,
+ account_t * master_alt,
+ scope_t * scope)
+{
+ std::size_t count = 0;
+ try {
+ if (! scope)
+ scope = scope_t::default_scope;
+
+ if (! scope)
+ throw_(std::runtime_error,
+ _("No default scope in which to read journal file '%1'")
+ << pathname);
+
+ value_t strict = expr_t("strict").calc(*scope);
+
+ count = parse(in, *scope, master_alt ? master_alt : master,
+ &pathname, strict.to_boolean());
+ }
+ catch (...) {
+ clear_xdata();
+ throw;
+ }
+
+ // xdata may have been set for some accounts and transaction due to the use
+ // of balance assertions or other calculations performed in valexpr-based
+ // posting amounts.
+ clear_xdata();
+
+ return count;
+}
+
+std::size_t journal_t::read(const path& pathname,
+ account_t * master,
+ scope_t * scope)
+{
+ path filename = resolve_path(pathname);
+
+ if (! exists(filename))
+ throw_(std::runtime_error,
+ _("Cannot read journal file '%1'") << filename);
+
+ ifstream stream(filename);
+ std::size_t count = read(stream, filename, master, scope);
+ if (count > 0)
+ sources.push_back(fileinfo_t(filename));
+ return count;
+}
+
+bool journal_t::has_xdata()
+{
+ foreach (xact_t * xact, xacts)
+ if (xact->has_xdata())
+ return true;
+
+ foreach (auto_xact_t * xact, auto_xacts)
+ if (xact->has_xdata())
+ return true;
+
+ foreach (period_xact_t * xact, period_xacts)
+ if (xact->has_xdata())
+ return true;
+
+ if (master->has_xdata() || master->children_with_xdata())
+ return true;
+
+ return false;
+}
+
+void journal_t::clear_xdata()
+{
+ foreach (xact_t * xact, xacts)
+ if (! xact->has_flags(ITEM_TEMP))
+ xact->clear_xdata();
+
+ foreach (auto_xact_t * xact, auto_xacts)
+ if (! xact->has_flags(ITEM_TEMP))
+ xact->clear_xdata();
+
+ foreach (period_xact_t * xact, period_xacts)
+ if (! xact->has_flags(ITEM_TEMP))
+ xact->clear_xdata();
+
+ master->clear_xdata();
+}
+
+bool journal_t::valid() const
+{
+ if (! master->valid()) {
+ DEBUG("ledger.validate", "journal_t: master not valid");
+ return false;
+ }
+
+ foreach (const xact_t * xact, xacts)
+ if (! xact->valid()) {
+ DEBUG("ledger.validate", "journal_t: xact not valid");
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace ledger
diff --git a/src/journal.h b/src/journal.h
new file mode 100644
index 00000000..8d59e3b4
--- /dev/null
+++ b/src/journal.h
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup data
+ */
+
+/**
+ * @file journal.h
+ * @author John Wiegley
+ *
+ * @ingroup data
+ */
+#ifndef _JOURNAL_H
+#define _JOURNAL_H
+
+#include "utils.h"
+#include "times.h"
+
+namespace ledger {
+
+class xact_base_t;
+class xact_t;
+class auto_xact_t;
+class period_xact_t;
+class account_t;
+class scope_t;
+
+typedef std::list<xact_t *> xacts_list;
+typedef std::list<auto_xact_t *> auto_xacts_list;
+typedef std::list<period_xact_t *> period_xacts_list;
+
+class journal_t : public noncopyable
+{
+public:
+ struct fileinfo_t
+ {
+ optional<path> filename;
+ uintmax_t size;
+ datetime_t modtime;
+ bool from_stream;
+
+ fileinfo_t() : size(0), from_stream(true) {
+ TRACE_CTOR(journal_t::fileinfo_t, "");
+ }
+ fileinfo_t(const path& _filename)
+ : filename(_filename), from_stream(false) {
+ TRACE_CTOR(journal_t::fileinfo_t, "const path&");
+ size = file_size(*filename);
+ modtime = posix_time::from_time_t(last_write_time(*filename));
+ }
+ fileinfo_t(const fileinfo_t& info)
+ : filename(info.filename), size(info.size),
+ modtime(info.modtime), from_stream(info.from_stream)
+ {
+ TRACE_CTOR(journal_t::fileinfo_t, "copy");
+ }
+ ~fileinfo_t() throw() {
+ TRACE_DTOR(journal_t::fileinfo_t);
+ }
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+ private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & filename;
+ ar & size;
+ ar & modtime;
+ ar & from_stream;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+ };
+
+ account_t * master;
+ account_t * bucket;
+ xacts_list xacts;
+ auto_xacts_list auto_xacts;
+ period_xacts_list period_xacts;
+ std::list<fileinfo_t> sources;
+ bool was_loaded;
+
+ journal_t();
+ journal_t(const path& pathname);
+ journal_t(const string& str);
+ ~journal_t();
+
+ void initialize();
+
+ std::list<fileinfo_t>::iterator sources_begin() {
+ return sources.begin();
+ }
+ std::list<fileinfo_t>::iterator sources_end() {
+ return sources.end();
+ }
+
+ // These four methods are delegated to the current session, 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_xact(xact_t * xact);
+ void extend_xact(xact_base_t * xact);
+ bool remove_xact(xact_t * xact);
+
+ xacts_list::iterator xacts_begin() {
+ return xacts.begin();
+ }
+ xacts_list::iterator xacts_end() {
+ return xacts.end();
+ }
+ auto_xacts_list::iterator auto_xacts_begin() {
+ return auto_xacts.begin();
+ }
+ auto_xacts_list::iterator auto_xacts_end() {
+ return auto_xacts.end();
+ }
+ period_xacts_list::iterator period_xacts_begin() {
+ return period_xacts.begin();
+ }
+ period_xacts_list::iterator period_xacts_end() {
+ return period_xacts.end();
+ }
+
+ std::size_t read(std::istream& in,
+ const path& pathname,
+ account_t * master = NULL,
+ scope_t * scope = NULL);
+ std::size_t read(const path& pathname,
+ account_t * master = NULL,
+ scope_t * scope = NULL);
+
+ std::size_t parse(std::istream& in,
+ scope_t& session_scope,
+ account_t * master = NULL,
+ const path * original_file = NULL,
+ bool strict = false);
+
+ bool has_xdata();
+ void clear_xdata();
+
+ bool valid() const;
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & master;
+ ar & bucket;
+ ar & xacts;
+ ar & auto_xacts;
+ ar & period_xacts;
+ ar & sources;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+} // namespace ledger
+
+#endif // _JOURNAL_H
diff --git a/src/main.cc b/src/main.cc
new file mode 100644
index 00000000..0aec8886
--- /dev/null
+++ b/src/main.cc
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "global.h" // This is where the meat of main() is, which
+ // was moved there for the sake of clarity here
+#include "session.h"
+
+using namespace ledger;
+
+#ifdef HAVE_BOOST_PYTHON
+namespace ledger {
+ extern char * argv0;
+}
+#endif
+
+int main(int argc, char * argv[], char * envp[])
+{
+ int status = 1;
+
+#ifdef HAVE_BOOST_PYTHON
+ argv0 = argv[0];
+#endif
+
+ // The very first thing we do is handle some very special command-line
+ // options, since they affect how the environment is setup:
+ //
+ // --verify ; turns on memory tracing
+ // --verbose ; turns on logging
+ // --debug CATEGORY ; turns on debug logging
+ // --trace LEVEL ; turns on trace logging
+ handle_debug_options(argc, argv);
+#if defined(VERIFY_ON)
+ IF_VERIFY() initialize_memory_tracing();
+#endif
+
+ INFO("Ledger starting");
+
+ // Initialize global Boost/C++ environment
+ std::ios::sync_with_stdio(false);
+ filesystem::path::default_name_check(filesystem::portable_posix_name);
+
+ std::signal(SIGINT, sigint_handler);
+ std::signal(SIGPIPE, sigpipe_handler);
+
+#if defined(HAVE_GETTEXT)
+ ::textdomain("ledger");
+#endif
+
+ std::auto_ptr<global_scope_t> global_scope;
+
+ try {
+ // Create the session object, which maintains nearly all state relating to
+ // this invocation of Ledger; and register all known journal parsers.
+ global_scope.reset(new global_scope_t(envp));
+
+ global_scope->session().set_flush_on_next_data_file(true);
+
+ // Construct an STL-style argument list from the process command arguments
+ strings_list args;
+ for (int i = 1; i < argc; i++)
+ args.push_back(argv[i]);
+
+ // Look for options and a command verb in the command-line arguments
+ bind_scope_t bound_scope(*global_scope.get(), global_scope->report());
+
+ args = global_scope->read_command_arguments(bound_scope, args);
+
+ if (global_scope->HANDLED(script_)) {
+ // Ledger is being invoked as a script command interpreter
+ global_scope->session().read_journal_files();
+
+ status = 0;
+
+ ifstream in(global_scope->HANDLER(script_).str());
+ while (status == 0 && ! in.eof()) {
+ char line[1024];
+ in.getline(line, 1023);
+
+ char * p = skip_ws(line);
+ if (*p && *p != '#')
+ status = global_scope->execute_command_wrapper(split_arguments(p),
+ true);
+ }
+ }
+ else if (! args.empty()) {
+ // User has invoke a verb at the interactive command-line
+ status = global_scope->execute_command_wrapper(args, false);
+ }
+ else {
+ // Commence the REPL by displaying the current Ledger version
+ global_scope->show_version_info(std::cout);
+
+ global_scope->session().read_journal_files();
+
+ bool exit_loop = false;
+
+#ifdef HAVE_LIBEDIT
+
+ rl_readline_name = const_cast<char *>("Ledger");
+#if 0
+ // jww (2009-02-05): NYI
+ rl_attempted_completion_function = ledger_completion;
+#endif
+
+ while (char * p = readline(global_scope->prompt_string())) {
+ char * expansion = NULL;
+ int result;
+
+ result = history_expand(skip_ws(p), &expansion);
+
+ if (result < 0 || result == 2) {
+ if (expansion)
+ std::free(expansion);
+ std::free(p);
+ throw_(std::logic_error,
+ _("Failed to expand history reference '%1'") << p);
+ }
+ else if (expansion) {
+ add_history(expansion);
+ }
+
+#else // HAVE_LIBEDIT
+
+ while (! std::cin.eof()) {
+ std::cout << global_scope->prompt_string();
+ char line[1024];
+ std::cin.getline(line, 1023);
+
+ char * p = skip_ws(line);
+
+#endif // HAVE_LIBEDIT
+
+ check_for_signal();
+
+ if (*p && *p != '#') {
+ if (std::strncmp(p, "quit", 4) == 0)
+ exit_loop = true;
+ else
+ global_scope->execute_command_wrapper(split_arguments(p), true);
+ }
+
+#ifdef HAVE_LIBEDIT
+ if (expansion)
+ std::free(expansion);
+ std::free(p);
+#endif
+
+ if (exit_loop)
+ break;
+ }
+
+ status = 0; // report success
+ }
+ }
+ catch (const std::exception& err) {
+ if (global_scope.get())
+ global_scope->report_error(err);
+ else
+ std::cerr << "Exception during initialization: " << err.what()
+ << std::endl;
+ }
+ catch (int _status) {
+ status = _status; // used for a "quick" exit, and is used only
+ // if help text (such as --help) was displayed
+ }
+
+ // If memory verification is being performed (which can be very slow), clean
+ // up everything by closing the session and deleting the session object, and
+ // then shutting down the memory tracing subsystem. Otherwise, let it all
+ // leak because we're about to exit anyway.
+ IF_VERIFY() {
+ global_scope.reset();
+
+ INFO("Ledger ended (Boost/libstdc++ may still hold memory)");
+#if defined(VERIFY_ON)
+ shutdown_memory_tracing();
+#endif
+ } else {
+ INFO("Ledger ended");
+ }
+
+ // Return the final status to the operating system, either 1 for error or 0
+ // for a successful completion.
+ return status;
+}
+
+// main.cc ends here.
diff --git a/src/mask.cc b/src/mask.cc
new file mode 100644
index 00000000..c1e66ced
--- /dev/null
+++ b/src/mask.cc
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "mask.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)
+{
+#if defined(HAVE_BOOST_REGEX_UNICODE)
+ expr = boost::make_u32regex(pat.c_str(), boost::regex::perl | boost::regex::icase);
+#else
+ expr.assign(pat.c_str(), boost::regex::perl | boost::regex::icase);
+#endif
+ VERIFY(valid());
+ return *this;
+}
+
+} // namespace ledger
diff --git a/src/mask.h b/src/mask.h
new file mode 100644
index 00000000..4608898f
--- /dev/null
+++ b/src/mask.h
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup util
+ */
+
+/**
+ * @file mask.h
+ * @author John Wiegley
+ *
+ * @ingroup util
+ *
+ * @brief Regular expression masking.
+ */
+#ifndef _MASK_H
+#define _MASK_H
+
+#include "utils.h"
+#if defined(HAVE_BOOST_REGEX_UNICODE)
+#include "unistring.h"
+#endif
+
+namespace ledger {
+
+class mask_t
+{
+public:
+#if defined(HAVE_BOOST_REGEX_UNICODE)
+ boost::u32regex expr;
+#else
+ boost::regex expr;
+#endif
+
+ explicit mask_t(const string& pattern);
+
+ mask_t() : expr() {
+ TRACE_CTOR(mask_t, "");
+ }
+ 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 operator==(const mask_t& other) const {
+ return expr == other.expr;
+ }
+
+ bool match(const string& text) const {
+#if defined(HAVE_BOOST_REGEX_UNICODE)
+ DEBUG("mask.match",
+ "Matching: \"" << text << "\" =~ /" << str() << "/ = "
+ << (boost::u32regex_search(text, expr) ? "true" : "false"));
+ return boost::u32regex_search(text, expr);
+#else
+ DEBUG("mask.match",
+ "Matching: \"" << text << "\" =~ /" << str() << "/ = "
+ << (boost::regex_search(text, expr) ? "true" : "false"));
+ return boost::regex_search(text, expr);
+#endif
+ }
+
+ bool empty() const {
+ return expr.empty();
+ }
+
+ string str() const {
+ if (! empty()) {
+#if defined(HAVE_BOOST_REGEX_UNICODE)
+ assert(sizeof(boost::uint32_t) == sizeof(UChar32));
+ unistring ustr;
+ std::basic_string<UChar32> expr_str = expr.str();
+ std::copy(expr_str.begin(), expr_str.end(),
+ std::back_inserter(ustr.utf32chars));
+ return ustr.extract();
+#else
+ return expr.str();
+#endif
+ } else {
+ return empty_string;
+ }
+ }
+
+ bool valid() const {
+ if (expr.status() != 0) {
+ DEBUG("ledger.validate", "mask_t: expr.status() != 0");
+ return false;
+ }
+ return true;
+ }
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ string temp;
+ if (Archive::is_loading::value) {
+ ar & temp;
+ *this = temp;
+ } else {
+ temp = str();
+ ar & temp;
+ }
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+inline std::ostream& operator<<(std::ostream& out, const mask_t& mask) {
+ out << mask.str();
+ return out;
+}
+
+inline void to_xml(std::ostream& out, const mask_t& mask)
+{
+ push_xml x(out, "mask");
+ out << x.guard(mask.str());
+}
+
+} // namespace ledger
+
+#endif // _MASK_H
diff --git a/src/op.cc b/src/op.cc
new file mode 100644
index 00000000..f38fc86b
--- /dev/null
+++ b/src/op.cc
@@ -0,0 +1,732 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "op.h"
+#include "scope.h"
+#include "commodity.h"
+#include "pool.h"
+
+namespace ledger {
+
+expr_t::ptr_op_t expr_t::op_t::compile(scope_t& scope, const int depth)
+{
+ if (is_ident()) {
+ DEBUG("expr.compile", "lookup: " << as_ident());
+
+ if (ptr_op_t def = scope.lookup(symbol_t::FUNCTION, as_ident())) {
+ // Identifier references are first looked up at the point of
+ // definition, and then at the point of every use if they could
+ // not be found there.
+ if (SHOW_DEBUG("expr.compile")) {
+ DEBUG("expr.compile", "Found definition:");
+ def->dump(*_log_stream, 0);
+ }
+ return copy(def);
+ }
+ else if (left()) {
+ return copy();
+ }
+ return this;
+ }
+
+ if (kind < TERMINALS)
+ return this;
+
+ if (kind == O_DEFINE) {
+ switch (left()->kind) {
+ case IDENT:
+ scope.define(symbol_t::FUNCTION, left()->as_ident(), right());
+ break;
+ case O_CALL:
+ if (left()->left()->is_ident())
+ scope.define(symbol_t::FUNCTION, left()->left()->as_ident(), this);
+ else
+ throw_(compile_error, _("Invalid function definition"));
+ break;
+ default:
+ throw_(compile_error, _("Invalid function definition"));
+ }
+ return wrap_value(value_t());
+ }
+
+ ptr_op_t lhs(left()->compile(scope, depth));
+ ptr_op_t rhs(kind > UNARY_OPERATORS && has_right() ?
+ (kind == O_LOOKUP ? right() :
+ right()->compile(scope, depth)) : NULL);
+
+ if (lhs == left() && (! rhs || rhs == right()))
+ return this;
+
+ ptr_op_t intermediate(copy(lhs, rhs));
+
+ // Reduce constants immediately if possible
+ if ((! lhs || lhs->is_value()) && (! rhs || rhs->is_value()))
+ return wrap_value(intermediate->calc(scope, NULL, depth));
+
+ return intermediate;
+}
+
+value_t expr_t::op_t::calc(scope_t& scope, ptr_op_t * locus, const int depth)
+{
+#if defined(DEBUG_ON)
+ bool skip_debug = false;
+#endif
+ try {
+
+ value_t result;
+
+ switch (kind) {
+ case VALUE:
+ result = as_value();
+ break;
+
+ case IDENT: {
+ if (! left())
+ throw_(calc_error, _("Unknown identifier '%1'") << as_ident());
+
+ // Evaluating an identifier is the same as calling its definition
+ // directly, so we create an empty call_scope_t to reflect the scope for
+ // this implicit call.
+ call_scope_t call_args(scope);
+ result = left()->compile(call_args, depth + 1)
+ ->calc(call_args, locus, depth + 1);
+ break;
+ }
+
+ 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);
+ result = as_function()(call_args);
+#if defined(DEBUG_ON)
+ skip_debug = true;
+#endif
+ break;
+ }
+
+ case O_DEFINE: {
+ call_scope_t& call_args(downcast<call_scope_t>(scope));
+ std::size_t args_count = call_args.size();
+ std::size_t args_index = 0;
+
+ assert(left()->kind == O_CALL);
+
+ for (ptr_op_t sym = left()->right();
+ sym;
+ sym = sym->has_right() ? sym->right() : NULL) {
+ ptr_op_t varname = sym;
+ if (sym->kind == O_CONS)
+ varname = sym->left();
+
+ if (! varname->is_ident())
+ throw_(calc_error, _("Invalid function definition"));
+ else if (args_index == args_count)
+ scope.define(symbol_t::FUNCTION, varname->as_ident(),
+ wrap_value(false));
+ else
+ scope.define(symbol_t::FUNCTION, varname->as_ident(),
+ wrap_value(call_args[args_index++]));
+ }
+
+ if (args_index < args_count)
+ throw_(calc_error,
+ _("Too many arguments in function call (saw %1)") << args_count);
+
+ result = right()->calc(scope, locus, depth + 1);
+ break;
+ }
+
+ case O_LOOKUP:
+ if (value_t obj = left()->calc(scope, locus, depth + 1)) {
+ if (obj.is_scope()) {
+ if (obj.as_scope() == NULL) {
+ throw_(calc_error, _("Left operand of . operator is NULL"));
+ } else {
+ scope_t& objscope(*obj.as_scope());
+ if (ptr_op_t member =
+ objscope.lookup(symbol_t::FUNCTION, right()->as_ident())) {
+ result = member->calc(objscope, NULL, depth + 1);
+ break;
+ }
+ }
+ }
+ }
+ if (right()->kind != IDENT)
+ throw_(calc_error,
+ _("Right operand of . operator must be an identifier"));
+ else
+ throw_(calc_error,
+ _("Failed to lookup member '%1'") << right()->as_ident());
+ break;
+
+ case O_CALL: {
+ call_scope_t call_args(scope);
+
+ if (has_right())
+ call_args.set_args(right()->calc(scope, locus, depth + 1));
+
+ ptr_op_t func = left();
+ const string& name(func->as_ident());
+
+ func = func->left();
+ if (! func)
+ throw_(calc_error, _("Calling unknown function '%1'") << name);
+
+ if (func->is_function())
+ result = func->as_function()(call_args);
+ else
+ result = func->calc(call_args, locus, depth + 1);
+ break;
+ }
+
+ case O_MATCH:
+ result = (right()->calc(scope, locus, depth + 1).as_mask()
+ .match(left()->calc(scope, locus, depth + 1).to_string()));
+ break;
+
+ case O_EQ:
+ result = (left()->calc(scope, locus, depth + 1) ==
+ right()->calc(scope, locus, depth + 1));
+ break;
+ case O_LT:
+ result = (left()->calc(scope, locus, depth + 1) <
+ right()->calc(scope, locus, depth + 1));
+ break;
+ case O_LTE:
+ result = (left()->calc(scope, locus, depth + 1) <=
+ right()->calc(scope, locus, depth + 1));
+ break;
+ case O_GT:
+ result = (left()->calc(scope, locus, depth + 1) >
+ right()->calc(scope, locus, depth + 1));
+ break;
+ case O_GTE:
+ result = (left()->calc(scope, locus, depth + 1) >=
+ right()->calc(scope, locus, depth + 1));
+ break;
+
+ case O_ADD:
+ result = (left()->calc(scope, locus, depth + 1) +
+ right()->calc(scope, locus, depth + 1));
+ break;
+ case O_SUB:
+ result = (left()->calc(scope, locus, depth + 1) -
+ right()->calc(scope, locus, depth + 1));
+ break;
+ case O_MUL:
+ result = (left()->calc(scope, locus, depth + 1) *
+ right()->calc(scope, locus, depth + 1));
+ break;
+ case O_DIV:
+ result = (left()->calc(scope, locus, depth + 1) /
+ right()->calc(scope, locus, depth + 1));
+ break;
+
+ case O_NEG:
+ result = left()->calc(scope, locus, depth + 1).negated();
+ break;
+
+ case O_NOT:
+ result = ! left()->calc(scope, locus, depth + 1);
+ break;
+
+ case O_AND:
+ if (left()->calc(scope, locus, depth + 1))
+ result = right()->calc(scope, locus, depth + 1);
+ else
+ result = false;
+ break;
+
+ case O_OR:
+ if (value_t temp = left()->calc(scope, locus, depth + 1))
+ result = temp;
+ else
+ result = right()->calc(scope, locus, depth + 1);
+ break;
+
+ case O_QUERY:
+ assert(right());
+ assert(right()->kind == O_COLON);
+
+ if (value_t temp = left()->calc(scope, locus, depth + 1))
+ result = right()->left()->calc(scope, locus, depth + 1);
+ else
+ result = right()->right()->calc(scope, locus, depth + 1);
+ break;
+
+ case O_COLON:
+ assert(! "We should never calculate an O_COLON operator");
+ break;
+
+ case O_CONS:
+ result = left()->calc(scope, locus, depth + 1);
+ DEBUG("op.cons", "car = " << result);
+
+ if (has_right()) {
+ value_t temp;
+ temp.push_back(result);
+
+ ptr_op_t next = right();
+ while (next) {
+ ptr_op_t value_op;
+ if (next->kind == O_CONS) {
+ value_op = next->left();
+ next = next->right();
+ } else {
+ value_op = next;
+ next = NULL;
+ }
+ temp.push_back(value_op->calc(scope, locus, depth + 1));
+ DEBUG("op.cons", "temp now = " << temp);
+ }
+ result = temp;
+ }
+ break;
+
+ case O_SEQ: {
+ symbol_scope_t seq_scope(scope);
+
+ // An O_SEQ is very similar to an O_CONS except that only the last result
+ // value in the series is kept. O_CONS builds up a list.
+ //
+ // Another feature of O_SEQ is that it pushes a new symbol scope onto the
+ // stack.
+ result = left()->calc(seq_scope, locus, depth + 1);
+
+ if (has_right()) {
+ ptr_op_t next = right();
+ while (next) {
+ ptr_op_t value_op;
+ if (next->kind == O_SEQ) {
+ value_op = next->left();
+ next = next->right();
+ } else {
+ value_op = next;
+ next = NULL;
+ }
+ result = value_op->calc(seq_scope, locus, depth + 1);
+ }
+ }
+ break;
+ }
+
+ case LAST:
+ default:
+ assert(false);
+ break;
+ }
+
+#if defined(DEBUG_ON)
+ if (! skip_debug && SHOW_DEBUG("expr.calc")) {
+ for (int i = 0; i < depth; i++)
+ ledger::_log_buffer << '.';
+ ledger::_log_buffer << op_context(this) << " => ";
+ result.dump(ledger::_log_buffer, true);
+ DEBUG("expr.calc", "");
+ }
+#endif
+
+ return result;
+
+ }
+ catch (const std::exception& err) {
+ if (locus && ! *locus)
+ *locus = this;
+ throw;
+ }
+}
+
+namespace {
+ bool print_cons(std::ostream& out, const expr_t::const_ptr_op_t op,
+ const expr_t::op_t::context_t& context)
+ {
+ bool found = false;
+
+ assert(op->left());
+ if (op->left()->print(out, context))
+ found = true;
+
+ if (op->has_right()) {
+ out << ", ";
+ if (op->right()->kind == expr_t::op_t::O_CONS)
+ found = print_cons(out, op->right(), context);
+ else if (op->right()->print(out, context))
+ found = true;
+ }
+ return found;
+ }
+
+ bool print_seq(std::ostream& out, const expr_t::const_ptr_op_t op,
+ const expr_t::op_t::context_t& context)
+ {
+ bool found = false;
+
+ assert(op->left());
+ if (op->left()->print(out, context))
+ found = true;
+
+ if (op->has_right()) {
+ out << "; ";
+
+ if (op->right()->kind == expr_t::op_t::O_CONS)
+ found = print_cons(out, op->right(), context);
+ else if (op->right()->print(out, context))
+ found = true;
+ }
+
+ return found;
+ }
+}
+
+bool expr_t::op_t::print(std::ostream& out, const context_t& context) const
+{
+ bool found = false;
+
+ if (context.start_pos && this == context.op_to_find) {
+ *context.start_pos = out.tellp();
+ *context.start_pos -= 1;
+ found = true;
+ }
+
+ string symbol;
+
+ switch (kind) {
+ case VALUE:
+ as_value().dump(out, context.relaxed);
+ break;
+
+ case IDENT:
+ out << as_ident();
+ break;
+
+ case FUNCTION:
+ out << "<FUNCTION>";
+ break;
+
+ case O_NOT:
+ out << "!(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_NEG:
+ out << "-(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+
+ case O_ADD:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " + ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_SUB:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " - ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_MUL:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " * ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_DIV:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " / ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+
+ case O_EQ:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " == ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_LT:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " < ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_LTE:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " <= ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_GT:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " > ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_GTE:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " >= ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+
+ case O_AND:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " & ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_OR:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " | ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+
+ case O_QUERY:
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " ? ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ break;
+
+ case O_COLON:
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " : ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ break;
+
+ case O_CONS:
+ found = print_cons(out, this, context);
+ break;
+
+ case O_SEQ:
+ out << "(";
+ found = print_seq(out, this, context);
+ out << ")";
+ break;
+
+ case O_DEFINE:
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " := ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ break;
+
+ case O_LOOKUP:
+ if (left() && left()->print(out, context))
+ found = true;
+ out << ".";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ break;
+
+ case O_CALL:
+ if (left() && left()->print(out, context))
+ found = true;
+ if (has_right()) {
+ if (right()->kind == O_SEQ) {
+ if (right()->print(out, context))
+ found = true;
+ } else {
+ out << "(";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ }
+ } else {
+ out << "()";
+ }
+ break;
+
+ case O_MATCH:
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " =~ ";
+ if (has_right() && right()->print(out, context))
+ found = true;
+ break;
+
+ case LAST:
+ default:
+ assert(false);
+ break;
+ }
+
+ if (! symbol.empty()) {
+ if (commodity_pool_t::current_pool->find(symbol))
+ out << '@';
+ out << symbol;
+ }
+
+ if (context.end_pos && this == context.op_to_find) {
+ *context.end_pos = out.tellp();
+ *context.end_pos -= 1;
+ }
+
+ return found;
+}
+
+void expr_t::op_t::dump(std::ostream& out, const int depth) const
+{
+ out.setf(std::ios::left);
+ out.width((sizeof(void *) * 2) + 2);
+ out << this;
+
+ for (int i = 0; i < depth; i++)
+ out << " ";
+
+ switch (kind) {
+ case VALUE:
+ out << "VALUE: ";
+ as_value().dump(out);
+ break;
+
+ case IDENT:
+ out << "IDENT: " << as_ident();
+ break;
+
+ case FUNCTION:
+ out << "FUNCTION";
+ break;
+
+ case O_DEFINE: out << "O_DEFINE"; break;
+ case O_LOOKUP: out << "O_LOOKUP"; 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_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_QUERY: out << "O_QUERY"; break;
+ case O_COLON: out << "O_COLON"; break;
+
+ case O_CONS: out << "O_CONS"; break;
+ case O_SEQ: out << "O_SEQ"; break;
+
+ case LAST:
+ default:
+ assert(false);
+ break;
+ }
+
+ out << " (" << refc << ')' << std::endl;
+
+ // An identifier is a special non-terminal, in that its left() can
+ // hold the compiled definition of the identifier.
+ if (kind > TERMINALS || is_ident()) {
+ if (left()) {
+ left()->dump(out, depth + 1);
+ if (kind > UNARY_OPERATORS && has_right())
+ right()->dump(out, depth + 1);
+ }
+ else if (kind > UNARY_OPERATORS) {
+ assert(! has_right());
+ }
+ }
+}
+
+string op_context(const expr_t::ptr_op_t op,
+ const expr_t::ptr_op_t locus)
+{
+ ostream_pos_type start_pos, end_pos;
+ expr_t::op_t::context_t context(op, locus, &start_pos, &end_pos);
+ std::ostringstream buf;
+ buf << " ";
+ if (op->print(buf, context)) {
+ buf << "\n";
+ for (int i = 0; i <= end_pos; i++) {
+ if (i > start_pos)
+ buf << "^";
+ else
+ buf << " ";
+ }
+ }
+ return buf.str();
+}
+
+} // namespace ledger
diff --git a/src/op.h b/src/op.h
new file mode 100644
index 00000000..347eac1d
--- /dev/null
+++ b/src/op.h
@@ -0,0 +1,343 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup expr
+ */
+
+/**
+ * @file op.h
+ * @author John Wiegley
+ *
+ * @ingroup expr
+ */
+#ifndef _OP_H
+#define _OP_H
+
+#include "expr.h"
+
+namespace ledger {
+
+class expr_t::op_t : public noncopyable
+{
+ friend class expr_t;
+ friend class expr_t::parser_t;
+
+public:
+ typedef expr_t::ptr_op_t ptr_op_t;
+
+private:
+ mutable short refc;
+ ptr_op_t left_;
+
+ variant<ptr_op_t, // used by all binary operators
+ value_t, // used by constant VALUE
+ string, // used by constant IDENT
+ expr_t::func_t // used by terminal FUNCTION
+ > data;
+
+public:
+ enum kind_t {
+ // Constants
+ VALUE,
+ IDENT,
+
+ CONSTANTS,
+
+ FUNCTION,
+
+ TERMINALS,
+
+ // Binary operators
+ O_NOT,
+ O_NEG,
+
+ UNARY_OPERATORS,
+
+ O_EQ,
+ 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_CONS,
+ O_SEQ,
+
+ O_DEFINE,
+ O_LOOKUP,
+ O_CALL,
+ O_MATCH,
+
+ BINARY_OPERATORS,
+
+ OPERATORS,
+
+ UNKNOWN,
+
+ LAST
+ };
+
+ kind_t kind;
+
+ explicit op_t() : refc(0), kind(UNKNOWN) {
+ TRACE_CTOR(op_t, "");
+ }
+ 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_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));
+ VERIFY(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) {
+ VERIFY(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_function() const {
+ return kind == FUNCTION;
+ }
+ expr_t::func_t& as_function_lval() {
+ assert(kind == FUNCTION);
+ return boost::get<expr_t::func_t>(data);
+ }
+ const expr_t::func_t& as_function() const {
+ return const_cast<op_t *>(this)->as_function_lval();
+ }
+ void set_function(const expr_t::func_t& val) {
+ data = val;
+ }
+
+ ptr_op_t& left() {
+ assert(kind > TERMINALS || kind == IDENT);
+ return left_;
+ }
+ const ptr_op_t& left() const {
+ assert(kind > TERMINALS || kind == IDENT);
+ return left_;
+ }
+ void set_left(const ptr_op_t& expr) {
+ assert(kind > TERMINALS || kind == IDENT);
+ left_ = expr;
+ }
+
+ ptr_op_t& as_op_lval() {
+ assert(kind > TERMINALS || kind == IDENT);
+ 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;
+ }
+ bool has_right() const {
+ if (kind < TERMINALS)
+ return false;
+ return as_op();
+ }
+
+private:
+ void acquire() const {
+ DEBUG("op.memory",
+ "Acquiring " << this << ", refc now " << refc + 1);
+ assert(refc >= 0);
+ refc++;
+ }
+ void release() const {
+ DEBUG("op.memory",
+ "Releasing " << this << ", refc now " << refc - 1);
+ assert(refc > 0);
+ if (--refc == 0)
+ checked_delete(this);
+ }
+
+ friend inline void intrusive_ptr_add_ref(const op_t * op) {
+ op->acquire();
+ }
+ friend inline void intrusive_ptr_release(const op_t * op) {
+ op->release();
+ }
+
+ ptr_op_t copy(ptr_op_t _left = NULL, ptr_op_t _right = NULL) const {
+ ptr_op_t node(new_node(kind, _left, _right));
+ if (kind < TERMINALS)
+ node->data = data;
+ return node;
+ }
+
+public:
+ static ptr_op_t new_node(kind_t _kind, ptr_op_t _left = NULL,
+ ptr_op_t _right = NULL);
+
+ ptr_op_t compile(scope_t& scope, const int depth = 0);
+ value_t calc(scope_t& scope, ptr_op_t * locus = NULL,
+ const int depth = 0);
+
+ struct context_t
+ {
+ ptr_op_t expr_op;
+ ptr_op_t op_to_find;
+ ostream_pos_type * start_pos;
+ ostream_pos_type * end_pos;
+ bool relaxed;
+
+ context_t(const ptr_op_t& _expr_op = NULL,
+ const ptr_op_t& _op_to_find = NULL,
+ ostream_pos_type * const _start_pos = NULL,
+ ostream_pos_type * const _end_pos = NULL,
+ const bool _relaxed = true)
+ : expr_op(_expr_op), op_to_find(_op_to_find),
+ start_pos(_start_pos), end_pos(_end_pos),
+ relaxed(_relaxed) {}
+ };
+
+ bool print(std::ostream& out, const context_t& context = context_t()) const;
+ void dump(std::ostream& out, const int depth) const;
+
+ static ptr_op_t wrap_value(const value_t& val);
+ static ptr_op_t wrap_functor(const expr_t::func_t& fobj);
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & refc;
+ ar & kind;
+ if (Archive::is_loading::value || ! left_ || left_->kind != FUNCTION) {
+ ar & left_;
+ } else {
+ ptr_op_t temp_op;
+ ar & temp_op;
+ }
+ if (Archive::is_loading::value || kind == VALUE || kind == IDENT ||
+ (kind > UNARY_OPERATORS &&
+ (! has_right() || ! right()->is_function()))) {
+ ar & data;
+ } else {
+ variant<ptr_op_t, value_t, string, expr_t::func_t> temp_data;
+ ar & temp_data;
+ }
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+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));
+ if (_left) node->set_left(_left);
+ if (_right) 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 expr_t::func_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)
+
+string op_context(const expr_t::ptr_op_t op,
+ const expr_t::ptr_op_t locus = NULL);
+
+} // namespace ledger
+
+#endif // _OP_H
diff --git a/src/option.cc b/src/option.cc
new file mode 100644
index 00000000..d2ec8808
--- /dev/null
+++ b/src/option.cc
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "option.h"
+
+namespace ledger {
+
+namespace {
+ typedef std::pair<expr_t::ptr_op_t, bool> op_bool_tuple;
+
+ op_bool_tuple find_option(scope_t& scope, const string& name)
+ {
+ char buf[128];
+ char * p = buf;
+ foreach (char ch, name) {
+ if (ch == '-')
+ *p++ = '_';
+ else
+ *p++ = ch;
+ }
+ *p++ = '_';
+ *p = '\0';
+
+ if (expr_t::ptr_op_t op = scope.lookup(symbol_t::OPTION, buf))
+ return op_bool_tuple(op, true);
+
+ *--p = '\0';
+
+ return op_bool_tuple(scope.lookup(symbol_t::OPTION, buf), false);
+ }
+
+ op_bool_tuple find_option(scope_t& scope, const char letter)
+ {
+ char buf[4];
+ buf[0] = letter;
+ buf[1] = '_';
+ buf[2] = '\0';
+
+ if (expr_t::ptr_op_t op = scope.lookup(symbol_t::OPTION, buf))
+ return op_bool_tuple(op, true);
+
+ buf[1] = '\0';
+
+ return op_bool_tuple(scope.lookup(symbol_t::OPTION, buf), false);
+ }
+
+ void process_option(const string& whence, const expr_t::func_t& opt,
+ scope_t& scope, const char * arg, const string& name)
+ {
+ try {
+ call_scope_t args(scope);
+
+ args.push_back(string_value(whence));
+ if (arg)
+ args.push_back(string_value(arg));
+
+ opt(args);
+ }
+ catch (const std::exception& err) {
+ if (name[0] == '-')
+ add_error_context(_("While parsing option '%1'") << name);
+
+ else
+ add_error_context(_("While parsing environent variable '%1'") << name);
+ throw;
+ }
+ }
+}
+
+bool process_option(const string& whence, const string& name, scope_t& scope,
+ const char * arg, const string& varname)
+{
+ op_bool_tuple opt(find_option(scope, name));
+ if (opt.first) {
+ process_option(whence, opt.first->as_function(), scope, arg, varname);
+ return true;
+ }
+ return false;
+}
+
+void process_environment(const char ** envp, const string& tag,
+ scope_t& scope)
+{
+ const char * tag_p = tag.c_str();
+ string::size_type tag_len = tag.length();
+
+ assert(tag_p);
+ assert(tag_len > 0);
+
+ for (const char ** p = envp; *p; p++) {
+ if (std::strlen(*p) >= tag_len && std::strncmp(*p, tag_p, tag_len) == 0) {
+ char buf[8192];
+ char * r = buf;
+ const char * q;
+ for (q = *p + tag_len;
+ *q && *q != '=' && r - buf < 8191;
+ q++)
+ if (*q == '_')
+ *r++ = '-';
+ else
+ *r++ = static_cast<char>(std::tolower(*q));
+ *r = '\0';
+
+ if (*q == '=') {
+ try {
+ string value = string(*p, q - *p);
+ if (! value.empty())
+ process_option(string("$") + buf, string(buf), scope, q + 1, value);
+ }
+ catch (const std::exception& err) {
+ add_error_context(_("While parsing environment variable option '%1':")
+ << *p);
+ throw;
+ }
+ }
+ }
+ }
+}
+
+namespace {
+ struct op_bool_char_tuple {
+ expr_t::ptr_op_t op;
+ bool truth;
+ char ch;
+
+ op_bool_char_tuple(expr_t::ptr_op_t _op, bool _truth, char _ch)
+ : op(_op), truth(_truth), ch(_ch) {}
+ };
+}
+
+strings_list process_arguments(strings_list args, scope_t& scope)
+{
+ bool anywhere = true;
+
+ strings_list remaining;
+
+ for (strings_list::iterator i = args.begin();
+ i != args.end();
+ i++) {
+ DEBUG("option.args", "Examining argument '" << *i << "'");
+
+ if (! anywhere || (*i)[0] != '-') {
+ DEBUG("option.args", " adding to list of real args");
+ remaining.push_back(*i);
+ continue;
+ }
+
+ // --long-option or -s
+ if ((*i)[1] == '-') {
+ if ((*i)[2] == '\0') {
+ DEBUG("option.args", " it's a --, ending options processing");
+ anywhere = false;
+ continue;
+ }
+
+ DEBUG("option.args", " it's an option string");
+
+ string opt_name;
+ const char * name = (*i).c_str() + 2;
+ const char * value = NULL;
+
+ if (const char * p = std::strchr(name, '=')) {
+ opt_name = string(name, p - name);
+ value = ++p;
+ DEBUG("option.args", " read option value from option: " << value);
+ } else {
+ opt_name = name;
+ }
+
+ op_bool_tuple opt(find_option(scope, opt_name));
+ if (! opt.first)
+ throw_(option_error, _("Illegal option --%1") << name);
+
+ if (opt.second && ! value && ++i != args.end() && value == NULL) {
+ value = (*i).c_str();
+ DEBUG("option.args", " read option value from arg: " << value);
+ if (value == NULL)
+ throw_(option_error, _("Missing option argument for --%1") << name);
+ }
+ process_option(string("--") + name,
+ opt.first->as_function(), scope, value,
+ string("--") + name);
+ }
+ else if ((*i)[1] == '\0') {
+ throw_(option_error, _("illegal option -%1") << (*i)[0]);
+ }
+ else {
+ DEBUG("option.args", " single-char option");
+
+ 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.first)
+ throw_(option_error, _("Illegal option -%1") << c);
+
+ option_queue.push_back(op_bool_char_tuple(opt.first, opt.second, c));
+ }
+
+ foreach (op_bool_char_tuple& o, option_queue) {
+ const char * value = NULL;
+ if (o.truth && ++i != args.end()) {
+ value = (*i).c_str();
+ DEBUG("option.args", " read option value from arg: " << value);
+ if (value == NULL)
+ throw_(option_error,
+ _("Missing option argument for -%1") << o.ch);
+ }
+ process_option(string("-") + o.ch, o.op->as_function(), scope, value,
+ string("-") + o.ch);
+ }
+ }
+ }
+
+ return remaining;
+}
+
+} // namespace ledger
diff --git a/src/option.h b/src/option.h
new file mode 100644
index 00000000..b81c2ce7
--- /dev/null
+++ b/src/option.h
@@ -0,0 +1,294 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup expr
+ */
+
+/**
+ * @file option.h
+ * @author John Wiegley
+ *
+ * @ingroup expr
+ */
+#ifndef _OPTION_H
+#define _OPTION_H
+
+#include "scope.h"
+
+namespace ledger {
+
+class call_scope_t;
+
+template <typename T>
+class option_t
+{
+protected:
+ const char * name;
+ string::size_type name_len;
+ const char ch;
+ bool handled;
+ optional<string> source;
+
+ option_t& operator=(const option_t&);
+
+public:
+ T * parent;
+ value_t value;
+ bool wants_arg;
+
+ option_t(const char * _name, const char _ch = '\0')
+ : name(_name), name_len(std::strlen(name)), ch(_ch),
+ handled(false), parent(NULL), value(),
+ wants_arg(name[name_len - 1] == '_') {
+ TRACE_CTOR(option_t, "const char *, const char");
+ DEBUG("option.names", "Option: " << name);
+ }
+ option_t(const option_t& other)
+ : name(other.name),
+ name_len(other.name_len),
+ ch(other.ch),
+ handled(other.handled),
+ parent(NULL),
+ value(other.value),
+ wants_arg(other.wants_arg)
+ {
+ TRACE_CTOR(option_t, "copy");
+ }
+
+ virtual ~option_t() {
+ TRACE_DTOR(option_t);
+ }
+
+ void report(std::ostream& out) const {
+ if (handled && source) {
+ if (wants_arg) {
+ out << desc() << " => ";
+ value.dump(out);
+ } else {
+ out << desc();
+ }
+ out << " <" << *source << ">" << std::endl;
+ }
+ }
+
+ string desc() const {
+ std::ostringstream out;
+ out << "--";
+ for (const char * p = name; *p; p++) {
+ if (*p == '_') {
+ if (*(p + 1))
+ out << '-';
+ } else {
+ out << *p;
+ }
+ }
+ if (ch)
+ out << " (-" << ch << ")";
+ return out.str();
+ }
+
+ operator bool() const {
+ return handled;
+ }
+
+ string& str() {
+ assert(handled);
+ if (! value)
+ throw_(std::runtime_error, _("No argument provided for %1") << desc());
+ return value.as_string_lval();
+ }
+
+ string str() const {
+ assert(handled);
+ if (! value)
+ throw_(std::runtime_error, _("No argument provided for %1") << desc());
+ return value.as_string();
+ }
+
+ void on_only(const optional<string>& whence) {
+ handled = true;
+ source = whence;
+ }
+ void on(const optional<string>& whence, const string& str) {
+ on_with(whence, string_value(str));
+ }
+ virtual void on_with(const optional<string>& whence,
+ const value_t& val) {
+ handled = true;
+ value = val;
+ source = whence;
+ }
+
+ void off() {
+ handled = false;
+ value = value_t();
+ source = none;
+ }
+
+ virtual void handler_thunk(call_scope_t&) {}
+
+ virtual void handler(call_scope_t& args) {
+ if (wants_arg) {
+ if (args.size() < 2)
+ throw_(std::runtime_error, _("No argument provided for %1") << desc());
+ else if (args.size() > 2)
+ throw_(std::runtime_error, _("To many arguments provided for %1") << desc());
+ else if (! args[0].is_string())
+ throw_(std::runtime_error, _("Context argument for %1 not a string") << desc());
+ on_with(args[0].as_string(), args[1]);
+ }
+ else if (args.size() < 1) {
+ throw_(std::runtime_error, _("No argument provided for %1") << desc());
+ }
+ else if (! args[0].is_string()) {
+ throw_(std::runtime_error, _("Context argument for %1 not a string") << desc());
+ }
+ else {
+ on_only(args[0].as_string());
+ }
+
+ handler_thunk(args);
+ }
+
+ virtual value_t handler_wrapper(call_scope_t& args) {
+ handler(args);
+ return true;
+ }
+
+ virtual value_t operator()(call_scope_t& args) {
+ if (! args.empty()) {
+ args.push_front(string_value("?expr"));
+ return handler_wrapper(args);
+ }
+ else if (wants_arg) {
+ if (handled)
+ return value;
+ else
+ return NULL_VALUE;
+ }
+ else {
+ return handled;
+ }
+ }
+};
+
+#define BEGIN(type, name) \
+ struct name ## _option_t : public option_t<type>
+
+#define CTOR(type, name) \
+ name ## _option_t() : option_t<type>(#name)
+#define DECL1(type, name, vartype, var, value) \
+ vartype var ; \
+ name ## _option_t() : option_t<type>(#name), var(value)
+
+#define DO() virtual void handler_thunk(call_scope_t&)
+#define DO_(var) virtual void handler_thunk(call_scope_t& var)
+
+#define END(name) name ## _handler
+
+#define COPY_OPT(name, other) name ## _handler(other.name ## _handler)
+
+#define MAKE_OPT_HANDLER(type, x) \
+ expr_t::op_t::wrap_functor(bind(&option_t<type>::handler_wrapper, x, _1))
+
+#define MAKE_OPT_FUNCTOR(type, x) \
+ expr_t::op_t::wrap_functor(bind(&option_t<type>::operator(), x, _1))
+
+inline bool is_eq(const char * p, const char * n) {
+ // Test whether p matches n, substituting - in p for _ in n.
+ for (; *p && *n; p++, n++) {
+ if (! (*p == '-' && *n == '_' ) && *p != *n)
+ return false;
+ }
+ // Ignore any trailing underscore
+ return *p == *n || (! *p && *n == '_' && ! *(n + 1));
+}
+
+#define OPT(name) \
+ if (is_eq(p, #name)) \
+ return ((name ## _handler).parent = this, &(name ## _handler))
+
+#define OPT_ALT(name, alt) \
+ if (is_eq(p, #name) || is_eq(p, #alt)) \
+ return ((name ## _handler).parent = this, &(name ## _handler))
+
+#define OPT_(name) \
+ if (! *(p + 1) || \
+ ((name ## _handler).wants_arg && \
+ *(p + 1) == '_' && ! *(p + 2)) || \
+ is_eq(p, #name)) \
+ return ((name ## _handler).parent = this, &(name ## _handler))
+
+#define OPT_CH(name) \
+ if (! *(p + 1) || \
+ ((name ## _handler).wants_arg && \
+ *(p + 1) == '_' && ! *(p + 2))) \
+ return ((name ## _handler).parent = this, &(name ## _handler))
+
+#define HANDLER(name) name ## _handler
+#define HANDLED(name) HANDLER(name)
+
+#define OPTION(type, name) \
+ BEGIN(type, name) \
+ { \
+ CTOR(type, name) {} \
+ } \
+ END(name)
+
+#define OPTION_(type, name, body) \
+ BEGIN(type, name) \
+ { \
+ CTOR(type, name) {} \
+ body \
+ } \
+ END(name)
+
+#define OPTION__(type, name, body) \
+ BEGIN(type, name) \
+ { \
+ body \
+ } \
+ END(name)
+
+bool process_option(const string& whence, const string& name, scope_t& scope,
+ const char * arg, const string& varname);
+
+void process_environment(const char ** envp, const string& tag,
+ scope_t& scope);
+
+strings_list process_arguments(strings_list args, scope_t& scope);
+
+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..71ec6d88
--- /dev/null
+++ b/src/output.cc
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "output.h"
+#include "xact.h"
+#include "post.h"
+#include "account.h"
+#include "session.h"
+#include "report.h"
+
+namespace ledger {
+
+format_posts::format_posts(report_t& _report,
+ const string& format,
+ bool _print_raw,
+ const optional<string>& _prepend_format)
+ : report(_report), last_xact(NULL), last_post(NULL),
+ print_raw(_print_raw)
+{
+ TRACE_CTOR(format_posts, "report&, const string&, bool");
+
+ const char * f = format.c_str();
+
+ if (const char * p = std::strstr(f, "%/")) {
+ first_line_format.parse_format(string(f, 0, p - f));
+ const char * n = p + 2;
+ if (const char * p = std::strstr(n, "%/")) {
+ next_lines_format.parse_format(string(n, 0, p - n),
+ first_line_format);
+ between_format.parse_format(string(p + 2),
+ first_line_format);
+ } else {
+ next_lines_format.parse_format(string(n), first_line_format);
+ }
+ } else {
+ first_line_format.parse_format(format);
+ next_lines_format.parse_format(format);
+ }
+
+ if (_prepend_format)
+ prepend_format.parse_format(*_prepend_format);
+}
+
+void format_posts::flush()
+{
+ report.output_stream.flush();
+}
+
+void format_posts::operator()(post_t& post)
+{
+ std::ostream& out(report.output_stream);
+
+ if (print_raw) {
+ if (! post.has_xdata() ||
+ ! post.xdata().has_flags(POST_EXT_DISPLAYED)) {
+ if (last_xact != post.xact) {
+ if (last_xact) {
+ bind_scope_t xact_scope(report, *last_xact);
+ out << between_format(xact_scope);
+ }
+ print_item(out, *post.xact);
+ out << '\n';
+ last_xact = post.xact;
+ }
+ post.xdata().add_flags(POST_EXT_DISPLAYED);
+ last_post = &post;
+ }
+ }
+ else if (! post.has_xdata() ||
+ ! post.xdata().has_flags(POST_EXT_DISPLAYED)) {
+ bind_scope_t bound_scope(report, post);
+
+ if (prepend_format)
+ out << prepend_format(bound_scope);
+
+ if (last_xact != post.xact) {
+ if (last_xact) {
+ bind_scope_t xact_scope(report, *last_xact);
+ out << between_format(xact_scope);
+ }
+ out << first_line_format(bound_scope);
+ last_xact = post.xact;
+ }
+ else if (last_post && last_post->date() != post.date()) {
+ out << first_line_format(bound_scope);
+ }
+ else {
+ out << next_lines_format(bound_scope);
+ }
+
+ post.xdata().add_flags(POST_EXT_DISPLAYED);
+ last_post = &post;
+ }
+}
+
+format_accounts::format_accounts(report_t& _report,
+ const string& format,
+ const optional<string>& _prepend_format)
+ : report(_report), disp_pred()
+{
+ TRACE_CTOR(format_accounts, "report&, const string&");
+
+ const char * f = format.c_str();
+
+ if (const char * p = std::strstr(f, "%/")) {
+ account_line_format.parse_format(string(f, 0, p - f));
+ const char * n = p + 2;
+ if (const char * p = std::strstr(n, "%/")) {
+ total_line_format.parse_format(string(n, 0, p - n), account_line_format);
+ separator_format.parse_format(string(p + 2), account_line_format);
+ } else {
+ total_line_format.parse_format(n, account_line_format);
+ }
+ } else {
+ account_line_format.parse_format(format);
+ total_line_format.parse_format(format, account_line_format);
+ }
+
+ if (_prepend_format)
+ prepend_format.parse_format(*_prepend_format);
+}
+
+std::size_t format_accounts::post_account(account_t& account, const bool flat)
+{
+ if (account.xdata().has_flags(ACCOUNT_EXT_TO_DISPLAY) &&
+ ! account.xdata().has_flags(ACCOUNT_EXT_DISPLAYED)) {
+ if (! flat && account.parent &&
+ account.parent->xdata().has_flags(ACCOUNT_EXT_TO_DISPLAY) &&
+ ! account.parent->xdata().has_flags(ACCOUNT_EXT_DISPLAYED))
+ post_account(*account.parent, flat);
+
+ account.xdata().add_flags(ACCOUNT_EXT_DISPLAYED);
+
+ bind_scope_t bound_scope(report, account);
+
+ if (prepend_format)
+ static_cast<std::ostream&>(report.output_stream)
+ << prepend_format(bound_scope);
+
+ static_cast<std::ostream&>(report.output_stream)
+ << account_line_format(bound_scope);
+
+ return 1;
+ }
+ return 0;
+}
+
+std::pair<std::size_t, std::size_t>
+format_accounts::mark_accounts(account_t& account, const bool flat)
+{
+ std::size_t visited = 0;
+ std::size_t to_display = 0;
+
+ foreach (accounts_map::value_type& pair, account.accounts) {
+ std::pair<std::size_t, std::size_t> i = mark_accounts(*pair.second, flat);
+ visited += i.first;
+ to_display += i.second;
+ }
+
+#if defined(DEBUG_ON)
+ DEBUG("account.display", "Considering account: " << account.fullname());
+ if (account.has_xflags(ACCOUNT_EXT_VISITED))
+ DEBUG("account.display", " it was visited itself");
+ DEBUG("account.display", " it has " << visited << " visited children");
+ DEBUG("account.display",
+ " it has " << to_display << " children to display");
+#endif
+
+ if (account.parent &&
+ (account.has_xflags(ACCOUNT_EXT_VISITED) || (! flat && visited > 0))) {
+ bind_scope_t bound_scope(report, account);
+ if ((! flat && to_display > 1) ||
+ ((flat || to_display != 1 ||
+ account.has_xflags(ACCOUNT_EXT_VISITED)) &&
+ disp_pred(bound_scope))) {
+ account.xdata().add_flags(ACCOUNT_EXT_TO_DISPLAY);
+ DEBUG("account.display", "Marking account as TO_DISPLAY");
+ to_display = 1;
+ }
+ visited = 1;
+ }
+
+ return std::pair<std::size_t, std::size_t>(visited, to_display);
+}
+
+void format_accounts::flush()
+{
+ std::ostream& out(report.output_stream);
+
+ if (report.HANDLED(display_)) {
+ DEBUG("account.display",
+ "Account display predicate: " << report.HANDLER(display_).str());
+ disp_pred.parse(report.HANDLER(display_).str());
+ }
+
+ mark_accounts(*report.session.journal->master, report.HANDLED(flat));
+
+ std::size_t displayed = 0;
+
+ foreach (account_t * account, posted_accounts)
+ displayed += post_account(*account, report.HANDLED(flat));
+
+ if (displayed > 1 &&
+ ! report.HANDLED(no_total) && ! report.HANDLED(percent)) {
+ bind_scope_t bound_scope(report, *report.session.journal->master);
+ out << separator_format(bound_scope);
+
+ if (prepend_format)
+ static_cast<std::ostream&>(report.output_stream)
+ << prepend_format(bound_scope);
+
+ out << total_line_format(bound_scope);
+ }
+
+ out.flush();
+}
+
+void format_accounts::operator()(account_t& account)
+{
+ DEBUG("account.display", "Posting account: " << account.fullname());
+ posted_accounts.push_back(&account);
+}
+
+} // namespace ledger
diff --git a/src/output.h b/src/output.h
new file mode 100644
index 00000000..778a9335
--- /dev/null
+++ b/src/output.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup report
+ */
+
+/**
+ * @file output.h
+ * @author John Wiegley
+ *
+ * @ingroup report
+ */
+#ifndef _OUTPUT_H
+#define _OUTPUT_H
+
+#include "chain.h"
+#include "predicate.h"
+#include "format.h"
+#include "account.h"
+
+namespace ledger {
+
+class xact_t;
+class post_t;
+class report_t;
+
+class format_posts : public item_handler<post_t>
+{
+protected:
+ report_t& report;
+ format_t first_line_format;
+ format_t next_lines_format;
+ format_t between_format;
+ format_t prepend_format;
+ xact_t * last_xact;
+ post_t * last_post;
+ bool print_raw;
+
+public:
+ format_posts(report_t& _report, const string& format,
+ bool _print_raw = false,
+ const optional<string>& _prepend_format = none);
+ virtual ~format_posts() {
+ TRACE_DTOR(format_posts);
+ }
+
+ virtual void flush();
+ virtual void operator()(post_t& post);
+};
+
+class format_accounts : public item_handler<account_t>
+{
+protected:
+ report_t& report;
+ format_t account_line_format;
+ format_t total_line_format;
+ format_t separator_format;
+ format_t prepend_format;
+ predicate_t disp_pred;
+
+ std::list<account_t *> posted_accounts;
+
+public:
+ format_accounts(report_t& _report, const string& _format,
+ const optional<string>& _prepend_format = none);
+ virtual ~format_accounts() {
+ TRACE_DTOR(format_accounts);
+ }
+
+ std::pair<std::size_t, std::size_t>
+ mark_accounts(account_t& account, const bool flat);
+
+ virtual std::size_t post_account(account_t& account, const bool flat);
+ 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..ef778411
--- /dev/null
+++ b/src/parser.cc
@@ -0,0 +1,517 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "parser.h"
+
+namespace ledger {
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_value_term(std::istream& in,
+ const parse_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::IDENT: {
+ string ident = tok.value.as_string();
+
+ node = new op_t(op_t::IDENT);
+ node->set_ident(ident);
+
+ // An identifier followed by ( represents a function call
+ tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT));
+ if (tok.kind == token_t::LPAREN) {
+ ptr_op_t call_node(new op_t(op_t::O_CALL));
+ call_node->set_left(node);
+ node = call_node;
+
+ push_token(tok); // let the parser see it again
+ node->set_right(parse_value_expr(in, tflags.plus_flags(PARSE_SINGLE)));
+
+ if (node->has_right() && node->right()->kind == op_t::O_CONS)
+ node->set_right(node->right()->left());
+ } else {
+ push_token(tok);
+ }
+ break;
+ }
+
+ case token_t::LPAREN:
+ node = parse_value_expr(in, tflags.plus_flags(PARSE_PARTIAL)
+ .minus_flags(PARSE_SINGLE));
+ tok = next_token(in, tflags);
+ if (tok.kind != token_t::RPAREN)
+ tok.expected(')');
+
+ if (node->kind == op_t::O_CONS) {
+ ptr_op_t prev(node);
+ node = new op_t(op_t::O_SEQ);
+ node->set_left(prev);
+ }
+ break;
+
+ default:
+ push_token(tok);
+ break;
+ }
+
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_dot_expr(std::istream& in,
+ const parse_flags_t& tflags) const
+{
+ ptr_op_t node(parse_value_term(in, tflags));
+
+ if (node && ! tflags.has_flags(PARSE_SINGLE)) {
+ while (true) {
+ token_t& tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT));
+ if (tok.kind == token_t::DOT) {
+ ptr_op_t prev(node);
+ node = new op_t(op_t::O_LOOKUP);
+ node->set_left(prev);
+ node->set_right(parse_value_term(in, tflags));
+ if (! node->right())
+ throw_(parse_error,
+ _("%1 operator not followed by argument") << tok.symbol);
+ } else {
+ push_token(tok);
+ break;
+ }
+ }
+ }
+
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_unary_expr(std::istream& in,
+ const parse_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_dot_expr(in, tflags));
+ if (! term)
+ throw_(parse_error,
+ _("%1 operator not followed by argument") << tok.symbol);
+
+ // A very quick optimization
+ if (term->kind == op_t::VALUE) {
+ term->as_value_lval().in_place_not();
+ 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_dot_expr(in, tflags));
+ if (! term)
+ throw_(parse_error,
+ _("%1 operator not followed by argument") << tok.symbol);
+
+ // 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_dot_expr(in, tflags);
+ break;
+ }
+
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_mul_expr(std::istream& in,
+ const parse_flags_t& tflags) const
+{
+ ptr_op_t node(parse_unary_expr(in, tflags));
+
+ if (node && ! tflags.has_flags(PARSE_SINGLE)) {
+ while (true) {
+ token_t& tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT));
+
+ if (tok.kind == token_t::STAR || tok.kind == token_t::SLASH ||
+ 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_unary_expr(in, tflags));
+ if (! node->right())
+ throw_(parse_error,
+ _("%1 operator not followed by argument") << tok.symbol);
+ } else {
+ push_token(tok);
+ break;
+ }
+ }
+ }
+
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_add_expr(std::istream& in,
+ const parse_flags_t& tflags) const
+{
+ ptr_op_t node(parse_mul_expr(in, tflags));
+
+ if (node && ! tflags.has_flags(PARSE_SINGLE)) {
+ while (true) {
+ token_t& tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT));
+
+ 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_mul_expr(in, tflags));
+ if (! node->right())
+ throw_(parse_error,
+ _("%1 operator not followed by argument") << tok.symbol);
+ } else {
+ push_token(tok);
+ break;
+ }
+ }
+ }
+
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_logic_expr(std::istream& in,
+ const parse_flags_t& tflags) const
+{
+ ptr_op_t node(parse_add_expr(in, tflags));
+
+ if (node && ! tflags.has_flags(PARSE_SINGLE)) {
+ while (true) {
+ op_t::kind_t kind = op_t::LAST;
+ parse_flags_t _flags = tflags;
+ token_t& tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT));
+ bool negate = false;
+
+ switch (tok.kind) {
+ case token_t::DEFINE:
+ kind = op_t::O_DEFINE;
+ break;
+ case token_t::EQUAL:
+ if (tflags.has_flags(PARSE_NO_ASSIGN))
+ tok.rewind(in);
+ else
+ kind = op_t::O_EQ;
+ break;
+ case token_t::NEQUAL:
+ kind = op_t::O_EQ;
+ negate = true;
+ break;
+ case token_t::MATCH:
+ kind = op_t::O_MATCH;
+ break;
+ case token_t::NMATCH:
+ kind = op_t::O_MATCH;
+ negate = true;
+ 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);
+ goto exit_loop;
+ }
+
+ 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,
+ _("%1 operator not followed by argument") << tok.symbol);
+
+ if (negate) {
+ prev = node;
+ node = new op_t(op_t::O_NOT);
+ node->set_left(prev);
+ }
+ }
+ }
+ }
+
+ exit_loop:
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_and_expr(std::istream& in,
+ const parse_flags_t& tflags) const
+{
+ ptr_op_t node(parse_logic_expr(in, tflags));
+
+ if (node && ! tflags.has_flags(PARSE_SINGLE)) {
+ while (true) {
+ token_t& tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT));
+
+ 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_logic_expr(in, tflags));
+ if (! node->right())
+ throw_(parse_error,
+ _("%1 operator not followed by argument") << tok.symbol);
+ } else {
+ push_token(tok);
+ break;
+ }
+ }
+ }
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_or_expr(std::istream& in,
+ const parse_flags_t& tflags) const
+{
+ ptr_op_t node(parse_and_expr(in, tflags));
+
+ if (node && ! tflags.has_flags(PARSE_SINGLE)) {
+ while (true) {
+ token_t& tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT));
+
+ 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_and_expr(in, tflags));
+ if (! node->right())
+ throw_(parse_error,
+ _("%1 operator not followed by argument") << tok.symbol);
+ } else {
+ push_token(tok);
+ break;
+ }
+ }
+ }
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_querycolon_expr(std::istream& in,
+ const parse_flags_t& tflags) const
+{
+ ptr_op_t node(parse_or_expr(in, tflags));
+
+ if (node && ! tflags.has_flags(PARSE_SINGLE)) {
+ token_t& tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT));
+
+ if (tok.kind == token_t::QUERY) {
+ ptr_op_t prev(node);
+ node = new op_t(op_t::O_QUERY);
+ node->set_left(prev);
+ node->set_right(parse_or_expr(in, tflags));
+ if (! node->right())
+ throw_(parse_error,
+ _("%1 operator not followed by argument") << tok.symbol);
+
+ token_t& next_tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT));
+ if (next_tok.kind != token_t::COLON)
+ next_tok.expected(':');
+
+ prev = node->right();
+ ptr_op_t subnode = new op_t(op_t::O_COLON);
+ subnode->set_left(prev);
+ subnode->set_right(parse_or_expr(in, tflags));
+ if (! subnode->right())
+ throw_(parse_error,
+ _("%1 operator not followed by argument") << tok.symbol);
+
+ node->set_right(subnode);
+ }
+ else if (tok.kind == token_t::KW_IF) {
+ ptr_op_t if_op(parse_or_expr(in, tflags));
+ if (! if_op)
+ throw_(parse_error, _("'if' keyword not followed by argument"));
+
+ tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT));
+ if (tok.kind == token_t::KW_ELSE) {
+ ptr_op_t else_op(parse_or_expr(in, tflags));
+ if (! else_op)
+ throw_(parse_error, _("'else' keyword not followed by argument"));
+
+ ptr_op_t subnode = new op_t(op_t::O_COLON);
+ subnode->set_left(node);
+ subnode->set_right(else_op);
+
+ node = new op_t(op_t::O_QUERY);
+ node->set_left(if_op);
+ node->set_right(subnode);
+ } else {
+ ptr_op_t null_node = new op_t(op_t::VALUE);
+ null_node->set_value(NULL_VALUE);
+
+ ptr_op_t subnode = new op_t(op_t::O_COLON);
+ subnode->set_left(node);
+ subnode->set_right(null_node);
+
+ node = new op_t(op_t::O_QUERY);
+ node->set_left(if_op);
+ node->set_right(subnode);
+
+ push_token(tok);
+ }
+ }
+ else {
+ push_token(tok);
+ }
+ }
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_value_expr(std::istream& in,
+ const parse_flags_t& tflags) const
+{
+ ptr_op_t node(parse_querycolon_expr(in, tflags));
+
+ if (node && ! tflags.has_flags(PARSE_SINGLE)) {
+ token_t& tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT));
+
+ if (tok.kind == token_t::COMMA || tok.kind == token_t::SEMI) {
+ bool comma_op = tok.kind == token_t::COMMA;
+
+ ptr_op_t prev(node);
+ node = new op_t(comma_op ? op_t::O_CONS : op_t::O_SEQ);
+ node->set_left(prev);
+ node->set_right(parse_value_expr(in, tflags));
+ if (! node->right())
+ throw_(parse_error,
+ _("%1 operator not followed by argument") << tok.symbol);
+
+ tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT));
+ }
+
+ if (tok.kind != token_t::TOK_EOF) {
+ if (tflags.has_flags(PARSE_PARTIAL))
+ push_token(tok);
+ else
+ tok.unexpected();
+ }
+ }
+ else if (! tflags.has_flags(PARSE_PARTIAL) &&
+ ! tflags.has_flags(PARSE_SINGLE)) {
+ throw_(parse_error, _("Failed to parse value expression"));
+ }
+
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse(std::istream& in,
+ const parse_flags_t& flags,
+ const optional<string>& original_string)
+{
+ 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) {
+ if (original_string) {
+ add_error_context(_("While parsing value expression:"));
+
+ std::streamoff end_pos = 0;
+ if (in.good())
+ end_pos = in.tellg();
+ std::streamoff pos = end_pos;
+
+ if (pos > 0)
+ pos -= lookahead.length;
+
+ DEBUG("parser.error", "original_string = '" << *original_string << "'");
+ DEBUG("parser.error", " pos = " << pos);
+ DEBUG("parser.error", " end_pos = " << end_pos);
+ DEBUG("parser.error", " token kind = " << int(lookahead.kind));
+ DEBUG("parser.error", " token length = " << lookahead.length);
+
+ add_error_context(line_context(*original_string,
+ static_cast<string::size_type>(pos),
+ static_cast<string::size_type>(end_pos)));
+ }
+ throw;
+ }
+}
+
+} // namespace ledger
diff --git a/src/parser.h b/src/parser.h
new file mode 100644
index 00000000..7a69fe08
--- /dev/null
+++ b/src/parser.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup expr
+ */
+
+/**
+ * @file parser.h
+ * @author John Wiegley
+ *
+ * @ingroup expr
+ */
+#ifndef _PARSER_H
+#define _PARSER_H
+
+#include "token.h"
+#include "op.h"
+
+namespace ledger {
+
+class expr_t::parser_t : public noncopyable
+{
+ mutable token_t lookahead;
+ mutable bool use_lookahead;
+
+ token_t& next_token(std::istream& in, const parse_flags_t& tflags) const {
+ 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 parse_flags_t& flags) const;
+ ptr_op_t parse_dot_expr(std::istream& in,
+ const parse_flags_t& flags) const;
+ ptr_op_t parse_unary_expr(std::istream& in,
+ const parse_flags_t& flags) const;
+ ptr_op_t parse_mul_expr(std::istream& in,
+ const parse_flags_t& flags) const;
+ ptr_op_t parse_add_expr(std::istream& in,
+ const parse_flags_t& flags) const;
+ ptr_op_t parse_logic_expr(std::istream& in,
+ const parse_flags_t& flags) const;
+ ptr_op_t parse_and_expr(std::istream& in,
+ const parse_flags_t& flags) const;
+ ptr_op_t parse_or_expr(std::istream& in,
+ const parse_flags_t& flags) const;
+ ptr_op_t parse_querycolon_expr(std::istream& in,
+ const parse_flags_t& flags) const;
+ ptr_op_t parse_value_expr(std::istream& in,
+ const parse_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 parse_flags_t& flags = PARSE_DEFAULT,
+ const optional<string>& original_string = NULL);
+};
+
+} // namespace ledger
+
+#endif // _PARSER_H
diff --git a/src/pool.cc b/src/pool.cc
new file mode 100644
index 00000000..70f4eed6
--- /dev/null
+++ b/src/pool.cc
@@ -0,0 +1,364 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "amount.h"
+#include "commodity.h"
+#include "annotate.h"
+#include "pool.h"
+#include "quotes.h"
+
+namespace ledger {
+
+shared_ptr<commodity_pool_t> commodity_pool_t::current_pool;
+
+commodity_pool_t::commodity_pool_t()
+ : default_commodity(NULL), keep_base(false),
+ quote_leeway(86400), get_quotes(false),
+ get_commodity_quote(commodity_quote_from_script)
+{
+ TRACE_CTOR(commodity_pool_t, "");
+ null_commodity = create("");
+ null_commodity->add_flags(COMMODITY_BUILTIN | COMMODITY_NOMARKET);
+}
+
+commodity_t * commodity_pool_t::create(const string& symbol)
+{
+ shared_ptr<commodity_t::base_t>
+ base_commodity(new commodity_t::base_t(symbol));
+ std::auto_ptr<commodity_t> commodity(new commodity_t(this, base_commodity));
+
+ DEBUG("amounts.commodities", "Creating base commodity " << symbol);
+
+ // Create the "qualified symbol" version of this commodity's symbol
+ if (commodity_t::symbol_needs_quotes(symbol)) {
+ commodity->qualified_symbol = "\"";
+ *commodity->qualified_symbol += symbol;
+ *commodity->qualified_symbol += "\"";
+ }
+
+ DEBUG("amounts.commodities",
+ "Creating commodity '" << commodity->symbol() << "'");
+
+ std::pair<commodities_map::iterator, bool> result
+ = commodities.insert(commodities_map::value_type(commodity->mapping_key(),
+ commodity.get()));
+ assert(result.second);
+
+ return commodity.release();
+}
+
+commodity_t * commodity_pool_t::find_or_create(const string& symbol)
+{
+ DEBUG("amounts.commodities", "Find-or-create commodity " << symbol);
+
+ commodity_t * commodity = find(symbol);
+ if (commodity)
+ return commodity;
+ return create(symbol);
+}
+
+commodity_t * commodity_pool_t::find(const string& symbol)
+{
+ DEBUG("amounts.commodities", "Find commodity " << symbol);
+
+ commodities_map::const_iterator i = commodities.find(symbol);
+ if (i != commodities.end())
+ return (*i).second;
+ return NULL;
+}
+
+commodity_t *
+commodity_pool_t::create(const string& symbol, const annotation_t& details)
+{
+ commodity_t * new_comm = create(symbol);
+ if (! new_comm)
+ return NULL;
+
+ if (details)
+ return find_or_create(*new_comm, details);
+ else
+ return new_comm;
+}
+
+string commodity_pool_t::make_qualified_name(const commodity_t& comm,
+ const annotation_t& details)
+{
+ assert(details);
+
+ if (details.price && details.price->sign() < 0)
+ throw_(amount_error, _("A commodity's price may not be negative"));
+
+ std::ostringstream name;
+ comm.print(name);
+ details.print(name, comm.pool().keep_base);
+
+ DEBUG("amounts.commodities", "make_qualified_name for "
+ << *comm.qualified_symbol << std::endl << details);
+ DEBUG("amounts.commodities", "qualified_name is " << name.str());
+
+ return name.str();
+}
+
+commodity_t *
+commodity_pool_t::find(const string& symbol, const annotation_t& details)
+{
+ commodity_t * comm = find(symbol);
+ if (! comm)
+ return NULL;
+
+ if (details) {
+ string name = make_qualified_name(*comm, details);
+
+ if (commodity_t * ann_comm = find(name)) {
+ assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details);
+ return ann_comm;
+ }
+ return NULL;
+ } else {
+ return comm;
+ }
+}
+
+commodity_t *
+commodity_pool_t::find_or_create(const string& symbol,
+ const annotation_t& details)
+{
+ commodity_t * comm = find(symbol);
+ if (! comm)
+ return NULL;
+
+ if (details)
+ return find_or_create(*comm, details);
+ else
+ return comm;
+}
+
+commodity_t *
+commodity_pool_t::create(commodity_t& comm,
+ const annotation_t& details,
+ const string& mapping_key)
+{
+ assert(comm);
+ assert(details);
+ assert(! mapping_key.empty());
+
+ std::auto_ptr<commodity_t> commodity
+ (new annotated_commodity_t(&comm, details));
+
+ commodity->qualified_symbol = comm.symbol();
+ assert(! commodity->qualified_symbol->empty());
+
+ DEBUG("amounts.commodities", "Creating annotated commodity "
+ << "symbol " << commodity->symbol()
+ << " key " << mapping_key << std::endl << details);
+
+ // Add the fully annotated name to the map, so that this symbol may
+ // quickly be found again.
+ commodity->mapping_key_ = mapping_key;
+
+ std::pair<commodities_map::iterator, bool> result
+ = commodities.insert(commodities_map::value_type(mapping_key,
+ commodity.get()));
+ assert(result.second);
+
+ return commodity.release();
+}
+
+commodity_t * commodity_pool_t::find_or_create(commodity_t& comm,
+ const annotation_t& details)
+{
+ assert(comm);
+ assert(details);
+
+ string name = make_qualified_name(comm, details);
+ assert(! name.empty());
+
+ if (commodity_t * ann_comm = find(name)) {
+ assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details);
+ return ann_comm;
+ }
+ return create(comm, details, name);
+}
+
+void commodity_pool_t::exchange(commodity_t& commodity,
+ const amount_t& per_unit_cost,
+ const datetime_t& moment)
+{
+ DEBUG("commodity.prices.add", "exchanging commodity " << commodity
+ << " at per unit cost " << per_unit_cost << " on " << moment);
+
+ commodity_t& base_commodity
+ (commodity.annotated ?
+ as_annotated_commodity(commodity).referent() : commodity);
+
+ base_commodity.add_price(moment, per_unit_cost);
+}
+
+cost_breakdown_t
+commodity_pool_t::exchange(const amount_t& amount,
+ const amount_t& cost,
+ const bool is_per_unit,
+ const optional<datetime_t>& moment,
+ const optional<string>& tag)
+{
+ DEBUG("commodity.prices.add", "exchange: " << amount << " for " << cost);
+ DEBUG("commodity.prices.add", "exchange: is-per-unit = " << is_per_unit);
+#if defined(DEBUG_ON)
+ if (moment)
+ DEBUG("commodity.prices.add", "exchange: moment = " << *moment);
+ if (tag)
+ DEBUG("commodity.prices.add", "exchange: tag = " << *tag);
+#endif
+
+ commodity_t& commodity(amount.commodity());
+
+ annotation_t * current_annotation = NULL;
+ if (commodity.annotated)
+ current_annotation = &as_annotated_commodity(commodity).details;
+
+ amount_t per_unit_cost =
+ (is_per_unit || amount.is_realzero() ? cost : cost / amount).abs();
+
+ DEBUG("commodity.prices.add", "exchange: per-unit-cost = " << per_unit_cost);
+
+ if (! per_unit_cost.is_realzero())
+ exchange(commodity, per_unit_cost, moment ? *moment : CURRENT_TIME());
+
+ cost_breakdown_t breakdown;
+ breakdown.final_cost = ! is_per_unit ? cost : cost * amount;
+
+ DEBUG("commodity.prices.add",
+ "exchange: final-cost = " << breakdown.final_cost);
+
+ if (current_annotation && current_annotation->price)
+ breakdown.basis_cost
+ = (*current_annotation->price * amount).unrounded();
+ else
+ breakdown.basis_cost = breakdown.final_cost;
+
+ DEBUG("commodity.prices.add",
+ "exchange: basis-cost = " << breakdown.basis_cost);
+
+ annotation_t annotation(per_unit_cost, moment ?
+ moment->date() : optional<date_t>(), tag);
+
+ annotation.add_flags(ANNOTATION_PRICE_CALCULATED);
+ if (moment)
+ annotation.add_flags(ANNOTATION_DATE_CALCULATED);
+ if (tag)
+ annotation.add_flags(ANNOTATION_TAG_CALCULATED);
+
+ breakdown.amount = amount_t(amount, annotation);
+
+ DEBUG("commodity.prices.add",
+ "exchange: amount = " << breakdown.amount);
+
+ return breakdown;
+}
+
+optional<std::pair<commodity_t *, price_point_t> >
+commodity_pool_t::parse_price_directive(char * line, bool do_not_add_price)
+{
+ char * date_field_ptr = line;
+ char * time_field_ptr = next_element(date_field_ptr);
+ if (! time_field_ptr) return none;
+ string date_field = date_field_ptr;
+
+ char * symbol_and_price;
+ datetime_t datetime;
+ string symbol;
+
+ if (std::isdigit(time_field_ptr[0])) {
+ symbol_and_price = next_element(time_field_ptr);
+ if (! symbol_and_price) return none;
+
+ datetime = parse_datetime(date_field + " " + time_field_ptr);
+ }
+ else if (std::isdigit(date_field_ptr[0])) {
+ symbol_and_price = time_field_ptr;
+ datetime = datetime_t(parse_date(date_field));
+ }
+ else {
+ symbol = date_field_ptr;
+ symbol_and_price = time_field_ptr;
+ datetime = CURRENT_TIME();
+ }
+
+ if (symbol.empty())
+ commodity_t::parse_symbol(symbol_and_price, symbol);
+
+ price_point_t point;
+ point.when = datetime;
+ point.price.parse(symbol_and_price, PARSE_NO_MIGRATE);
+ VERIFY(point.price.valid());
+
+ DEBUG("commodity.download", "Looking up symbol: " << symbol);
+ if (commodity_t * commodity = find_or_create(symbol)) {
+ DEBUG("commodity.download", "Adding price for " << symbol << ": "
+ << point.when << " " << point.price);
+ if (! do_not_add_price)
+ commodity->add_price(point.when, point.price, true);
+ commodity->add_flags(COMMODITY_KNOWN);
+ return std::pair<commodity_t *, price_point_t>(commodity, point);
+ }
+
+ return none;
+}
+
+commodity_t *
+commodity_pool_t::parse_price_expression(const std::string& str,
+ const bool add_prices,
+ const optional<datetime_t>& moment)
+{
+ scoped_array<char> buf(new char[str.length() + 1]);
+
+ std::strcpy(buf.get(), str.c_str());
+
+ char * price = std::strchr(buf.get(), '=');
+ if (price)
+ *price++ = '\0';
+
+ if (commodity_t * commodity = find_or_create(trim_ws(buf.get()))) {
+ if (price && add_prices) {
+ for (char * p = std::strtok(price, ";");
+ p;
+ p = std::strtok(NULL, ";")) {
+ commodity->add_price(moment ? *moment : CURRENT_TIME(), amount_t(p));
+ }
+ }
+ return commodity;
+ }
+ return NULL;
+}
+
+} // namespace ledger
diff --git a/src/pool.h b/src/pool.h
new file mode 100644
index 00000000..995ab23c
--- /dev/null
+++ b/src/pool.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup math
+ */
+
+/**
+ * @file pool.h
+ * @author John Wiegley
+ *
+ * @ingroup math
+ *
+ * @brief Types for managing commodity pools
+ *
+ * Long.
+ */
+#ifndef _POOL_H
+#define _POOL_H
+
+namespace ledger {
+
+struct cost_breakdown_t
+{
+ amount_t amount;
+ amount_t final_cost;
+ amount_t basis_cost;
+};
+
+class commodity_pool_t : public noncopyable
+{
+public:
+ /**
+ * The commodities collection in commodity_pool_t maintains pointers to all
+ * the commodities which have ever been created by the user, whether
+ * explicitly by calling the create methods of commodity_pool_t, or
+ * implicitly by parsing a commoditized amount.
+ */
+ typedef std::map<string, commodity_t *> commodities_map;
+
+ commodities_map commodities;
+ commodity_t * null_commodity;
+ commodity_t * default_commodity;
+
+ bool keep_base; // --base
+
+ optional<path> price_db; // --price-db=
+ long quote_leeway; // --leeway=
+ bool get_quotes; // --download
+
+ static shared_ptr<commodity_pool_t> current_pool;
+
+ function<optional<price_point_t>
+ (commodity_t& commodity, const optional<commodity_t&>& in_terms_of)>
+ get_commodity_quote;
+
+ explicit commodity_pool_t();
+
+ virtual ~commodity_pool_t() {
+ TRACE_DTOR(commodity_pool_t);
+ foreach (commodities_map::value_type pair, commodities)
+ checked_delete(pair.second);
+ }
+
+ string make_qualified_name(const commodity_t& comm,
+ const annotation_t& details);
+
+ commodity_t * create(const string& symbol);
+ commodity_t * find(const string& name);
+ commodity_t * find_or_create(const string& symbol);
+
+ commodity_t * create(const string& symbol, const annotation_t& details);
+ commodity_t * find(const string& symbol, const annotation_t& details);
+ commodity_t * find_or_create(const string& symbol,
+ const annotation_t& details);
+
+ commodity_t * create(commodity_t& comm,
+ const annotation_t& details,
+ const string& mapping_key);
+
+ commodity_t * find_or_create(commodity_t& comm,
+ const annotation_t& details);
+
+ // Exchange one commodity for another, while recording the factored price.
+
+ void exchange(commodity_t& commodity,
+ const amount_t& per_unit_cost,
+ const datetime_t& moment);
+
+ cost_breakdown_t exchange(const amount_t& amount,
+ const amount_t& cost,
+ const bool is_per_unit = false,
+ const optional<datetime_t>& moment = none,
+ const optional<string>& tag = none);
+
+ // Parse commodity prices from a textual representation
+
+ optional<std::pair<commodity_t *, price_point_t> >
+ parse_price_directive(char * line, bool do_not_add_price = false);
+
+ commodity_t *
+ parse_price_expression(const std::string& str,
+ const bool add_prices = true,
+ const optional<datetime_t>& moment = none);
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & current_pool;
+ ar & commodities;
+ ar & null_commodity;
+ ar & default_commodity;
+ ar & keep_base;
+ ar & price_db;
+ ar & quote_leeway;
+ ar & get_quotes;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+} // namespace ledger
+
+#endif // _POOL_H
diff --git a/src/post.cc b/src/post.cc
new file mode 100644
index 00000000..34284e1b
--- /dev/null
+++ b/src/post.cc
@@ -0,0 +1,576 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "post.h"
+#include "xact.h"
+#include "account.h"
+#include "journal.h"
+#include "interactive.h"
+#include "format.h"
+
+namespace ledger {
+
+bool post_t::has_tag(const string& tag) const
+{
+ if (item_t::has_tag(tag))
+ return true;
+ if (xact)
+ return xact->has_tag(tag);
+ return false;
+}
+
+bool post_t::has_tag(const mask_t& tag_mask,
+ const optional<mask_t>& value_mask) const
+{
+ if (item_t::has_tag(tag_mask, value_mask))
+ return true;
+ if (xact)
+ return xact->has_tag(tag_mask, value_mask);
+ return false;
+}
+
+optional<string> post_t::get_tag(const string& tag) const
+{
+ if (optional<string> value = item_t::get_tag(tag))
+ return value;
+ if (xact)
+ return xact->get_tag(tag);
+ return none;
+}
+
+optional<string> post_t::get_tag(const mask_t& tag_mask,
+ const optional<mask_t>& value_mask) const
+{
+ if (optional<string> value = item_t::get_tag(tag_mask, value_mask))
+ return value;
+ if (xact)
+ return xact->get_tag(tag_mask, value_mask);
+ return none;
+}
+
+date_t post_t::date() const
+{
+ if (xdata_ && is_valid(xdata_->date))
+ return xdata_->date;
+
+ if (item_t::use_effective_date) {
+ if (_date_eff)
+ return *_date_eff;
+ else if (xact && xact->_date_eff)
+ return *xact->_date_eff;
+ }
+
+ if (! _date) {
+ assert(xact);
+ return xact->date();
+ }
+ return *_date;
+}
+
+optional<date_t> post_t::effective_date() const
+{
+ optional<date_t> date = item_t::effective_date();
+ if (! date && xact)
+ return xact->effective_date();
+ return date;
+}
+
+namespace {
+ value_t get_this(post_t& post) {
+ return value_t(static_cast<scope_t *>(&post));
+ }
+
+ value_t get_is_calculated(post_t& post) {
+ return post.has_flags(POST_CALCULATED);
+ }
+
+ value_t get_is_cost_calculated(post_t& post) {
+ return post.has_flags(POST_COST_CALCULATED);
+ }
+
+ value_t get_virtual(post_t& post) {
+ return post.has_flags(POST_VIRTUAL);
+ }
+
+ value_t get_real(post_t& post) {
+ return ! post.has_flags(POST_VIRTUAL);
+ }
+
+ value_t get_xact(post_t& post) {
+ return value_t(static_cast<scope_t *>(post.xact));
+ }
+
+ value_t get_code(post_t& post) {
+ if (post.xact->code)
+ return string_value(*post.xact->code);
+ else
+ return string_value(empty_string);
+ }
+
+ value_t get_payee(post_t& post) {
+ return string_value(post.xact->payee);
+ }
+
+ value_t get_note(post_t& post) {
+ string note = post.note ? *post.note : empty_string;
+ note += post.xact->note ? *post.xact->note : empty_string;
+ return string_value(note);
+ }
+
+ value_t get_magnitude(post_t& post) {
+ return post.xact->magnitude();
+ }
+ value_t get_idstring(post_t& post) {
+ return string_value(post.xact->idstring());
+ }
+ value_t get_id(post_t& post) {
+ return string_value(post.xact->id());
+ }
+
+ value_t get_amount(post_t& post) {
+ if (post.has_xdata() && post.xdata().has_flags(POST_EXT_COMPOUND))
+ return post.xdata().compound_value;
+ else if (post.amount.is_null())
+ return 0L;
+ else
+ return post.amount;
+ }
+
+ value_t get_use_direct_amount(post_t& post) {
+ return post.has_xdata() && post.xdata().has_flags(POST_EXT_DIRECT_AMT);
+ }
+
+ value_t get_commodity(post_t& post) {
+ return string_value(post.amount.commodity().symbol());
+ }
+
+ value_t get_commodity_is_primary(post_t& post) {
+ return post.amount.commodity().has_flags(COMMODITY_PRIMARY);
+ }
+
+ value_t get_has_cost(post_t& post) {
+ return post.cost ? true : false;
+ }
+
+ value_t get_cost(post_t& post) {
+ if (post.cost)
+ return *post.cost;
+ else if (post.has_xdata() &&
+ post.xdata().has_flags(POST_EXT_COMPOUND))
+ return post.xdata().compound_value;
+ else if (post.amount.is_null())
+ return 0L;
+ else
+ return post.amount;
+ }
+
+ value_t get_total(post_t& post) {
+ if (post.xdata_ && ! post.xdata_->total.is_null())
+ return post.xdata_->total;
+ else if (post.amount.is_null())
+ return 0L;
+ else
+ return post.amount;
+ }
+
+ value_t get_count(post_t& post) {
+ if (post.xdata_)
+ return long(post.xdata_->count);
+ else
+ return 1L;
+ }
+
+ value_t get_account(call_scope_t& scope)
+ {
+ in_context_t<post_t> env(scope, "&v");
+
+ string name;
+
+ if (env.has(0)) {
+ if (env.value_at(0).is_long()) {
+ if (env.get<long>(0) > 2)
+ name = format_t::truncate(env->reported_account()->fullname(),
+ env.get<long>(0) - 2,
+ 2 /* account_abbrev_length */);
+ else
+ name = env->reported_account()->fullname();
+ } else {
+ account_t * account = NULL;
+ account_t * master = env->account;
+ while (master->parent)
+ master = master->parent;
+
+ if (env.value_at(0).is_string()) {
+ name = env.get<string>(0);
+ account = master->find_account(name, false);
+ }
+ else if (env.value_at(0).is_mask()) {
+ name = env.get<mask_t>(0).str();
+ account = master->find_account_re(name);
+ }
+ else {
+ throw_(std::runtime_error,
+ _("Expected string or mask for argument 1, but received %1")
+ << env.value_at(0).label());
+ }
+
+ if (! account)
+ throw_(std::runtime_error,
+ _("Could not find an account matching ") << env.value_at(0));
+ else
+ return value_t(static_cast<scope_t *>(account));
+ }
+ } else {
+ name = env->reported_account()->fullname();
+ }
+
+ if (env->has_flags(POST_VIRTUAL)) {
+ if (env->must_balance())
+ name = string("[") + name + "]";
+ else
+ name = string("(") + name + ")";
+ }
+ return string_value(name);
+ }
+
+ value_t get_account_base(post_t& post) {
+ return string_value(post.reported_account()->name);
+ }
+
+ value_t get_account_depth(post_t& post) {
+ return long(post.reported_account()->depth);
+ }
+
+ value_t get_datetime(post_t& post) {
+ return post.xdata().datetime;
+ }
+
+ template <value_t (*Func)(post_t&)>
+ value_t get_wrapper(call_scope_t& scope) {
+ return (*Func)(find_scope<post_t>(scope));
+ }
+}
+
+expr_t::ptr_op_t post_t::lookup(const symbol_t::kind_t kind,
+ const string& name)
+{
+ if (kind != symbol_t::FUNCTION)
+ return item_t::lookup(kind, 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 'b':
+ if (name[1] == '\0')
+ return WRAP_FUNCTOR(get_wrapper<&get_cost>);
+ break;
+
+ case 'c':
+ if (name == "code")
+ return WRAP_FUNCTOR(get_wrapper<&get_code>);
+ else if (name == "cost")
+ return WRAP_FUNCTOR(get_wrapper<&get_cost>);
+ else if (name == "cost_calculated")
+ return WRAP_FUNCTOR(get_wrapper<&get_is_cost_calculated>);
+ else if (name == "count")
+ return WRAP_FUNCTOR(get_wrapper<&get_count>);
+ else if (name == "calculated")
+ return WRAP_FUNCTOR(get_wrapper<&get_is_calculated>);
+ else if (name == "commodity")
+ return WRAP_FUNCTOR(get_wrapper<&get_commodity>);
+ break;
+
+ case 'd':
+ if (name == "depth")
+ return WRAP_FUNCTOR(get_wrapper<&get_account_depth>);
+ else if (name == "datetime")
+ return WRAP_FUNCTOR(get_wrapper<&get_datetime>);
+ break;
+
+ case 'h':
+ if (name == "has_cost")
+ return WRAP_FUNCTOR(get_wrapper<&get_has_cost>);
+ break;
+
+ case 'i':
+ if (name == "index")
+ return WRAP_FUNCTOR(get_wrapper<&get_count>);
+ else if (name == "id")
+ return WRAP_FUNCTOR(get_wrapper<&get_id>);
+ else if (name == "idstring")
+ return WRAP_FUNCTOR(get_wrapper<&get_idstring>);
+ break;
+
+ case 'm':
+ if (name == "magnitude")
+ return WRAP_FUNCTOR(get_wrapper<&get_magnitude>);
+ break;
+
+ case 'n':
+ if (name == "note")
+ return WRAP_FUNCTOR(get_wrapper<&get_note>);
+ else if (name[1] == '\0')
+ return WRAP_FUNCTOR(get_wrapper<&get_count>);
+ break;
+
+ case 'p':
+ if (name == "post")
+ return WRAP_FUNCTOR(get_wrapper<&get_this>);
+ else if (name == "payee")
+ return WRAP_FUNCTOR(get_wrapper<&get_payee>);
+ else if (name == "primary")
+ return WRAP_FUNCTOR(get_wrapper<&get_commodity_is_primary>);
+ else if (name == "parent")
+ return WRAP_FUNCTOR(get_wrapper<&get_xact>);
+ break;
+
+ case 'r':
+ if (name == "real")
+ return WRAP_FUNCTOR(get_wrapper<&get_real>);
+ break;
+
+ case 't':
+ if (name == "total")
+ return WRAP_FUNCTOR(get_wrapper<&get_total>);
+ break;
+
+ case 'u':
+ if (name == "use_direct_amount")
+ return WRAP_FUNCTOR(get_wrapper<&get_use_direct_amount>);
+ break;
+
+ case 'v':
+ if (name == "virtual")
+ return WRAP_FUNCTOR(get_wrapper<&get_virtual>);
+ break;
+
+ case 'x':
+ if (name == "xact")
+ return WRAP_FUNCTOR(get_wrapper<&get_xact>);
+ break;
+
+ case 'N':
+ if (name[1] == '\0')
+ return WRAP_FUNCTOR(get_wrapper<&get_count>);
+ break;
+
+ case 'O':
+ if (name[1] == '\0')
+ return WRAP_FUNCTOR(get_wrapper<&get_total>);
+ break;
+
+ case 'R':
+ if (name[1] == '\0')
+ return WRAP_FUNCTOR(get_wrapper<&get_real>);
+ break;
+ }
+
+ return item_t::lookup(kind, name);
+}
+
+bool post_t::valid() const
+{
+ if (! xact) {
+ DEBUG("ledger.validate", "post_t: ! xact");
+ return false;
+ }
+
+ posts_list::const_iterator i =
+ std::find(xact->posts.begin(),
+ xact->posts.end(), this);
+ if (i == xact->posts.end()) {
+ DEBUG("ledger.validate", "post_t: ! found");
+ return false;
+ }
+
+ if (! account) {
+ DEBUG("ledger.validate", "post_t: ! account");
+ return false;
+ }
+
+ if (! amount.valid()) {
+ DEBUG("ledger.validate", "post_t: ! amount.valid()");
+ return false;
+ }
+
+ if (cost) {
+ if (! cost->valid()) {
+ DEBUG("ledger.validate", "post_t: cost && ! cost->valid()");
+ return false;
+ }
+ if (! cost->keep_precision()) {
+ DEBUG("ledger.validate", "post_t: ! cost->keep_precision()");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void post_t::add_to_value(value_t& value, const optional<expr_t&>& expr) const
+{
+ if (xdata_ && xdata_->has_flags(POST_EXT_COMPOUND)) {
+ add_or_set_value(value, xdata_->compound_value);
+ }
+ else if (expr) {
+ bind_scope_t bound_scope(*expr->get_context(),
+ const_cast<post_t&>(*this));
+#if 1
+ value_t temp(expr->calc(bound_scope));
+ add_or_set_value(value, temp);
+#else
+ if (! xdata_) xdata_ = xdata_t();
+ xdata_->value = expr->calc(bound_scope);
+ xdata_->add_flags(POST_EXT_COMPOUND);
+
+ add_or_set_value(value, xdata_->value);
+#endif
+ }
+ else if (xdata_ && xdata_->has_flags(POST_EXT_VISITED) &&
+ ! xdata_->visited_value.is_null()) {
+ add_or_set_value(value, xdata_->visited_value);
+ }
+ else {
+ add_or_set_value(value, amount);
+ }
+}
+
+void post_t::set_reported_account(account_t * account)
+{
+ xdata().account = account;
+ account->xdata().reported_posts.push_back(this);
+}
+
+void to_xml(std::ostream& out, const post_t& post)
+{
+ push_xml x(out, "posting", true);
+
+ if (post.state() == item_t::CLEARED)
+ out << " state=\"cleared\"";
+ else if (post.state() == item_t::PENDING)
+ out << " state=\"pending\"";
+
+ if (post.has_flags(POST_VIRTUAL))
+ out << " virtual=\"true\"";
+ if (post.has_flags(ITEM_GENERATED))
+ out << " generated=\"true\"";
+
+ x.close_attrs();
+
+ if (post._date) {
+ push_xml y(out, "date");
+ to_xml(out, *post._date, false);
+ }
+ if (post._date_eff) {
+ push_xml y(out, "effective-date");
+ to_xml(out, *post._date_eff, false);
+ }
+
+ if (post.account) {
+ push_xml y(out, "account", true);
+
+ out << " ref=\"";
+ out.width(sizeof(unsigned long) * 2);
+ out.fill('0');
+ out << std::hex << reinterpret_cast<unsigned long>(post.account);
+ out << '"';
+ y.close_attrs();
+
+ {
+ push_xml z(out, "name");
+ out << z.guard(post.account->fullname());
+ }
+ }
+
+ {
+ push_xml y(out, "post-amount");
+ if (post.has_xdata() && post.xdata().has_flags(POST_EXT_COMPOUND))
+ to_xml(out, post.xdata().compound_value);
+ else
+ to_xml(out, post.amount);
+ }
+
+ if (post.cost) {
+ push_xml y(out, "cost");
+ to_xml(out, *post.cost);
+ }
+
+ if (post.assigned_amount) {
+ if (post.has_flags(POST_CALCULATED)) {
+ push_xml y(out, "balance-assertion");
+ to_xml(out, *post.assigned_amount);
+ } else {
+ push_xml y(out, "balance-assignment");
+ to_xml(out, *post.assigned_amount);
+ }
+ }
+
+ if (post.note) {
+ push_xml y(out, "note");
+ out << y.guard(*post.note);
+ }
+
+ if (post.metadata) {
+ push_xml y(out, "metadata");
+ foreach (const item_t::string_map::value_type& pair, *post.metadata) {
+ if (pair.second) {
+ push_xml z(out, "variable");
+ {
+ push_xml z(out, "key");
+ out << y.guard(pair.first);
+ }
+ {
+ push_xml z(out, "value");
+ out << y.guard(*pair.second);
+ }
+ } else {
+ push_xml z(out, "tag");
+ out << y.guard(pair.first);
+ }
+ }
+ }
+
+ if (post.xdata_ && ! post.xdata_->total.is_null()) {
+ push_xml y(out, "total");
+ to_xml(out, post.xdata_->total);
+ }
+}
+
+} // namespace ledger
diff --git a/src/post.h b/src/post.h
new file mode 100644
index 00000000..a0ab5ffd
--- /dev/null
+++ b/src/post.h
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup data
+ */
+
+/**
+ * @file post.h
+ * @author John Wiegley
+ *
+ * @ingroup data
+ */
+#ifndef _POST_H
+#define _POST_H
+
+#include "item.h"
+
+namespace ledger {
+
+class xact_t;
+class account_t;
+
+class post_t : public item_t
+{
+public:
+#define POST_VIRTUAL 0x10 // the account was specified with (parens)
+#define POST_MUST_BALANCE 0x20 // posting must balance in the transaction
+#define POST_CALCULATED 0x40 // posting's amount was calculated
+#define POST_COST_CALCULATED 0x80 // posting's cost was calculated
+
+ xact_t * xact; // only set for posts of regular xacts
+ account_t * account;
+
+ amount_t amount; // can be null until finalization
+ optional<expr_t> amount_expr;
+ optional<amount_t> cost;
+ optional<amount_t> assigned_amount;
+
+ post_t(account_t * _account = NULL,
+ flags_t _flags = ITEM_NORMAL)
+ : item_t(_flags),
+ xact(NULL), account(_account)
+ {
+ TRACE_CTOR(post_t, "account_t *, flags_t");
+ }
+ post_t(account_t * _account,
+ const amount_t& _amount,
+ flags_t _flags = ITEM_NORMAL,
+ const optional<string>& _note = none)
+ : item_t(_flags, _note),
+ xact(NULL), account(_account), amount(_amount)
+ {
+ TRACE_CTOR(post_t, "account_t *, const amount_t&, flags_t, const optional<string>&");
+ }
+ post_t(const post_t& post)
+ : item_t(post),
+ xact(post.xact),
+ account(post.account),
+ amount(post.amount),
+ cost(post.cost),
+ assigned_amount(post.assigned_amount),
+ xdata_(post.xdata_)
+ {
+ TRACE_CTOR(post_t, "copy");
+ }
+ virtual ~post_t() {
+ TRACE_DTOR(post_t);
+ }
+
+ virtual bool has_tag(const string& tag) const;
+ virtual bool has_tag(const mask_t& tag_mask,
+ const optional<mask_t>& value_mask = none) const;
+
+ virtual optional<string> get_tag(const string& tag) const;
+ virtual optional<string> get_tag(const mask_t& tag_mask,
+ const optional<mask_t>& value_mask = none) const;
+
+ virtual date_t date() const;
+ virtual optional<date_t> effective_date() const;
+
+ bool must_balance() const {
+ return ! has_flags(POST_VIRTUAL) || has_flags(POST_MUST_BALANCE);
+ }
+
+ virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind,
+ const string& name);
+
+ bool valid() const;
+
+ struct xdata_t : public supports_flags<uint_least16_t>
+ {
+#define POST_EXT_RECEIVED 0x0001
+#define POST_EXT_HANDLED 0x0002
+#define POST_EXT_DISPLAYED 0x0004
+#define POST_EXT_DIRECT_AMT 0x0008
+#define POST_EXT_SORT_CALC 0x0010
+#define POST_EXT_COMPOUND 0x0020
+#define POST_EXT_VISITED 0x0040
+#define POST_EXT_MATCHES 0x0080
+#define POST_EXT_CONSIDERED 0x0100
+
+ value_t visited_value;
+ value_t compound_value;
+ value_t total;
+ std::size_t count;
+ date_t date;
+ datetime_t datetime;
+ account_t * account;
+
+ std::list<sort_value_t> sort_values;
+
+ xdata_t()
+ : supports_flags<uint_least16_t>(), count(0), account(NULL) {
+ TRACE_CTOR(post_t::xdata_t, "");
+ }
+ xdata_t(const xdata_t& other)
+ : supports_flags<uint_least16_t>(other.flags()),
+ visited_value(other.visited_value),
+ compound_value(other.compound_value),
+ total(other.total),
+ count(other.count),
+ date(other.date),
+ account(other.account),
+ sort_values(other.sort_values)
+ {
+ TRACE_CTOR(post_t::xdata_t, "copy");
+ }
+ ~xdata_t() throw() {
+ TRACE_DTOR(post_t::xdata_t);
+ }
+ };
+
+ // This variable holds optional "extended data" which is usually produced
+ // only during reporting, and only for the posting 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_;
+ }
+ const xdata_t& xdata() const {
+ return const_cast<post_t *>(this)->xdata();
+ }
+
+ void add_to_value(value_t& value,
+ const optional<expr_t&>& expr = none) const;
+
+ void set_reported_account(account_t * account);
+
+ 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<post_t *>(this)->reported_account();
+ }
+
+ friend class xact_t;
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & boost::serialization::base_object<item_t>(*this);
+ ar & xact;
+ ar & account;
+ ar & amount;
+ ar & amount_expr;
+ ar & cost;
+ ar & assigned_amount;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+void to_xml(std::ostream& out, const post_t& post);
+
+} // namespace ledger
+
+#endif // _POST_H
diff --git a/src/precmd.cc b/src/precmd.cc
new file mode 100644
index 00000000..31249016
--- /dev/null
+++ b/src/precmd.cc
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "precmd.h"
+#include "xact.h"
+#include "post.h"
+#include "account.h"
+#include "query.h"
+#include "session.h"
+#include "report.h"
+#include "format.h"
+
+namespace ledger {
+
+namespace {
+ post_t * get_sample_xact(report_t& report)
+ {
+ {
+ string str;
+ {
+ std::ostringstream buf;
+
+ buf << "2004/05/27 Book Store\n"
+ << " ; This note applies to all postings. :SecondTag:\n"
+ << " Expenses:Books 20 BOOK @ $10\n"
+ << " ; Metadata: Some Value\n"
+ << " ; :ExampleTag:\n"
+ << " ; Here follows a note describing the posting.\n"
+ << " Liabilities:MasterCard $-200.00\n";
+
+ str = buf.str();
+ }
+
+ std::ostream& out(report.output_stream);
+
+ out << _("--- Context is first posting of the following transaction ---")
+ << std::endl << str << std::endl;
+ {
+ std::istringstream in(str);
+ report.session.journal->parse(in, report.session);
+ report.session.journal->clear_xdata();
+ }
+ }
+ xact_t * first = report.session.journal->xacts.front();
+ return first->posts.front();
+ }
+}
+
+value_t parse_command(call_scope_t& args)
+{
+ string arg = join_args(args);
+ if (arg.empty()) {
+ throw std::logic_error(_("Usage: parse TEXT"));
+ return 1L;
+ }
+
+ report_t& report(find_scope<report_t>(args));
+ std::ostream& out(report.output_stream);
+
+ post_t * post = get_sample_xact(report);
+
+ out << _("--- Input expression ---") << std::endl;
+ out << arg << std::endl;
+
+ out << std::endl << _("--- Text as parsed ---") << std::endl;
+ expr_t expr(arg);
+ expr.print(out);
+ out << std::endl;
+
+ out << std::endl << _("--- Expression tree ---") << std::endl;
+ expr.dump(out);
+
+ bind_scope_t bound_scope(args, *post);
+ expr.compile(bound_scope);
+ out << std::endl << _("--- Compiled tree ---") << std::endl;
+ expr.dump(out);
+
+ out << std::endl << _("--- Calculated value ---") << std::endl;
+ value_t result(expr.calc());
+ result.strip_annotations(report.what_to_keep()).dump(out);
+ out << std::endl;
+
+ return NULL_VALUE;
+}
+
+value_t eval_command(call_scope_t& args)
+{
+ report_t& report(find_scope<report_t>(args));
+ expr_t expr(join_args(args));
+ value_t result(expr.calc(args).strip_annotations(report.what_to_keep()));
+
+ if (! result.is_null())
+ report.output_stream << result << std::endl;
+
+ return NULL_VALUE;
+}
+
+value_t format_command(call_scope_t& args)
+{
+ string arg = join_args(args);
+ if (arg.empty()) {
+ throw std::logic_error(_("Usage: format TEXT"));
+ return 1L;
+ }
+
+ report_t& report(find_scope<report_t>(args));
+ std::ostream& out(report.output_stream);
+
+ post_t * post = get_sample_xact(report);
+
+ out << _("--- Input format string ---") << std::endl;
+ out << arg << std::endl << std::endl;
+
+ out << _("--- Format elements ---") << std::endl;
+ format_t fmt(arg);
+ fmt.dump(out);
+
+ out << std::endl << _("--- Formatted string ---") << std::endl;
+ bind_scope_t bound_scope(args, *post);
+ out << '"';
+ out << fmt(bound_scope);
+ out << "\"\n";
+
+ return NULL_VALUE;
+}
+
+value_t period_command(call_scope_t& args)
+{
+ string arg = join_args(args);
+ if (arg.empty()) {
+ throw std::logic_error(_("Usage: period TEXT"));
+ return 1L;
+ }
+
+ report_t& report(find_scope<report_t>(args));
+ std::ostream& out(report.output_stream);
+
+ show_period_tokens(out, arg);
+ out << std::endl;
+
+ date_interval_t interval(arg);
+ interval.dump(out, report.session.current_year);
+
+ return NULL_VALUE;
+}
+
+value_t args_command(call_scope_t& args)
+{
+ report_t& report(find_scope<report_t>(args));
+ std::ostream& out(report.output_stream);
+
+ out << _("--- Input arguments ---") << std::endl;
+ args.value().dump(out);
+ out << std::endl << std::endl;
+
+ query_t query(args.value(), report.what_to_keep());
+ if (query) {
+ call_scope_t sub_args(static_cast<scope_t&>(args));
+ sub_args.push_back(string_value(query.text()));
+
+ parse_command(sub_args);
+ }
+
+ if (query.tokens_remaining()) {
+ out << std::endl << _("====== Display predicate ======")
+ << std::endl << std::endl;
+
+ query.parse_again();
+
+ if (query) {
+ call_scope_t disp_sub_args(static_cast<scope_t&>(args));
+ disp_sub_args.push_back(string_value(query.text()));
+
+ parse_command(disp_sub_args);
+ }
+ }
+
+ return NULL_VALUE;
+}
+
+} // namespace ledger
diff --git a/src/precmd.h b/src/precmd.h
new file mode 100644
index 00000000..e0f81cf8
--- /dev/null
+++ b/src/precmd.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup report
+ */
+
+/**
+ * @file precmd.h
+ * @author John Wiegley
+ *
+ * @ingroup report
+ */
+#ifndef _PRECMD_H
+#define _PRECMD_H
+
+#include "value.h"
+
+namespace ledger {
+
+class call_scope_t;
+
+value_t parse_command(call_scope_t& args);
+value_t eval_command(call_scope_t& args);
+value_t format_command(call_scope_t& args);
+value_t period_command(call_scope_t& args);
+value_t args_command(call_scope_t& args);
+
+} // namespace ledger
+
+#endif // _PRECMD_H
diff --git a/src/predicate.cc b/src/predicate.cc
new file mode 100644
index 00000000..4da4decf
--- /dev/null
+++ b/src/predicate.cc
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "predicate.h"
+#include "query.h"
+#include "op.h"
+
+namespace ledger {
+
+predicate_t::predicate_t(const query_t& other)
+ : expr_t(other), what_to_keep(other.what_to_keep)
+{
+ TRACE_CTOR(predicate_t, "query_t");
+}
+
+} // namespace ledger
diff --git a/src/predicate.h b/src/predicate.h
new file mode 100644
index 00000000..b3b81f9b
--- /dev/null
+++ b/src/predicate.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup expr
+ */
+
+/**
+ * @file predicate.h
+ * @author John Wiegley
+ *
+ * @ingroup expr
+ */
+#ifndef _PREDICATE_H
+#define _PREDICATE_H
+
+#include "expr.h"
+#include "commodity.h"
+#include "annotate.h"
+
+namespace ledger {
+
+class query_t;
+
+class predicate_t : public expr_t
+{
+public:
+ keep_details_t what_to_keep;
+
+ predicate_t(const keep_details_t& _what_to_keep = keep_details_t())
+ : what_to_keep(_what_to_keep) {
+ TRACE_CTOR(predicate_t, "");
+ }
+ predicate_t(const predicate_t& other)
+ : expr_t(other), what_to_keep(other.what_to_keep) {
+ TRACE_CTOR(predicate_t, "copy");
+ }
+ predicate_t(const query_t& other);
+
+ predicate_t(const string& str, const keep_details_t& _what_to_keep,
+ const parse_flags_t& flags = PARSE_DEFAULT)
+ : expr_t(str, flags), what_to_keep(_what_to_keep) {
+ TRACE_CTOR(predicate_t, "string, keep_details_t, parse_flags_t");
+ }
+ predicate_t(std::istream& in, const keep_details_t& _what_to_keep,
+ const parse_flags_t& flags = PARSE_DEFAULT)
+ : expr_t(in, flags), what_to_keep(_what_to_keep) {
+ TRACE_CTOR(predicate_t, "std::istream&, keep_details_t, parse_flags_t");
+ }
+ virtual ~predicate_t() {
+ TRACE_DTOR(predicate_t);
+ }
+
+ virtual value_t real_calc(scope_t& scope) {
+ return (*this ?
+ expr_t::real_calc(scope)
+ .strip_annotations(what_to_keep)
+ .to_boolean() :
+ true);
+ }
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & boost::serialization::base_object<expr_t>(*this);
+ ar & what_to_keep;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+} // namespace ledger
+
+#endif // _PREDICATE_H
diff --git a/src/pstream.h b/src/pstream.h
new file mode 100644
index 00000000..00caf4e0
--- /dev/null
+++ b/src/pstream.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup util
+ */
+
+/**
+ * @file pstream.h
+ * @author John Wiegley
+ *
+ * @ingroup util
+ */
+#ifndef _PSTREAM_H
+#define _PSTREAM_H
+
+//#include <istream>
+//#include <streambuf>
+
+class ptristream : public std::istream
+{
+ class ptrinbuf : public std::streambuf
+ {
+ ptrinbuf(const ptrinbuf&);
+ ptrinbuf& operator=(const ptrinbuf&);
+
+ protected:
+ char * ptr;
+ std::size_t len;
+
+ public:
+ ptrinbuf(char * _ptr, std::size_t _len) : ptr(_ptr), len(_len) {
+ if (*ptr && len == 0)
+ len = std::strlen(ptr);
+
+ setg(ptr, // beginning of putback area
+ ptr, // read position
+ ptr+len); // end position
+ }
+
+ protected:
+ virtual int_type underflow() {
+ // is read position before end of buffer?
+ if (gptr() < egptr())
+ return traits_type::to_int_type(*gptr());
+ else
+ return EOF;
+ }
+
+ virtual pos_type seekoff(off_type off, ios_base::seekdir way,
+ ios_base::openmode)
+ {
+ switch (way) {
+ case std::ios::cur:
+ setg(ptr, gptr()+off, ptr+len);
+ break;
+ case std::ios::beg:
+ setg(ptr, ptr+off, ptr+len);
+ break;
+ case std::ios::end:
+ setg(ptr, egptr()+off, ptr+len);
+ break;
+
+ default:
+ return pos_type(off_type(-1));
+ }
+ return pos_type(gptr() - ptr);
+ }
+ };
+
+protected:
+ ptrinbuf buf;
+
+public:
+ ptristream(char * ptr, std::size_t len = 0)
+ : std::istream(0), buf(ptr, len) {
+ rdbuf(&buf);
+ }
+};
+
+#endif // _PSTREAM_H
diff --git a/src/py_account.cc b/src/py_account.cc
new file mode 100644
index 00000000..056cd722
--- /dev/null
+++ b/src/py_account.cc
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "pyinterp.h"
+#include "pyutils.h"
+#include "account.h"
+#include "post.h"
+
+namespace ledger {
+
+using namespace boost::python;
+
+namespace {
+
+ long accounts_len(account_t& account)
+ {
+ return account.accounts.size();
+ }
+
+ account_t& accounts_getitem(account_t& account, long i)
+ {
+ static long last_index = 0;
+ static account_t * last_account = NULL;
+ static accounts_map::iterator elem;
+
+ long len = account.accounts.size();
+
+ if (labs(i) >= len) {
+ PyErr_SetString(PyExc_IndexError, _("Index out of range"));
+ throw_error_already_set();
+ }
+
+ if (&account == last_account && i == last_index + 1) {
+ last_index = i;
+ return *(*++elem).second;
+ }
+
+ long x = i < 0 ? len + i : i;
+ elem = account.accounts.begin();
+ while (--x >= 0)
+ elem++;
+
+ last_account = &account;
+ last_index = i;
+
+ return *(*elem).second;
+ }
+
+ account_t * py_find_account_1(journal_t& journal, const string& name)
+ {
+ return journal.find_account(name);
+ }
+
+ account_t * py_find_account_2(journal_t& journal, const string& name,
+ const bool auto_create)
+ {
+ return journal.find_account(name, auto_create);
+ }
+
+ account_t::xdata_t& py_xdata(account_t& account) {
+ return account.xdata();
+ }
+
+ PyObject * py_account_unicode(account_t& account) {
+ return str_to_py_unicode(account.fullname());
+ }
+
+} // unnamed namespace
+
+void export_account()
+{
+ scope().attr("ACCOUNT_EXT_SORT_CALC") = ACCOUNT_EXT_SORT_CALC;
+ scope().attr("ACCOUNT_EXT_HAS_NON_VIRTUALS") = ACCOUNT_EXT_HAS_NON_VIRTUALS;
+ scope().attr("ACCOUNT_EXT_HAS_UNB_VIRTUALS") = ACCOUNT_EXT_HAS_UNB_VIRTUALS;
+ scope().attr("ACCOUNT_EXT_AUTO_VIRTUALIZE") = ACCOUNT_EXT_AUTO_VIRTUALIZE;
+ scope().attr("ACCOUNT_EXT_VISITED") = ACCOUNT_EXT_VISITED;
+ scope().attr("ACCOUNT_EXT_MATCHING") = ACCOUNT_EXT_MATCHING;
+ scope().attr("ACCOUNT_EXT_TO_DISPLAY") = ACCOUNT_EXT_TO_DISPLAY;
+ scope().attr("ACCOUNT_EXT_DISPLAYED") = ACCOUNT_EXT_DISPLAYED;
+
+ class_< account_t::xdata_t::details_t > ("AccountXDataDetails")
+ .def_readonly("total", &account_t::xdata_t::details_t::total)
+ .def_readonly("calculated", &account_t::xdata_t::details_t::calculated)
+ .def_readonly("gathered", &account_t::xdata_t::details_t::gathered)
+
+ .def_readonly("posts_count", &account_t::xdata_t::details_t::posts_count)
+ .def_readonly("posts_virtuals_count",
+ &account_t::xdata_t::details_t::posts_virtuals_count)
+ .def_readonly("posts_cleared_count",
+ &account_t::xdata_t::details_t::posts_cleared_count)
+ .def_readonly("posts_last_7_count",
+ &account_t::xdata_t::details_t::posts_last_7_count)
+ .def_readonly("posts_last_30_count",
+ &account_t::xdata_t::details_t::posts_last_30_count)
+ .def_readonly("posts_this_month_count",
+ &account_t::xdata_t::details_t::posts_this_month_count)
+
+ .def_readonly("earliest_post",
+ &account_t::xdata_t::details_t::earliest_post)
+ .def_readonly("earliest_cleared_post",
+ &account_t::xdata_t::details_t::earliest_cleared_post)
+ .def_readonly("latest_post",
+ &account_t::xdata_t::details_t::latest_post)
+ .def_readonly("latest_cleared_post",
+ &account_t::xdata_t::details_t::latest_cleared_post)
+
+ .def_readonly("filenames", &account_t::xdata_t::details_t::filenames)
+ .def_readonly("accounts_referenced",
+ &account_t::xdata_t::details_t::accounts_referenced)
+ .def_readonly("payees_referenced",
+ &account_t::xdata_t::details_t::payees_referenced)
+
+ .def(self += self)
+
+ .def("update", &account_t::xdata_t::details_t::update)
+ ;
+
+ class_< account_t::xdata_t > ("AccountXData")
+#if 1
+ .add_property("flags",
+ &supports_flags<uint_least16_t>::flags,
+ &supports_flags<uint_least16_t>::set_flags)
+ .def("has_flags", &supports_flags<uint_least16_t>::has_flags)
+ .def("clear_flags", &supports_flags<uint_least16_t>::clear_flags)
+ .def("add_flags", &supports_flags<uint_least16_t>::add_flags)
+ .def("drop_flags", &supports_flags<uint_least16_t>::drop_flags)
+#endif
+
+ .def_readonly("self_details", &account_t::xdata_t::self_details)
+ .def_readonly("family_details", &account_t::xdata_t::family_details)
+ .def_readonly("reported_posts", &account_t::xdata_t::reported_posts)
+ .def_readonly("sort_values", &account_t::xdata_t::sort_values)
+ ;
+
+ scope().attr("ACCOUNT_NORMAL") = ACCOUNT_NORMAL;
+ scope().attr("ACCOUNT_KNOWN") = ACCOUNT_KNOWN;
+ scope().attr("ACCOUNT_TEMP") = ACCOUNT_TEMP;
+
+ class_< account_t > ("Account")
+#if 1
+ .add_property("flags",
+ &supports_flags<>::flags,
+ &supports_flags<>::set_flags)
+ .def("has_flags", &supports_flags<>::has_flags)
+ .def("clear_flags", &supports_flags<>::clear_flags)
+ .def("add_flags", &supports_flags<>::add_flags)
+ .def("drop_flags", &supports_flags<>::drop_flags)
+#endif
+
+ .add_property("parent",
+ make_getter(&account_t::parent,
+ return_internal_reference<>()))
+
+ .def_readwrite("name", &account_t::name)
+ .def_readwrite("note", &account_t::note)
+ .def_readonly("depth", &account_t::depth)
+
+ .def("__str__", &account_t::fullname)
+ .def("__unicode__", py_account_unicode)
+
+ .def("fullname", &account_t::fullname)
+ .def("partial_name", &account_t::partial_name)
+
+ .def("add_account", &account_t::add_account)
+ .def("remove_account", &account_t::remove_account)
+
+ .def("find_account", &account_t::find_account,
+ return_internal_reference<>())
+ .def("find_account_re", &account_t::find_account,
+ return_internal_reference<>())
+
+ .def("add_post", &account_t::add_post)
+ .def("remove_post", &account_t::remove_post)
+
+ .def("valid", &account_t::valid)
+
+ .def("__len__", accounts_len)
+ .def("__getitem__", accounts_getitem, return_internal_reference<>())
+
+ .def("__iter__", range<return_internal_reference<> >
+ (&account_t::accounts_begin, &account_t::accounts_end))
+ .def("accounts", range<return_internal_reference<> >
+ (&account_t::accounts_begin, &account_t::accounts_end))
+ .def("posts", range<return_internal_reference<> >
+ (&account_t::posts_begin, &account_t::posts_end))
+
+ .def("has_xdata", &account_t::has_xdata)
+ .def("clear_xdata", &account_t::clear_xdata)
+ .def("xdata", py_xdata,
+ return_internal_reference<>())
+
+ .def("amount", &account_t::amount)
+ .def("total", &account_t::total)
+
+ .def("self_details", &account_t::self_details,
+ return_internal_reference<>())
+ .def("family_details", &account_t::family_details,
+ return_internal_reference<>())
+
+ .def("has_xflags", &account_t::has_xflags)
+ .def("children_with_flags", &account_t::children_with_flags)
+ ;
+}
+
+} // namespace ledger
diff --git a/src/py_amount.cc b/src/py_amount.cc
new file mode 100644
index 00000000..8fb507a3
--- /dev/null
+++ b/src/py_amount.cc
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "pyinterp.h"
+#include "pyutils.h"
+#include "pyfstream.h"
+#include "commodity.h"
+#include "annotate.h"
+#include "pool.h"
+
+namespace ledger {
+
+using namespace boost::python;
+
+namespace {
+
+ boost::optional<amount_t> py_value_0(const amount_t& amount) {
+ return amount.value(false, CURRENT_TIME());
+ }
+ boost::optional<amount_t> py_value_1(const amount_t& amount,
+ commodity_t& in_terms_of) {
+ return amount.value(false, CURRENT_TIME(), in_terms_of);
+ }
+ boost::optional<amount_t> py_value_2(const amount_t& amount,
+ commodity_t& in_terms_of,
+ datetime_t& moment) {
+ return amount.value(false, moment, in_terms_of);
+ }
+
+ void py_parse_2(amount_t& amount, object in, unsigned char flags) {
+ if (PyFile_Check(in.ptr())) {
+ pyifstream instr(reinterpret_cast<PyFileObject *>(in.ptr()));
+ amount.parse(instr, flags);
+ } else {
+ PyErr_SetString(PyExc_IOError,
+ _("Argument to amount.parse(file) is not a file object"));
+ }
+ }
+ void py_parse_1(amount_t& amount, object in) {
+ py_parse_2(amount, in, 0);
+ }
+
+ void py_parse_str_1(amount_t& amount, const string& str) {
+ amount.parse(str);
+ }
+ void py_parse_str_2(amount_t& amount, const string& str, unsigned char flags) {
+ amount.parse(str, flags);
+ }
+
+ void py_print(amount_t& amount, object out) {
+ if (PyFile_Check(out.ptr())) {
+ pyofstream outstr(reinterpret_cast<PyFileObject *>(out.ptr()));
+ amount.print(outstr);
+ } else {
+ PyErr_SetString(PyExc_IOError,
+ _("Argument to amount.print_(file) is not a file object"));
+ }
+ }
+
+ annotation_t& py_amount_annotation(amount_t& amount) {
+ return amount.annotation();
+ }
+
+ amount_t py_strip_annotations_0(amount_t& amount) {
+ return amount.strip_annotations(keep_details_t());
+ }
+ amount_t py_strip_annotations_1(amount_t& amount, const keep_details_t& keep) {
+ return amount.strip_annotations(keep);
+ }
+
+ PyObject * py_amount_unicode(amount_t& amount) {
+ return str_to_py_unicode(amount.to_string());
+ }
+
+} // unnamed namespace
+
+#define EXC_TRANSLATOR(type) \
+ void exc_translate_ ## type(const type& err) { \
+ PyErr_SetString(PyExc_ArithmeticError, err.what()); \
+ }
+
+EXC_TRANSLATOR(amount_error)
+
+void export_amount()
+{
+ class_< amount_t > ("Amount")
+ .def("initialize", &amount_t::initialize) // only for the PyUnitTests
+ .staticmethod("initialize")
+ .def("shutdown", &amount_t::shutdown)
+ .staticmethod("shutdown")
+
+ .add_static_property("is_initialized",
+ make_getter(&amount_t::is_initialized),
+ make_setter(&amount_t::is_initialized))
+ .add_static_property("stream_fullstrings",
+ make_getter(&amount_t::stream_fullstrings),
+ make_setter(&amount_t::stream_fullstrings))
+
+ .def(init<long>())
+ .def(init<std::string>())
+
+ .def("exact", &amount_t::exact, args("value"),
+ _("Construct an amount object whose display precision is always equal to its\n\
+internal precision."))
+ .staticmethod("exact")
+
+ .def(init<amount_t>())
+
+ .def("compare", &amount_t::compare, args("amount"),
+ _("Compare two amounts for equality, returning <0, 0 or >0."))
+
+ .def(self == self)
+ .def(self == long())
+ .def(long() == self)
+
+ .def(self != self)
+ .def(self != long())
+ .def(long() != self)
+
+ .def(! self)
+
+ .def(self < self)
+ .def(self < long())
+ .def(long() < self)
+
+ .def(self <= self)
+ .def(self <= long())
+ .def(long() <= self)
+
+ .def(self > self)
+ .def(self > long())
+ .def(long() > self)
+
+ .def(self >= self)
+ .def(self >= long())
+ .def(long() >= self)
+
+ .def(self += self)
+ .def(self += long())
+
+ .def(self + self)
+ .def(self + long())
+ .def(long() + self)
+
+ .def(self -= self)
+ .def(self -= long())
+
+ .def(self - self)
+ .def(self - long())
+ .def(long() - self)
+
+ .def(self *= self)
+ .def(self *= long())
+
+ .def(self * self)
+ .def(self * long())
+ .def(long() * self)
+
+ .def(self /= self)
+ .def(self /= long())
+
+ .def(self / self)
+ .def(self / long())
+ .def(long() / self)
+
+ .add_property("precision", &amount_t::precision)
+ .add_property("display_precision", &amount_t::display_precision)
+ .add_property("keep_precision",
+ &amount_t::keep_precision,
+ &amount_t::set_keep_precision)
+
+ .def("negated", &amount_t::negated)
+ .def("in_place_negate", &amount_t::in_place_negate,
+ return_internal_reference<>())
+ .def(- self)
+
+ .def("abs", &amount_t::abs)
+ .def("__abs__", &amount_t::abs)
+
+ .def("inverted", &amount_t::inverted)
+
+ .def("rounded", &amount_t::rounded)
+ .def("in_place_round", &amount_t::in_place_round,
+ return_internal_reference<>())
+
+ .def("truncated", &amount_t::truncated)
+ .def("in_place_truncate", &amount_t::in_place_truncate,
+ return_internal_reference<>())
+
+ .def("floored", &amount_t::floored)
+ .def("in_place_floor", &amount_t::in_place_floor,
+ return_internal_reference<>())
+
+ .def("unrounded", &amount_t::unrounded)
+ .def("in_place_unround", &amount_t::in_place_unround,
+ return_internal_reference<>())
+
+ .def("reduced", &amount_t::reduced)
+ .def("in_place_reduce", &amount_t::in_place_reduce,
+ return_internal_reference<>())
+
+ .def("unreduced", &amount_t::unreduced)
+ .def("in_place_unreduce", &amount_t::in_place_unreduce,
+ return_internal_reference<>())
+
+ .def("value", py_value_0)
+ .def("value", py_value_1, args("in_terms_of"))
+ .def("value", py_value_2, args("in_terms_of", "moment"))
+
+ .def("price", &amount_t::price)
+
+ .def("sign", &amount_t::sign)
+ .def("__nonzero__", &amount_t::is_nonzero)
+ .def("is_nonzero", &amount_t::is_nonzero)
+ .def("is_zero", &amount_t::is_zero)
+ .def("is_realzero", &amount_t::is_realzero)
+ .def("is_null", &amount_t::is_null)
+
+ .def("to_double", &amount_t::to_double)
+ .def("__float__", &amount_t::to_double)
+ .def("to_long", &amount_t::to_long)
+ .def("__int__", &amount_t::to_long)
+ .def("fits_in_long", &amount_t::fits_in_long)
+
+ .def("__str__", &amount_t::to_string)
+ .def("to_string", &amount_t::to_string)
+ .def("__unicode__", py_amount_unicode)
+ .def("to_fullstring", &amount_t::to_fullstring)
+ .def("__repr__", &amount_t::to_fullstring)
+ .def("quantity_string", &amount_t::quantity_string)
+
+ .add_property("commodity",
+ make_function(&amount_t::commodity,
+ return_value_policy<reference_existing_object>()),
+ make_function(&amount_t::set_commodity,
+ with_custodian_and_ward<1, 2>()))
+ .def("has_commodity", &amount_t::has_commodity)
+ .def("clear_commodity", &amount_t::clear_commodity)
+
+ .def("number", &amount_t::number)
+
+ .def("annotate", &amount_t::annotate)
+ .def("has_annotation", &amount_t::has_annotation)
+ .add_property("annotation",
+ make_function(py_amount_annotation,
+ return_internal_reference<>()))
+ .def("strip_annotations", py_strip_annotations_0)
+ .def("strip_annotations", py_strip_annotations_1)
+
+ .def("parse", py_parse_1)
+ .def("parse", py_parse_2)
+ .def("parse", py_parse_str_1)
+ .def("parse", py_parse_str_2)
+
+ .def("parse_conversion", &amount_t::parse_conversion)
+ .staticmethod("parse_conversion")
+
+ .def("valid", &amount_t::valid)
+ ;
+
+ enum_< parse_flags_enum_t >("ParseFlags")
+ .value("Default", PARSE_DEFAULT)
+ .value("Partial", PARSE_PARTIAL)
+ .value("Single", PARSE_SINGLE)
+ .value("NoMigrate", PARSE_NO_MIGRATE)
+ .value("NoReduce", PARSE_NO_REDUCE)
+ .value("NoAssign", PARSE_NO_ASSIGN)
+ .value("NoDates", PARSE_NO_DATES)
+ .value("OpContext", PARSE_OP_CONTEXT)
+ .value("SoftFail", PARSE_SOFT_FAIL)
+ ;
+
+ register_optional_to_python<amount_t>();
+
+ implicitly_convertible<long, amount_t>();
+ implicitly_convertible<string, amount_t>();
+
+#define EXC_TRANSLATE(type) \
+ register_exception_translator<type>(&exc_translate_ ## type);
+
+ EXC_TRANSLATE(amount_error);
+}
+
+} // namespace ledger
diff --git a/src/py_balance.cc b/src/py_balance.cc
new file mode 100644
index 00000000..8c0c4c58
--- /dev/null
+++ b/src/py_balance.cc
@@ -0,0 +1,238 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "pyinterp.h"
+#include "pyutils.h"
+#include "pyfstream.h"
+#include "commodity.h"
+#include "annotate.h"
+#include "balance.h"
+
+namespace ledger {
+
+using namespace boost::python;
+
+namespace {
+
+ boost::optional<balance_t> py_value_0(const balance_t& balance) {
+ return balance.value(false, CURRENT_TIME());
+ }
+ boost::optional<balance_t> py_value_1(const balance_t& balance,
+ commodity_t& in_terms_of) {
+ return balance.value(false, CURRENT_TIME(), in_terms_of);
+ }
+ boost::optional<balance_t> py_value_2(const balance_t& balance,
+ commodity_t& in_terms_of,
+ datetime_t& moment) {
+ return balance.value(false, moment, in_terms_of);
+ }
+
+ boost::optional<amount_t>
+ py_commodity_amount_0(const balance_t& balance) {
+ return balance.commodity_amount();
+ }
+
+ boost::optional<amount_t>
+ py_commodity_amount_1(const balance_t& balance,
+ const boost::optional<const commodity_t&>& commodity) {
+ return balance.commodity_amount(commodity);
+ }
+
+ void py_print(balance_t& balance, object out) {
+ if (PyFile_Check(out.ptr())) {
+ pyofstream outstr(reinterpret_cast<PyFileObject *>(out.ptr()));
+ balance.print(outstr);
+ } else {
+ PyErr_SetString(PyExc_IOError,
+ _("Argument to balance.print_(file) is not a file object"));
+ }
+ }
+
+ long balance_len(balance_t& bal) {
+ return bal.amounts.size();
+ }
+
+ amount_t balance_getitem(balance_t& bal, long i) {
+ long len = bal.amounts.size();
+
+ if (labs(i) >= len) {
+ PyErr_SetString(PyExc_IndexError, _("Index out of range"));
+ throw_error_already_set();
+ }
+
+ long x = i < 0 ? len + i : i;
+ balance_t::amounts_map::iterator elem = bal.amounts.begin();
+ while (--x >= 0)
+ elem++;
+
+ return (*elem).second;
+ }
+
+ balance_t py_strip_annotations_0(balance_t& balance) {
+ return balance.strip_annotations(keep_details_t());
+ }
+ balance_t py_strip_annotations_1(balance_t& balance, const keep_details_t& keep) {
+ return balance.strip_annotations(keep);
+ }
+
+ PyObject * py_balance_unicode(balance_t& balance) {
+ return str_to_py_unicode(balance.to_string());
+ }
+
+} // unnamed namespace
+
+#define EXC_TRANSLATOR(type) \
+ void exc_translate_ ## type(const type& err) { \
+ PyErr_SetString(PyExc_ArithmeticError, err.what()); \
+ }
+
+EXC_TRANSLATOR(balance_error)
+
+void export_balance()
+{
+ class_< balance_t > ("Balance")
+ .def(init<balance_t>())
+ .def(init<amount_t>())
+ .def(init<long>())
+ .def(init<string>())
+
+ .def(self += self)
+ .def(self += other<amount_t>())
+ .def(self += long())
+ .def(self + self)
+ .def(self + other<amount_t>())
+ .def(self + long())
+ .def(self -= self)
+ .def(self -= other<amount_t>())
+ .def(self -= long())
+ .def(self - self)
+ .def(self - other<amount_t>())
+ .def(self - long())
+ .def(self *= other<amount_t>())
+ .def(self *= long())
+ .def(self * other<amount_t>())
+ .def(self * long())
+ .def(self /= other<amount_t>())
+ .def(self /= long())
+ .def(self / other<amount_t>())
+ .def(self / long())
+ .def(- self)
+
+ .def(self == self)
+ .def(self == other<amount_t>())
+ .def(self == long())
+ .def(self != self)
+ .def(self != other<amount_t>())
+ .def(self != long())
+ .def(! self)
+
+ .def("__str__", &balance_t::to_string)
+ .def("to_string", &balance_t::to_string)
+ .def("__unicode__", py_balance_unicode)
+
+ .def("negated", &balance_t::negated)
+ .def("in_place_negate", &balance_t::in_place_negate,
+ return_internal_reference<>())
+ .def(- self)
+
+ .def("abs", &balance_t::abs)
+ .def("__abs__", &balance_t::abs)
+
+ .def("__len__", balance_len)
+ .def("__getitem__", balance_getitem)
+
+ .def("rounded", &balance_t::rounded)
+ .def("in_place_round", &balance_t::in_place_round,
+ return_internal_reference<>())
+
+ .def("truncated", &balance_t::truncated)
+ .def("in_place_truncate", &balance_t::in_place_truncate,
+ return_internal_reference<>())
+
+ .def("floored", &balance_t::floored)
+ .def("in_place_floor", &balance_t::in_place_floor,
+ return_internal_reference<>())
+
+ .def("unrounded", &balance_t::unrounded)
+ .def("in_place_unround", &balance_t::in_place_unround,
+ return_internal_reference<>())
+
+ .def("reduced", &balance_t::reduced)
+ .def("in_place_reduce", &balance_t::in_place_reduce,
+ return_internal_reference<>())
+
+ .def("unreduced", &balance_t::unreduced)
+ .def("in_place_unreduce", &balance_t::in_place_unreduce,
+ return_internal_reference<>())
+
+ .def("value", py_value_0)
+ .def("value", py_value_1, args("in_terms_of"))
+ .def("value", py_value_2, args("in_terms_of", "moment"))
+
+ .def("price", &balance_t::price)
+
+ .def("__nonzero__", &balance_t::is_nonzero)
+ .def("is_nonzero", &balance_t::is_nonzero)
+ .def("is_zero", &balance_t::is_zero)
+ .def("is_realzero", &balance_t::is_realzero)
+
+ .def("is_empty", &balance_t::is_empty)
+ .def("single_amount", &balance_t::single_amount)
+
+ .def("to_amount", &balance_t::to_amount)
+
+ .def("commodity_count", &balance_t::commodity_count)
+ .def("commodity_amount", py_commodity_amount_0)
+ .def("commodity_amount", py_commodity_amount_1)
+
+ .def("number", &balance_t::number)
+
+ .def("strip_annotations", py_strip_annotations_0)
+ .def("strip_annotations", py_strip_annotations_1)
+
+ .def("valid", &balance_t::valid)
+ ;
+
+ register_optional_to_python<balance_t>();
+
+ implicitly_convertible<long, balance_t>();
+ implicitly_convertible<string, balance_t>();
+ implicitly_convertible<amount_t, balance_t>();
+
+#define EXC_TRANSLATE(type) \
+ register_exception_translator<type>(&exc_translate_ ## type);
+
+ EXC_TRANSLATE(balance_error);
+}
+
+} // namespace ledger
diff --git a/src/py_commodity.cc b/src/py_commodity.cc
new file mode 100644
index 00000000..984be5f0
--- /dev/null
+++ b/src/py_commodity.cc
@@ -0,0 +1,451 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "pyinterp.h"
+#include "pyutils.h"
+#include "commodity.h"
+#include "annotate.h"
+#include "pool.h"
+
+namespace ledger {
+
+using namespace boost::python;
+
+namespace {
+
+ commodity_t * py_create_1(commodity_pool_t& pool,
+ const string& symbol)
+ {
+ return pool.create(symbol);
+ }
+ commodity_t * py_create_2(commodity_pool_t& pool,
+ const string& symbol,
+ const annotation_t& details)
+ {
+ return pool.create(symbol, details);
+ }
+
+ commodity_t * py_find_or_create_1(commodity_pool_t& pool,
+ const string& symbol)
+ {
+ return pool.find_or_create(symbol);
+ }
+ commodity_t * py_find_or_create_2(commodity_pool_t& pool,
+ const string& symbol,
+ const annotation_t& details)
+ {
+ return pool.find_or_create(symbol, details);
+ }
+
+ commodity_t * py_find_1(commodity_pool_t& pool,
+ const string& name)
+ {
+ return pool.find(name);
+ }
+
+ commodity_t * py_find_2(commodity_pool_t& pool,
+ const string& symbol,
+ const annotation_t& details)
+ {
+ return pool.find(symbol, details);
+ }
+
+ // Exchange one commodity for another, while recording the factored price.
+
+ void py_exchange_2(commodity_pool_t& pool,
+ commodity_t& commodity,
+ const amount_t& per_unit_cost)
+ {
+ pool.exchange(commodity, per_unit_cost, CURRENT_TIME());
+ }
+ void py_exchange_3(commodity_pool_t& pool,
+ commodity_t& commodity,
+ const amount_t& per_unit_cost,
+ const datetime_t& moment)
+ {
+ pool.exchange(commodity, per_unit_cost, moment);
+ }
+
+ cost_breakdown_t py_exchange_5(commodity_pool_t& pool,
+ const amount_t& amount,
+ const amount_t& cost,
+ const bool is_per_unit,
+ const boost::optional<datetime_t>& moment,
+ const boost::optional<string>& tag)
+ {
+ return pool.exchange(amount, cost, is_per_unit, moment, tag);
+ }
+
+ commodity_t * py_pool_getitem(commodity_pool_t& pool, const string& symbol)
+ {
+ commodity_pool_t::commodities_map::iterator i =
+ pool.commodities.find(symbol);
+ if (i == pool.commodities.end()) {
+ PyErr_SetString(PyExc_ValueError,
+ (string("Could not find commodity ") + symbol).c_str());
+ throw boost::python::error_already_set();
+ }
+ return (*i).second;
+ }
+
+ python::list py_pool_keys(commodity_pool_t& pool) {
+ python::list keys;
+ BOOST_REVERSE_FOREACH
+ (const commodity_pool_t::commodities_map::value_type& pair,
+ pool.commodities) {
+ keys.insert(0, pair.first);
+ }
+ return keys;
+ }
+
+ bool py_pool_contains(commodity_pool_t& pool, const string& symbol) {
+ return pool.commodities.find(symbol) != pool.commodities.end();
+ }
+
+ commodity_pool_t::commodities_map::iterator
+ py_pool_commodities_begin(commodity_pool_t& pool) {
+ return pool.commodities.begin();
+ }
+ commodity_pool_t::commodities_map::iterator
+ py_pool_commodities_end(commodity_pool_t& pool) {
+ return pool.commodities.end();
+ }
+
+ typedef transform_iterator
+ <function<string(commodity_pool_t::commodities_map::value_type&)>,
+ commodity_pool_t::commodities_map::iterator>
+ commodities_map_firsts_iterator;
+ commodities_map_firsts_iterator
+
+ py_pool_commodities_keys_begin(commodity_pool_t& pool) {
+ return make_transform_iterator
+ (pool.commodities.begin(),
+ bind(&commodity_pool_t::commodities_map::value_type::first, _1));
+ }
+ commodities_map_firsts_iterator
+ py_pool_commodities_keys_end(commodity_pool_t& pool) {
+ return make_transform_iterator
+ (pool.commodities.end(),
+ bind(&commodity_pool_t::commodities_map::value_type::first, _1));
+ }
+
+ typedef transform_iterator
+ <function<commodity_t *(commodity_pool_t::commodities_map::value_type&)>,
+ commodity_pool_t::commodities_map::iterator>
+ commodities_map_seconds_iterator;
+
+ commodities_map_seconds_iterator
+ py_pool_commodities_values_begin(commodity_pool_t& pool) {
+ return make_transform_iterator
+ (pool.commodities.begin(),
+ bind(&commodity_pool_t::commodities_map::value_type::second, _1));
+ }
+ commodities_map_seconds_iterator
+ py_pool_commodities_values_end(commodity_pool_t& pool) {
+ return make_transform_iterator
+ (pool.commodities.end(),
+ bind(&commodity_pool_t::commodities_map::value_type::second, _1));
+ }
+
+ void py_add_price_2(commodity_t& commodity,
+ const datetime_t& date, const amount_t& price) {
+ commodity.add_price(date, price);
+ }
+
+ void py_add_price_3(commodity_t& commodity, const datetime_t& date,
+ const amount_t& price, const bool reflexive) {
+ commodity.add_price(date, price, reflexive);
+ }
+
+ bool py_keep_all_0(keep_details_t& details) {
+ return details.keep_all();
+ }
+ bool py_keep_all_1(keep_details_t& details, const commodity_t& comm) {
+ return details.keep_all(comm);
+ }
+
+ bool py_keep_any_0(keep_details_t& details) {
+ return details.keep_any();
+ }
+ bool py_keep_any_1(keep_details_t& details, const commodity_t& comm) {
+ return details.keep_any(comm);
+ }
+
+ commodity_t& py_commodity_referent(commodity_t& comm) {
+ return comm.referent();
+ }
+ commodity_t& py_annotated_commodity_referent(annotated_commodity_t& comm) {
+ return comm.referent();
+ }
+
+ commodity_t& py_strip_annotations_0(commodity_t& comm) {
+ return comm.strip_annotations(keep_details_t());
+ }
+ commodity_t& py_strip_annotations_1(commodity_t& comm,
+ const keep_details_t& keep) {
+ return comm.strip_annotations(keep);
+ }
+
+ commodity_t& py_strip_ann_annotations_0(annotated_commodity_t& comm) {
+ return comm.strip_annotations(keep_details_t());
+ }
+ commodity_t& py_strip_ann_annotations_1(annotated_commodity_t& comm,
+ const keep_details_t& keep) {
+ return comm.strip_annotations(keep);
+ }
+
+ boost::optional<amount_t> py_price(annotation_t& ann) {
+ return ann.price;
+ }
+ boost::optional<amount_t> py_set_price(annotation_t& ann,
+ const boost::optional<amount_t>& price) {
+ return ann.price = price;
+ }
+
+ PyObject * py_commodity_unicode(commodity_t& commodity) {
+ return str_to_py_unicode(commodity.symbol());
+ }
+
+} // unnamed namespace
+
+void export_commodity()
+{
+ class_< commodity_pool_t, shared_ptr<commodity_pool_t>,
+ boost::noncopyable > ("CommodityPool", no_init)
+ .add_property("null_commodity",
+ make_getter(&commodity_pool_t::null_commodity,
+ return_internal_reference<>()))
+ .add_property("default_commodity",
+ make_getter(&commodity_pool_t::default_commodity,
+ return_internal_reference<>()),
+ make_setter(&commodity_pool_t::default_commodity,
+ with_custodian_and_ward<1, 2>()))
+
+ .add_property("keep_base",
+ make_getter(&commodity_pool_t::keep_base),
+ make_setter(&commodity_pool_t::keep_base))
+ .add_property("price_db",
+ make_getter(&commodity_pool_t::price_db),
+ make_setter(&commodity_pool_t::price_db))
+ .add_property("quote_leeway",
+ make_getter(&commodity_pool_t::quote_leeway),
+ make_setter(&commodity_pool_t::quote_leeway))
+ .add_property("get_quotes",
+ make_getter(&commodity_pool_t::get_quotes),
+ make_setter(&commodity_pool_t::get_quotes))
+ .add_property("get_commodity_quote",
+ make_getter(&commodity_pool_t::get_commodity_quote),
+ make_setter(&commodity_pool_t::get_commodity_quote))
+
+ .def("make_qualified_name", &commodity_pool_t::make_qualified_name)
+
+ .def("create", py_create_1,
+ return_value_policy<reference_existing_object>())
+ .def("create", py_create_2,
+ return_value_policy<reference_existing_object>())
+
+ .def("find_or_create", py_find_or_create_1,
+ return_value_policy<reference_existing_object>())
+ .def("find_or_create", py_find_or_create_2,
+ return_value_policy<reference_existing_object>())
+
+ .def("find", py_find_1, return_value_policy<reference_existing_object>())
+ .def("find", py_find_2, return_value_policy<reference_existing_object>())
+
+ .def("exchange", py_exchange_2, with_custodian_and_ward<1, 2>())
+ .def("exchange", py_exchange_3, with_custodian_and_ward<1, 2>())
+ .def("exchange", py_exchange_5)
+
+ .def("parse_price_directive", &commodity_pool_t::parse_price_directive)
+ .def("parse_price_expression", &commodity_pool_t::parse_price_expression,
+ return_value_policy<reference_existing_object>())
+
+ .def("__getitem__", py_pool_getitem,
+ return_value_policy<reference_existing_object>())
+ .def("keys", py_pool_keys)
+ .def("has_key", py_pool_contains)
+ .def("__contains__", py_pool_contains)
+ .def("__iter__", range<return_value_policy<reference_existing_object> >
+ (py_pool_commodities_begin, py_pool_commodities_end))
+ .def("iteritems", range<return_value_policy<reference_existing_object> >
+ (py_pool_commodities_begin, py_pool_commodities_end))
+ .def("iterkeys", range<>(py_pool_commodities_keys_begin,
+ py_pool_commodities_keys_end))
+ .def("itervalues", range<return_value_policy<reference_existing_object> >
+ (py_pool_commodities_values_begin, py_pool_commodities_values_end))
+ ;
+
+ map_value_type_converter<commodity_pool_t::commodities_map>();
+
+ scope().attr("commodities") = commodity_pool_t::current_pool;
+
+ scope().attr("COMMODITY_STYLE_DEFAULTS") = COMMODITY_STYLE_DEFAULTS;
+ scope().attr("COMMODITY_STYLE_SUFFIXED") = COMMODITY_STYLE_SUFFIXED;
+ scope().attr("COMMODITY_STYLE_SEPARATED") = COMMODITY_STYLE_SEPARATED;
+ scope().attr("COMMODITY_STYLE_EUROPEAN") = COMMODITY_STYLE_EUROPEAN;
+ scope().attr("COMMODITY_STYLE_THOUSANDS") = COMMODITY_STYLE_THOUSANDS;
+ scope().attr("COMMODITY_NOMARKET") = COMMODITY_NOMARKET;
+ scope().attr("COMMODITY_BUILTIN") = COMMODITY_BUILTIN;
+ scope().attr("COMMODITY_WALKED") = COMMODITY_WALKED;
+ scope().attr("COMMODITY_KNOWN") = COMMODITY_KNOWN;
+ scope().attr("COMMODITY_PRIMARY") = COMMODITY_PRIMARY;
+
+ class_< commodity_t, boost::noncopyable > ("Commodity", no_init)
+#if 1
+ .add_property("flags",
+ &supports_flags<uint_least16_t>::flags,
+ &supports_flags<uint_least16_t>::set_flags)
+ .def("has_flags", &delegates_flags<uint_least16_t>::has_flags)
+ .def("clear_flags", &delegates_flags<uint_least16_t>::clear_flags)
+ .def("add_flags", &delegates_flags<uint_least16_t>::add_flags)
+ .def("drop_flags", &delegates_flags<uint_least16_t>::drop_flags)
+#endif
+
+ .add_static_property("european_by_default",
+ make_getter(&commodity_t::european_by_default),
+ make_setter(&commodity_t::european_by_default))
+
+ .def("__str__", &commodity_t::symbol)
+ .def("__unicode__", py_commodity_unicode)
+ .def("__nonzero__", &commodity_t::operator bool)
+
+ .def(self == self)
+
+ .def("symbol_needs_quotes", &commodity_t::symbol_needs_quotes)
+ .staticmethod("symbol_needs_quotes")
+
+ .add_property("referent",
+ make_function(py_commodity_referent,
+ return_value_policy<reference_existing_object>()))
+
+ .def("has_annotation", &commodity_t::has_annotation)
+ .def("strip_annotations", py_strip_annotations_0,
+ return_value_policy<reference_existing_object>())
+ .def("strip_annotations", py_strip_annotations_1,
+ return_value_policy<reference_existing_object>())
+ .def("write_annotations", &commodity_t::write_annotations)
+
+ .def("pool", &commodity_t::pool,
+ return_value_policy<reference_existing_object>())
+
+ .add_property("base_symbol", &commodity_t::base_symbol)
+ .add_property("symbol", &commodity_t::symbol)
+ .add_property("mapping_key", &commodity_t::mapping_key)
+
+ .add_property("name", &commodity_t::name, &commodity_t::set_name)
+ .add_property("note", &commodity_t::note, &commodity_t::set_note)
+ .add_property("precision", &commodity_t::precision,
+ &commodity_t::set_precision)
+ .add_property("smaller", &commodity_t::smaller, &commodity_t::set_smaller)
+ .add_property("larger", &commodity_t::larger, &commodity_t::set_larger)
+
+ .def("add_price", py_add_price_2)
+ .def("add_price", py_add_price_3)
+ .def("remove_price", &commodity_t::remove_price,
+ with_custodian_and_ward<1, 3>())
+ .def("find_price", &commodity_t::find_price)
+ .def("check_for_updated_price", &commodity_t::check_for_updated_price)
+
+ .def("valid", &commodity_t::valid)
+ ;
+
+ class_< annotation_t > ("Annotation", no_init)
+#if 1
+ .add_property("flags", &supports_flags<>::flags,
+ &supports_flags<>::set_flags)
+ .def("has_flags", &supports_flags<>::has_flags)
+ .def("clear_flags", &supports_flags<>::clear_flags)
+ .def("add_flags", &supports_flags<>::add_flags)
+ .def("drop_flags", &supports_flags<>::drop_flags)
+#endif
+
+ .add_property("price", py_price, py_set_price)
+ .add_property("date",
+ make_getter(&annotation_t::date),
+ make_setter(&annotation_t::date))
+ .add_property("tag",
+ make_getter(&annotation_t::tag),
+ make_setter(&annotation_t::tag))
+
+ .def("__nonzero__", &annotation_t::operator bool)
+
+ .def(self == self)
+
+ .def("valid", &annotation_t::valid)
+ ;
+
+ class_< keep_details_t > ("KeepDetails")
+ .def(init<bool, bool, bool, bool>())
+
+ .add_property("keep_price",
+ make_getter(&keep_details_t::keep_price),
+ make_setter(&keep_details_t::keep_price))
+ .add_property("keep_date",
+ make_getter(&keep_details_t::keep_date),
+ make_setter(&keep_details_t::keep_date))
+ .add_property("keep_tag",
+ make_getter(&keep_details_t::keep_tag),
+ make_setter(&keep_details_t::keep_tag))
+ .add_property("only_actuals",
+ make_getter(&keep_details_t::only_actuals),
+ make_setter(&keep_details_t::only_actuals))
+
+ .def("keep_all", py_keep_all_0)
+ .def("keep_all", py_keep_all_1)
+ .def("keep_any", py_keep_any_0)
+ .def("keep_any", py_keep_any_1)
+ ;
+
+ class_< annotated_commodity_t, bases<commodity_t>,
+ annotated_commodity_t, boost::noncopyable >
+ ("AnnotatedCommodity", no_init)
+ .add_property("details",
+ make_getter(&annotated_commodity_t::details),
+ make_setter(&annotated_commodity_t::details))
+
+ .def(self == self)
+ .def(self == other<commodity_t>())
+
+ .add_property("referent",
+ make_function(py_annotated_commodity_referent,
+ return_value_policy<reference_existing_object>()))
+
+ .def("strip_annotations", py_strip_ann_annotations_0,
+ return_value_policy<reference_existing_object>())
+ .def("strip_annotations", py_strip_ann_annotations_1,
+ return_value_policy<reference_existing_object>())
+ .def("write_annotations", &annotated_commodity_t::write_annotations)
+ ;
+}
+
+} // namespace ledger
diff --git a/src/py_expr.cc b/src/py_expr.cc
new file mode 100644
index 00000000..cad2c0fa
--- /dev/null
+++ b/src/py_expr.cc
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "pyinterp.h"
+
+namespace ledger {
+
+using namespace boost::python;
+
+namespace {
+ value_t py_expr_call(expr_t& expr)
+ {
+ return expr.calc();
+ }
+}
+
+void export_expr()
+{
+ class_< expr_t > ("Expr")
+ .def(init<string>())
+
+ .def("__nonzero__", &expr_t::operator bool)
+ .def("text", &expr_t::text)
+ .def("set_text", &expr_t::set_text)
+
+ //.def("parse", &expr_t::parse)
+
+ .def("__call__", py_expr_call)
+ .def("compile", &expr_t::compile)
+ //.def("calc", &expr_t::calc)
+
+ .def("is_constant", &expr_t::is_constant)
+
+ //.def("constant_value", &expr_t::constant_value)
+ ;
+}
+
+} // namespace ledger
diff --git a/src/py_format.cc b/src/py_format.cc
new file mode 100644
index 00000000..d4825b42
--- /dev/null
+++ b/src/py_format.cc
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "pyinterp.h"
+
+namespace ledger {
+
+using namespace boost::python;
+
+#define EXC_TRANSLATOR(type) \
+ void exc_translate_ ## type(const type& err) { \
+ PyErr_SetString(PyExc_ArithmeticError, err.what()); \
+ }
+
+//EXC_TRANSLATOR(format_error)
+
+void export_format()
+{
+#if 0
+ class_< format_t > ("Format")
+ ;
+#endif
+
+ //register_optional_to_python<amount_t>();
+
+ //implicitly_convertible<string, amount_t>();
+
+#define EXC_TRANSLATE(type) \
+ register_exception_translator<type>(&exc_translate_ ## type);
+
+ //EXC_TRANSLATE(format_error);
+}
+
+} // namespace ledger
diff --git a/src/py_item.cc b/src/py_item.cc
new file mode 100644
index 00000000..8a6e495a
--- /dev/null
+++ b/src/py_item.cc
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "pyinterp.h"
+#include "scope.h"
+#include "mask.h"
+#include "item.h"
+
+namespace ledger {
+
+using namespace boost::python;
+
+namespace {
+
+ bool py_has_tag_1s(item_t& item, const string& tag) {
+ return item.has_tag(tag);
+ }
+ bool py_has_tag_1m(item_t& item, const mask_t& tag_mask) {
+ return item.has_tag(tag_mask);
+ }
+ bool py_has_tag_2m(item_t& item, const mask_t& tag_mask,
+ const boost::optional<mask_t>& value_mask) {
+ return item.has_tag(tag_mask, value_mask);
+ }
+
+ boost::optional<string> py_get_tag_1s(item_t& item, const string& tag) {
+ return item.get_tag(tag);
+ }
+ boost::optional<string> py_get_tag_1m(item_t& item, const mask_t& tag_mask) {
+ return item.get_tag(tag_mask);
+ }
+ boost::optional<string> py_get_tag_2m(item_t& item, const mask_t& tag_mask,
+ const boost::optional<mask_t>& value_mask) {
+ return item.get_tag(tag_mask, value_mask);
+ }
+
+} // unnamed namespace
+
+#define EXC_TRANSLATOR(type) \
+ void exc_translate_ ## type(const type& err) { \
+ PyErr_SetString(PyExc_ArithmeticError, err.what()); \
+ }
+
+//EXC_TRANSLATOR(item_error)
+
+void export_item()
+{
+ class_< position_t > ("Position")
+ .add_property("pathname",
+ make_getter(&position_t::pathname),
+ make_setter(&position_t::pathname))
+ .add_property("beg_pos",
+ make_getter(&position_t::beg_pos),
+ make_setter(&position_t::beg_pos))
+ .add_property("beg_line",
+ make_getter(&position_t::beg_line),
+ make_setter(&position_t::beg_line))
+ .add_property("end_pos",
+ make_getter(&position_t::end_pos),
+ make_setter(&position_t::end_pos))
+ .add_property("end_line",
+ make_getter(&position_t::end_line),
+ make_setter(&position_t::end_line))
+ ;
+
+ scope().attr("ITEM_NORMAL") = ITEM_NORMAL;
+ scope().attr("ITEM_GENERATED") = ITEM_GENERATED;
+ scope().attr("ITEM_TEMP") = ITEM_TEMP;
+
+ enum_< item_t::state_t > ("State")
+ .value("Uncleared", item_t::UNCLEARED)
+ .value("Cleared", item_t::CLEARED)
+ .value("Pending", item_t::PENDING)
+ ;
+
+#if 0
+ class_< item_t, bases<scope_t> > ("JournalItem", init<uint_least8_t>())
+#else
+ class_< item_t > ("JournalItem", init<uint_least8_t>())
+#endif
+#if 1
+ .add_property("flags", &supports_flags<>::flags,
+ &supports_flags<>::set_flags)
+ .def("has_flags", &supports_flags<>::has_flags)
+ .def("clear_flags", &supports_flags<>::clear_flags)
+ .def("add_flags", &supports_flags<>::add_flags)
+ .def("drop_flags", &supports_flags<>::drop_flags)
+#endif
+
+ .add_property("note",
+ make_getter(&item_t::note),
+ make_setter(&item_t::note))
+ .add_property("pos",
+ make_getter(&item_t::pos),
+ make_setter(&item_t::pos))
+ .add_property("metadata",
+ make_getter(&item_t::metadata),
+ make_setter(&item_t::metadata))
+
+ .def("copy_details", &item_t::copy_details)
+
+ .def(self == self)
+ .def(self != self)
+
+ .def("has_tag", py_has_tag_1s)
+ .def("has_tag", py_has_tag_1m)
+ .def("has_tag", py_has_tag_2m)
+ .def("get_tag", py_get_tag_1s)
+ .def("get_tag", py_get_tag_1m)
+ .def("get_tag", py_get_tag_2m)
+ .def("tag", py_get_tag_1s)
+ .def("tag", py_get_tag_1m)
+ .def("tag", py_get_tag_2m)
+
+ .def("set_tag", &item_t::set_tag)
+
+ .def("parse_tags", &item_t::parse_tags)
+ .def("append_note", &item_t::append_note)
+
+ .add_static_property("use_effective_date",
+ make_getter(&item_t::use_effective_date),
+ make_setter(&item_t::use_effective_date))
+
+ .add_property("date", &item_t::date, make_setter(&item_t::_date))
+ .add_property("effective_date", &item_t::effective_date,
+ make_setter(&item_t::_date_eff))
+
+ .add_property("state", &item_t::state, &item_t::set_state)
+
+ .def("lookup", &item_t::lookup)
+
+ .def("valid", &item_t::valid)
+ ;
+}
+
+} // namespace ledger
diff --git a/src/py_journal.cc b/src/py_journal.cc
new file mode 100644
index 00000000..5be9cbe1
--- /dev/null
+++ b/src/py_journal.cc
@@ -0,0 +1,321 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "pyinterp.h"
+#include "pyutils.h"
+#include "journal.h"
+#include "xact.h"
+#include "post.h"
+#include "chain.h"
+#include "filters.h"
+#include "iterators.h"
+#include "scope.h"
+#include "report.h"
+
+namespace ledger {
+
+using namespace boost::python;
+
+namespace {
+
+ account_t& py_account_master(journal_t& journal) {
+ return *journal.master;
+ }
+
+ long xacts_len(journal_t& journal)
+ {
+ return journal.xacts.size();
+ }
+
+ xact_t& xacts_getitem(journal_t& journal, long i)
+ {
+ static long last_index = 0;
+ static journal_t * last_journal = NULL;
+ static xacts_list::iterator elem;
+
+ long len = journal.xacts.size();
+
+ if (labs(i) >= len) {
+ PyErr_SetString(PyExc_IndexError, _("Index out of range"));
+ throw_error_already_set();
+ }
+
+ if (&journal == last_journal && i == last_index + 1) {
+ last_index = i;
+ return **++elem;
+ }
+
+ long x = i < 0 ? len + i : i;
+ elem = journal.xacts.begin();
+ while (--x >= 0)
+ elem++;
+
+ last_journal = &journal;
+ last_index = i;
+
+ return **elem;
+ }
+
+ long accounts_len(account_t& account)
+ {
+ return account.accounts.size();
+ }
+
+ account_t& accounts_getitem(account_t& account, long i)
+ {
+ static long last_index = 0;
+ static account_t * last_account = NULL;
+ static accounts_map::iterator elem;
+
+ long len = account.accounts.size();
+
+ if (labs(i) >= len) {
+ PyErr_SetString(PyExc_IndexError, _("Index out of range"));
+ throw_error_already_set();
+ }
+
+ if (&account == last_account && i == last_index + 1) {
+ last_index = i;
+ return *(*++elem).second;
+ }
+
+ long x = i < 0 ? len + i : i;
+ elem = account.accounts.begin();
+ while (--x >= 0)
+ elem++;
+
+ last_account = &account;
+ last_index = i;
+
+ return *(*elem).second;
+ }
+
+ account_t * py_find_account_1(journal_t& journal, const string& name)
+ {
+ return journal.find_account(name);
+ }
+
+ account_t * py_find_account_2(journal_t& journal, const string& name,
+ const bool auto_create)
+ {
+ return journal.find_account(name, auto_create);
+ }
+
+ std::size_t py_read(journal_t& journal, const string& pathname)
+ {
+ return journal.read(pathname);
+ }
+
+ struct collector_wrapper
+ {
+ journal_t& journal;
+ report_t report;
+ collect_posts * posts_collector;
+ post_handler_ptr chain;
+
+ collector_wrapper(journal_t& _journal, report_t& base)
+ : journal(_journal), report(base),
+ posts_collector(new collect_posts) {}
+ ~collector_wrapper() {
+ journal.clear_xdata();
+ }
+
+ std::size_t length() const {
+ return posts_collector->length();
+ }
+
+ std::vector<post_t *>::iterator begin() {
+ return posts_collector->begin();
+ }
+ std::vector<post_t *>::iterator end() {
+ return posts_collector->end();
+ }
+ };
+
+ shared_ptr<collector_wrapper>
+ py_collect(journal_t& journal, const string& query)
+ {
+ if (journal.has_xdata()) {
+ PyErr_SetString(PyExc_RuntimeError,
+ _("Cannot have multiple journal collections open at once"));
+ throw_error_already_set();
+ }
+
+ report_t& current_report(downcast<report_t>(*scope_t::default_scope));
+ shared_ptr<collector_wrapper> coll(new collector_wrapper(journal,
+ current_report));
+ std::auto_ptr<journal_t> save_journal
+ (current_report.session.journal.release());
+ current_report.session.journal.reset(&journal);
+
+ try {
+ strings_list remaining =
+ process_arguments(split_arguments(query.c_str()), coll->report);
+ coll->report.normalize_options("register");
+
+ value_t args;
+ foreach (const string& arg, remaining)
+ args.push_back(string_value(arg));
+ coll->report.parse_query_args(args, "@Journal.collect");
+
+ journal_posts_iterator walker(coll->journal);
+ coll->chain =
+ chain_post_handlers(coll->report,
+ post_handler_ptr(coll->posts_collector));
+ pass_down_posts(coll->chain, walker);
+ }
+ catch (...) {
+ current_report.session.journal.release();
+ current_report.session.journal.reset(save_journal.release());
+ throw;
+ }
+ current_report.session.journal.release();
+ current_report.session.journal.reset(save_journal.release());
+
+ return coll;
+ }
+
+ post_t * posts_getitem(collector_wrapper& collector, long i)
+ {
+ post_t * post = collector.posts_collector->posts[i];
+ std::cerr << typeid(post).name() << std::endl;
+ std::cerr << typeid(*post).name() << std::endl;
+ std::cerr << typeid(post->account).name() << std::endl;
+ std::cerr << typeid(*post->account).name() << std::endl;
+ return post;
+ }
+
+} // unnamed namespace
+
+void export_journal()
+{
+ class_< item_handler<post_t>, shared_ptr<item_handler<post_t> >,
+ boost::noncopyable >("PostHandler")
+ ;
+
+ class_< collect_posts, bases<item_handler<post_t> >,
+ shared_ptr<collect_posts>, boost::noncopyable >("PostCollector")
+ .def("__len__", &collect_posts::length)
+ .def("__iter__", range<return_internal_reference<1,
+ with_custodian_and_ward_postcall<1, 0> > >
+ (&collect_posts::begin, &collect_posts::end))
+ ;
+
+ class_< collector_wrapper, shared_ptr<collector_wrapper>,
+ boost::noncopyable >("PostCollectorWrapper", no_init)
+ .def("__len__", &collector_wrapper::length)
+ .def("__getitem__", posts_getitem, return_internal_reference<1,
+ with_custodian_and_ward_postcall<0, 1> >())
+ .def("__iter__", range<return_value_policy<reference_existing_object,
+ with_custodian_and_ward_postcall<0, 1> > >
+ (&collector_wrapper::begin, &collector_wrapper::end))
+ ;
+
+ class_< journal_t::fileinfo_t > ("FileInfo")
+ .def(init<path>())
+
+ .add_property("filename",
+ make_getter(&journal_t::fileinfo_t::filename),
+ make_setter(&journal_t::fileinfo_t::filename))
+ .add_property("size",
+ make_getter(&journal_t::fileinfo_t::size),
+ make_setter(&journal_t::fileinfo_t::size))
+ .add_property("modtime",
+ make_getter(&journal_t::fileinfo_t::modtime),
+ make_setter(&journal_t::fileinfo_t::modtime))
+ .add_property("from_stream",
+ make_getter(&journal_t::fileinfo_t::from_stream),
+ make_setter(&journal_t::fileinfo_t::from_stream))
+ ;
+
+ class_< journal_t, boost::noncopyable > ("Journal")
+ .def(init<path>())
+ .def(init<string>())
+
+ .add_property("master",
+ make_getter(&journal_t::master,
+ return_internal_reference<1,
+ with_custodian_and_ward_postcall<1, 0> >()))
+ .add_property("bucket",
+ make_getter(&journal_t::bucket,
+ return_internal_reference<1,
+ with_custodian_and_ward_postcall<1, 0> >()),
+ make_setter(&journal_t::bucket))
+ .add_property("was_loaded", make_getter(&journal_t::was_loaded))
+
+ .def("add_account", &journal_t::add_account)
+ .def("remove_account", &journal_t::remove_account)
+
+ .def("find_account", py_find_account_1,
+ return_internal_reference<1,
+ with_custodian_and_ward_postcall<0, 1> >())
+ .def("find_account", py_find_account_2,
+ return_internal_reference<1,
+ with_custodian_and_ward_postcall<0, 1> >())
+ .def("find_account_re", &journal_t::find_account_re,
+ return_internal_reference<1,
+ with_custodian_and_ward_postcall<0, 1> >())
+
+ .def("add_xact", &journal_t::add_xact)
+ .def("remove_xact", &journal_t::remove_xact)
+
+ .def("__len__", xacts_len)
+#if 0
+ .def("__getitem__", xacts_getitem,
+ return_internal_reference<1,
+ with_custodian_and_ward_postcall<0, 1> >())
+#endif
+
+ .def("__iter__", range<return_internal_reference<> >
+ (&journal_t::xacts_begin, &journal_t::xacts_end))
+ .def("xacts", range<return_internal_reference<> >
+ (&journal_t::xacts_begin, &journal_t::xacts_end))
+ .def("auto_xacts", range<return_internal_reference<> >
+ (&journal_t::auto_xacts_begin, &journal_t::auto_xacts_end))
+ .def("period_xacts", range<return_internal_reference<> >
+ (&journal_t::period_xacts_begin, &journal_t::period_xacts_end))
+ .def("sources", range<return_internal_reference<> >
+ (&journal_t::sources_begin, &journal_t::sources_end))
+
+ .def("read", py_read)
+
+ .def("has_xdata", &journal_t::has_xdata)
+ .def("clear_xdata", &journal_t::clear_xdata)
+
+ .def("collect", py_collect, with_custodian_and_ward_postcall<0, 1>())
+
+ .def("valid", &journal_t::valid)
+ ;
+}
+
+} // namespace ledger
diff --git a/src/py_post.cc b/src/py_post.cc
new file mode 100644
index 00000000..20cdba6b
--- /dev/null
+++ b/src/py_post.cc
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "pyinterp.h"
+#include "post.h"
+#include "xact.h"
+
+namespace ledger {
+
+using namespace boost::python;
+
+namespace {
+
+ bool py_has_tag_1s(post_t& post, const string& tag) {
+ return post.has_tag(tag);
+ }
+ bool py_has_tag_1m(post_t& post, const mask_t& tag_mask) {
+ return post.has_tag(tag_mask);
+ }
+ bool py_has_tag_2m(post_t& post, const mask_t& tag_mask,
+ const boost::optional<mask_t>& value_mask) {
+ return post.has_tag(tag_mask, value_mask);
+ }
+
+ boost::optional<string> py_get_tag_1s(post_t& post, const string& tag) {
+ return post.get_tag(tag);
+ }
+ boost::optional<string> py_get_tag_1m(post_t& post, const mask_t& tag_mask) {
+ return post.get_tag(tag_mask);
+ }
+ boost::optional<string> py_get_tag_2m(post_t& post, const mask_t& tag_mask,
+ const boost::optional<mask_t>& value_mask) {
+ return post.get_tag(tag_mask, value_mask);
+ }
+
+ post_t::xdata_t& py_xdata(post_t& post) {
+ return post.xdata();
+ }
+
+ account_t * py_reported_account(post_t& post) {
+ return post.reported_account();
+ }
+
+} // unnamed namespace
+
+void export_post()
+{
+ scope().attr("POST_EXT_RECEIVED") = POST_EXT_RECEIVED;
+ scope().attr("POST_EXT_HANDLED") = POST_EXT_HANDLED;
+ scope().attr("POST_EXT_DISPLAYED") = POST_EXT_DISPLAYED;
+ scope().attr("POST_EXT_DIRECT_AMT") = POST_EXT_DIRECT_AMT;
+ scope().attr("POST_EXT_SORT_CALC") = POST_EXT_SORT_CALC;
+ scope().attr("POST_EXT_COMPOUND") = POST_EXT_COMPOUND;
+ scope().attr("POST_EXT_VISITED") = POST_EXT_VISITED;
+ scope().attr("POST_EXT_MATCHES") = POST_EXT_MATCHES;
+ scope().attr("POST_EXT_CONSIDERED") = POST_EXT_CONSIDERED;
+
+ class_< post_t::xdata_t > ("PostingXData")
+#if 1
+ .add_property("flags",
+ &supports_flags<uint_least16_t>::flags,
+ &supports_flags<uint_least16_t>::set_flags)
+ .def("has_flags", &supports_flags<uint_least16_t>::has_flags)
+ .def("clear_flags", &supports_flags<uint_least16_t>::clear_flags)
+ .def("add_flags", &supports_flags<uint_least16_t>::add_flags)
+ .def("drop_flags", &supports_flags<uint_least16_t>::drop_flags)
+#endif
+
+ .add_property("visited_value",
+ make_getter(&post_t::xdata_t::visited_value),
+ make_setter(&post_t::xdata_t::visited_value))
+ .add_property("compound_value",
+ make_getter(&post_t::xdata_t::compound_value),
+ make_setter(&post_t::xdata_t::compound_value))
+ .add_property("total",
+ make_getter(&post_t::xdata_t::total),
+ make_setter(&post_t::xdata_t::total))
+ .add_property("count",
+ make_getter(&post_t::xdata_t::count),
+ make_setter(&post_t::xdata_t::count))
+ .add_property("date",
+ make_getter(&post_t::xdata_t::date),
+ make_setter(&post_t::xdata_t::date))
+ .add_property("datetime",
+ make_getter(&post_t::xdata_t::datetime),
+ make_setter(&post_t::xdata_t::datetime))
+ .add_property("account",
+ make_getter(&post_t::xdata_t::account,
+ return_value_policy<reference_existing_object>()),
+ make_setter(&post_t::xdata_t::account,
+ with_custodian_and_ward<1, 2>()))
+ .add_property("sort_values",
+ make_getter(&post_t::xdata_t::sort_values),
+ make_setter(&post_t::xdata_t::sort_values))
+ ;
+
+ scope().attr("POST_VIRTUAL") = POST_VIRTUAL;
+ scope().attr("POST_MUST_BALANCE") = POST_MUST_BALANCE;
+ scope().attr("POST_CALCULATED") = POST_CALCULATED;
+ scope().attr("POST_COST_CALCULATED") = POST_COST_CALCULATED;
+
+ class_< post_t, bases<item_t> > ("Posting")
+ //.def(init<account_t *>())
+
+ .add_property("xact",
+ make_getter(&post_t::xact,
+ return_internal_reference<>()),
+ make_setter(&post_t::xact,
+ with_custodian_and_ward<1, 2>()))
+ .add_property("account",
+ make_getter(&post_t::account,
+ return_internal_reference<>()),
+ make_setter(&post_t::account,
+ with_custodian_and_ward<1, 2>()))
+ .add_property("amount",
+ make_getter(&post_t::amount),
+ make_setter(&post_t::amount))
+ .add_property("cost",
+ make_getter(&post_t::cost),
+ make_setter(&post_t::cost))
+ .add_property("assigned_amount",
+ make_getter(&post_t::assigned_amount),
+ make_setter(&post_t::assigned_amount))
+
+ .def("has_tag", py_has_tag_1s)
+ .def("has_tag", py_has_tag_1m)
+ .def("has_tag", py_has_tag_2m)
+ .def("get_tag", py_get_tag_1s)
+ .def("get_tag", py_get_tag_1m)
+ .def("get_tag", py_get_tag_2m)
+
+ .def("date", &post_t::date)
+ .def("effective_date", &post_t::effective_date)
+
+ .def("must_balance", &post_t::must_balance)
+
+ .def("lookup", &post_t::lookup)
+
+ .def("valid", &post_t::valid)
+
+ .def("has_xdata", &post_t::has_xdata)
+ .def("clear_xdata", &post_t::clear_xdata)
+ .def("xdata", py_xdata,
+ return_internal_reference<>())
+
+ .def("add_to_value", &post_t::add_to_value)
+ .def("set_reported_account", &post_t::set_reported_account)
+
+ .def("reported_account", py_reported_account,
+ return_internal_reference<>())
+ ;
+}
+
+} // namespace ledger
diff --git a/src/py_times.cc b/src/py_times.cc
new file mode 100644
index 00000000..a62bb9b6
--- /dev/null
+++ b/src/py_times.cc
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "pyinterp.h"
+#include "pyutils.h"
+#include "times.h"
+
+// jww (2007-05-04): Convert time duration objects to PyDelta
+
+namespace ledger {
+
+using namespace boost::python;
+
+#define MY_PyDateTime_IMPORT \
+ PyDateTimeAPI = (PyDateTime_CAPI*) \
+ PyCObject_Import(const_cast<char *>("datetime"), \
+ const_cast<char *>("datetime_CAPI"))
+
+struct date_to_python
+{
+ static PyObject* convert(const date_t& dte)
+ {
+ MY_PyDateTime_IMPORT;
+ return PyDate_FromDate(dte.year(), dte.month(), dte.day());
+ }
+};
+
+struct date_from_python
+{
+ static void* convertible(PyObject* obj_ptr)
+ {
+ MY_PyDateTime_IMPORT;
+ if (PyDate_Check(obj_ptr)) return obj_ptr;
+ return 0;
+ }
+
+ static void construct(PyObject * obj_ptr,
+ converter::rvalue_from_python_stage1_data * data)
+ {
+ MY_PyDateTime_IMPORT;
+
+ int year = PyDateTime_GET_YEAR(obj_ptr);
+ date::year_type y = gregorian::greg_year(static_cast<unsigned short>(year));
+ date::month_type m =
+ static_cast<date::month_type>(PyDateTime_GET_MONTH(obj_ptr));
+ date::day_type d =
+ static_cast<date::day_type>(PyDateTime_GET_DAY(obj_ptr));
+
+ date_t * dte = new date_t(y, m, d);
+
+ data->convertible = (void *) dte;
+ }
+};
+
+typedef register_python_conversion<date_t, date_to_python, date_from_python>
+ date_python_conversion;
+
+
+struct datetime_to_python
+{
+ static PyObject* convert(const datetime_t& moment)
+ {
+ MY_PyDateTime_IMPORT;
+
+ date_t dte = moment.date();
+ datetime_t::time_duration_type tod = moment.time_of_day();
+
+ return PyDateTime_FromDateAndTime
+ (static_cast<int>(dte.year()), static_cast<int>(dte.month()),
+ static_cast<int>(dte.day()), static_cast<int>(tod.hours()),
+ static_cast<int>(tod.minutes()), static_cast<int>(tod.seconds()),
+ static_cast<int>(tod.total_microseconds() % 1000000));
+ }
+};
+
+struct datetime_from_python
+{
+ static void* convertible(PyObject* obj_ptr)
+ {
+ MY_PyDateTime_IMPORT;
+ if(PyDateTime_Check(obj_ptr)) return obj_ptr;
+ return 0;
+ }
+
+ static void construct(PyObject * obj_ptr,
+ converter::rvalue_from_python_stage1_data * data)
+ {
+ MY_PyDateTime_IMPORT;
+
+ int year = PyDateTime_GET_YEAR(obj_ptr);
+ date::year_type y = gregorian::greg_year(static_cast<unsigned short>(year));
+ date::month_type m =
+ static_cast<date::month_type>(PyDateTime_GET_MONTH(obj_ptr));
+ date::day_type d =
+ static_cast<date::day_type>(PyDateTime_GET_DAY(obj_ptr));
+
+ datetime_t::time_duration_type::hour_type h =
+ static_cast<datetime_t::time_duration_type::hour_type>
+ (PyDateTime_DATE_GET_HOUR(obj_ptr));
+ datetime_t::time_duration_type::min_type min =
+ static_cast<datetime_t::time_duration_type::min_type>
+ (PyDateTime_DATE_GET_MINUTE(obj_ptr));
+ datetime_t::time_duration_type::sec_type s =
+ static_cast<datetime_t::time_duration_type::sec_type>
+ (PyDateTime_DATE_GET_SECOND(obj_ptr));
+ datetime_t::time_duration_type::fractional_seconds_type ms =
+ static_cast<datetime_t::time_duration_type::fractional_seconds_type>
+ (PyDateTime_DATE_GET_MICROSECOND(obj_ptr)) * 1000000;
+
+ datetime_t * moment
+ = new datetime_t(date_t(y, m, d),
+ datetime_t::time_duration_type(h, min, s, ms));
+
+ data->convertible = (void *) moment;
+ }
+};
+
+typedef register_python_conversion<datetime_t,
+ datetime_to_python, datetime_from_python>
+ datetime_python_conversion;
+
+
+/* Convert time_duration to/from python */
+struct duration_to_python
+{
+ static int get_usecs(boost::posix_time::time_duration const& d)
+ {
+ static int64_t resolution =
+ boost::posix_time::time_duration::ticks_per_second();
+ int64_t fracsecs = d.fractional_seconds();
+ if (resolution > 1000000)
+ return static_cast<int>(fracsecs / (resolution / 1000000));
+ else
+ return static_cast<int>(fracsecs * (1000000 / resolution));
+ }
+
+ static PyObject * convert(posix_time::time_duration d)
+ {
+ int days = d.hours() / 24;
+ if (days < 0)
+ days --;
+ int seconds = d.total_seconds() - days*(24*3600);
+ int usecs = get_usecs(d);
+ if (days < 0)
+ usecs = 1000000-1 - usecs;
+ return PyDelta_FromDSU(days, seconds, usecs);
+ }
+};
+
+/* Should support the negative values, but not the special boost time
+ durations */
+struct duration_from_python
+{
+ static void* convertible(PyObject * obj_ptr)
+ {
+ if ( ! PyDelta_Check(obj_ptr))
+ return 0;
+ return obj_ptr;
+ }
+
+ static void construct(PyObject* obj_ptr,
+ python::converter::rvalue_from_python_stage1_data * data)
+ {
+ PyDateTime_Delta const* pydelta
+ = reinterpret_cast<PyDateTime_Delta*>(obj_ptr);
+
+ int days = pydelta->days;
+ bool is_negative = (days < 0);
+ if (is_negative)
+ days = -days;
+
+ // Create time duration object
+ posix_time::time_duration
+ duration = (posix_time::hours(24) * days +
+ posix_time::seconds(pydelta->seconds) +
+ posix_time::microseconds(pydelta->microseconds));
+ if (is_negative)
+ duration = duration.invert_sign();
+
+ void * storage =
+ reinterpret_cast<converter::rvalue_from_python_storage
+ <posix_time::time_duration> *>
+ (data)->storage.bytes;
+
+ new (storage) posix_time::time_duration(duration);
+ data->convertible = storage;
+ }
+};
+
+typedef register_python_conversion<time_duration_t,
+ duration_to_python, duration_from_python>
+ duration_python_conversion;
+
+
+datetime_t py_parse_datetime(const string& str) {
+ return parse_datetime(str);
+}
+
+date_t py_parse_date(const string& str) {
+ return parse_date(str);
+}
+
+void export_times()
+{
+ datetime_python_conversion();
+ date_python_conversion();
+ duration_python_conversion();
+
+ register_optional_to_python<datetime_t>();
+ register_optional_to_python<date_t>();
+
+ scope().attr("parse_datetime") = &py_parse_datetime;
+ scope().attr("parse_date") = &py_parse_date;
+ scope().attr("times_initialize") = &times_initialize;
+ scope().attr("times_shutdown") = &times_shutdown;
+
+#if 0
+ class_< interval_t > ("Interval")
+ ;
+#endif
+}
+
+} // namespace ledger
diff --git a/src/py_utils.cc b/src/py_utils.cc
new file mode 100644
index 00000000..5203599f
--- /dev/null
+++ b/src/py_utils.cc
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "pyinterp.h"
+#include "pyutils.h"
+#include "pyfstream.h"
+
+namespace ledger {
+
+using namespace boost::python;
+
+struct bool_to_python
+{
+ static PyObject * convert(const bool truth)
+ {
+ if (truth)
+ Py_RETURN_TRUE;
+ else
+ Py_RETURN_FALSE;
+ }
+};
+
+struct bool_from_python
+{
+ static void* convertible(PyObject* obj_ptr)
+ {
+ if (!PyBool_Check(obj_ptr)) return 0;
+ return obj_ptr;
+ }
+
+ static void construct(PyObject* obj_ptr,
+ converter::rvalue_from_python_stage1_data* data)
+ {
+ void * storage =
+ ((converter::rvalue_from_python_storage<bool>*) data)->storage.bytes;
+ if (obj_ptr == Py_True)
+ new (storage) bool(true);
+ else
+ new (storage) bool(false);
+ data->convertible = storage;
+ }
+};
+
+typedef register_python_conversion<bool, bool_to_python, bool_from_python>
+ bool_python_conversion;
+
+
+struct string_to_python
+{
+ static PyObject* convert(const string& str)
+ {
+ // Return bytes, not characters; see __unicode__ methods for that
+ return incref(object(static_cast<const std::string&>(str)).ptr());
+ }
+};
+
+struct string_from_python
+{
+ static void* convertible(PyObject* obj_ptr)
+ {
+ if (!PyUnicode_Check(obj_ptr) &&
+ !PyString_Check(obj_ptr)) return 0;
+ return obj_ptr;
+ }
+
+ static void construct(PyObject* obj_ptr,
+ converter::rvalue_from_python_stage1_data* data)
+ {
+ if (PyString_Check(obj_ptr)) {
+ const char* value = PyString_AsString(obj_ptr);
+ if (value == 0) throw_error_already_set();
+ void* storage =
+ reinterpret_cast<converter::rvalue_from_python_storage<string> *>
+ (data)->storage.bytes;
+ new (storage) string(value);
+ data->convertible = storage;
+ } else {
+ VERIFY(PyUnicode_Check(obj_ptr));
+
+ Py_ssize_t size = PyUnicode_GET_SIZE(obj_ptr);
+ const Py_UNICODE* value = PyUnicode_AS_UNICODE(obj_ptr);
+
+ string str;
+ if (sizeof(Py_UNICODE) == 2) // UTF-16
+ utf8::unchecked::utf16to8(value, value + size, std::back_inserter(str));
+ else if (sizeof(Py_UNICODE) == 4) // UTF-32
+ utf8::unchecked::utf32to8(value, value + size, std::back_inserter(str));
+ else
+ assert(! "Py_UNICODE has an unexpected size");
+
+ if (value == 0) throw_error_already_set();
+ void* storage =
+ reinterpret_cast<converter::rvalue_from_python_storage<string> *>
+ (data)->storage.bytes;
+ new (storage) string(str);
+ data->convertible = storage;
+ }
+ }
+};
+
+typedef register_python_conversion<string, string_to_python, string_from_python>
+ string_python_conversion;
+
+
+struct istream_to_python
+{
+ static PyObject* convert(const std::istream&)
+ {
+ return incref(boost::python::detail::none());
+ }
+};
+
+struct istream_from_python
+{
+ static void* convertible(PyObject* obj_ptr)
+ {
+ if (!PyFile_Check(obj_ptr)) return 0;
+ return obj_ptr;
+ }
+
+ static void construct(PyObject* obj_ptr,
+ converter::rvalue_from_python_stage1_data* data)
+ {
+ void* storage =
+ reinterpret_cast<converter::rvalue_from_python_storage<pyifstream> *>
+ (data)->storage.bytes;
+ new (storage) pyifstream(reinterpret_cast<PyFileObject *>(obj_ptr));
+ data->convertible = storage;
+ }
+};
+
+typedef register_python_conversion<std::istream,
+ istream_to_python, istream_from_python>
+ istream_python_conversion;
+
+
+struct ostream_to_python
+{
+ static PyObject* convert(const std::ostream&)
+ {
+ return incref(boost::python::detail::none());
+ }
+};
+
+struct ostream_from_python
+{
+ static void* convertible(PyObject* obj_ptr)
+ {
+ if (!PyFile_Check(obj_ptr)) return 0;
+ return obj_ptr;
+ }
+
+ static void construct(PyObject* obj_ptr,
+ converter::rvalue_from_python_stage1_data* data)
+ {
+ void* storage =
+ reinterpret_cast<converter::rvalue_from_python_storage<pyofstream> *>
+ (data)->storage.bytes;
+ new (storage) pyofstream(reinterpret_cast<PyFileObject *>(obj_ptr));
+ data->convertible = storage;
+ }
+};
+
+typedef register_python_conversion<std::ostream,
+ ostream_to_python, ostream_from_python>
+ ostream_python_conversion;
+
+
+void export_utils()
+{
+ class_< supports_flags<uint_least8_t> > ("SupportFlags8")
+ .def(init<supports_flags<uint_least8_t> >())
+ .def(init<uint_least8_t>())
+
+ .add_property("flags",
+ &supports_flags<uint_least8_t>::flags,
+ &supports_flags<uint_least8_t>::set_flags)
+ .def("has_flags", &supports_flags<uint_least8_t>::has_flags)
+ .def("clear_flags", &supports_flags<uint_least8_t>::clear_flags)
+ .def("add_flags", &supports_flags<uint_least8_t>::add_flags)
+ .def("drop_flags", &supports_flags<uint_least8_t>::drop_flags)
+ ;
+
+ class_< supports_flags<uint_least16_t> > ("SupportFlags16")
+ .def(init<supports_flags<uint_least16_t> >())
+ .def(init<uint_least16_t>())
+
+ .add_property("flags",
+ &supports_flags<uint_least16_t>::flags,
+ &supports_flags<uint_least16_t>::set_flags)
+ .def("has_flags", &supports_flags<uint_least16_t>::has_flags)
+ .def("clear_flags", &supports_flags<uint_least16_t>::clear_flags)
+ .def("add_flags", &supports_flags<uint_least16_t>::add_flags)
+ .def("drop_flags", &supports_flags<uint_least16_t>::drop_flags)
+ ;
+
+#if 0
+ class_< basic_flags_t<uint_least8_t>,
+ bases<supports_flags<uint_least8_t> > > ("BasicFlags8")
+ .def(init<uint_least8_t>())
+
+ .def("plus_flags", &basic_flags_t<uint_least8_t>::plus_flags)
+ .def("minus_flags", &basic_flags_t<uint_least8_t>::minus_flags)
+ ;
+#endif
+
+ class_< delegates_flags<uint_least16_t>,
+ boost::noncopyable > ("DelegatesFlags16", no_init)
+ .add_property("flags",
+ &delegates_flags<uint_least16_t>::flags,
+ &delegates_flags<uint_least16_t>::set_flags)
+ .def("has_flags", &delegates_flags<uint_least16_t>::has_flags)
+ .def("clear_flags", &delegates_flags<uint_least16_t>::clear_flags)
+ .def("add_flags", &delegates_flags<uint_least16_t>::add_flags)
+ .def("drop_flags", &delegates_flags<uint_least16_t>::drop_flags)
+ ;
+
+ bool_python_conversion();
+ string_python_conversion();
+ istream_python_conversion();
+ ostream_python_conversion();
+}
+
+} // namespace ledger
diff --git a/src/py_value.cc b/src/py_value.cc
new file mode 100644
index 00000000..713dc3d4
--- /dev/null
+++ b/src/py_value.cc
@@ -0,0 +1,380 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "pyinterp.h"
+#include "pyutils.h"
+#include "commodity.h"
+#include "annotate.h"
+
+namespace ledger {
+
+using namespace boost::python;
+
+BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(value_overloads, value, 0, 2)
+BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(exchange_commodities_overloads,
+ exchange_commodities, 1, 2)
+BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(set_string_overloads, set_string, 0, 2)
+
+namespace {
+
+ boost::optional<value_t> py_value_0(const value_t& value) {
+ return value.value(false, CURRENT_TIME());
+ }
+ boost::optional<value_t> py_value_1(const value_t& value,
+ commodity_t& in_terms_of) {
+ return value.value(false, CURRENT_TIME(), in_terms_of);
+ }
+ boost::optional<value_t> py_value_2(const value_t& value,
+ commodity_t& in_terms_of,
+ datetime_t& moment) {
+ return value.value(false, moment, in_terms_of);
+ }
+
+ PyObject * py_base_type(value_t& value)
+ {
+ if (value.is_boolean()) {
+ return (PyObject *)&PyBool_Type;
+ }
+ else if (value.is_long()) {
+ return (PyObject *)&PyInt_Type;
+ }
+ else if (value.is_string()) {
+ return (PyObject *)&PyUnicode_Type;
+ }
+ else {
+ object typeobj(object(value).attr("__class__"));
+ return typeobj.ptr();
+ }
+ }
+
+ string py_dump(const value_t& value) {
+ std::ostringstream buf;
+ value.dump(buf);
+ return buf.str();
+ }
+
+ string py_dump_relaxed(const value_t& value) {
+ std::ostringstream buf;
+ value.dump(buf, true);
+ return buf.str();
+ }
+
+ void py_set_string(value_t& value, const string& str) {
+ return value.set_string(str);
+ }
+
+ annotation_t& py_value_annotation(value_t& value) {
+ return value.annotation();
+ }
+
+ value_t py_strip_annotations_0(value_t& value) {
+ return value.strip_annotations(keep_details_t());
+ }
+ value_t py_strip_annotations_1(value_t& value, const keep_details_t& keep) {
+ return value.strip_annotations(keep);
+ }
+
+ PyObject * py_value_unicode(value_t& value) {
+ return str_to_py_unicode(value.to_string());
+ }
+
+} // unnamed namespace
+
+#define EXC_TRANSLATOR(type) \
+ void exc_translate_ ## type(const type& err) { \
+ PyErr_SetString(PyExc_ArithmeticError, err.what()); \
+ }
+
+EXC_TRANSLATOR(value_error)
+
+void export_value()
+{
+ enum_< value_t::type_t >("ValueType")
+ .value("Void", value_t::VOID)
+ .value("Boolean", value_t::BOOLEAN)
+ .value("DateTime", value_t::DATETIME)
+ .value("Date", value_t::DATE)
+ .value("Integer", value_t::INTEGER)
+ .value("Amount", value_t::AMOUNT)
+ .value("Balance", value_t::BALANCE)
+ .value("String", value_t::STRING)
+ .value("Sequence", value_t::SEQUENCE)
+ .value("Scope", value_t::SCOPE)
+ ;
+
+ class_< value_t > ("Value")
+ .def("initialize", &value_t::initialize)
+ .staticmethod("initialize")
+ .def("shutdown", &value_t::shutdown)
+ .staticmethod("shutdown")
+
+ .def(init<bool>())
+ .def(init<datetime_t>())
+ .def(init<date_t>())
+ .def(init<long>())
+ .def(init<double>())
+ .def(init<amount_t>())
+ .def(init<balance_t>())
+ .def(init<mask_t>())
+ .def(init<std::string>())
+ // jww (2009-11-02): Need to support conversion eof value_t::sequence_t
+ //.def(init<value_t::sequence_t>())
+ .def(init<value_t>())
+
+ .def("is_equal_to", &value_t::is_equal_to)
+ .def("is_less_than", &value_t::is_less_than)
+ .def("is_greater_than", &value_t::is_greater_than)
+
+ .def(self == self)
+ .def(self == long())
+ .def(long() == self)
+ .def(self == other<amount_t>())
+ .def(other<amount_t>() == self)
+ .def(self == other<balance_t>())
+ .def(other<balance_t>() == self)
+
+ .def(self != self)
+ .def(self != long())
+ .def(long() != self)
+ .def(self != other<amount_t>())
+ .def(other<amount_t>() != self)
+ .def(self != other<balance_t>())
+ .def(other<balance_t>() != self)
+
+ .def(! self)
+
+ .def(self < self)
+ .def(self < long())
+ .def(long() < self)
+ .def(self < other<amount_t>())
+ .def(other<amount_t>() < self)
+
+ .def(self <= self)
+ .def(self <= long())
+ .def(long() <= self)
+ .def(self <= other<amount_t>())
+ .def(other<amount_t>() <= self)
+
+ .def(self > self)
+ .def(self > long())
+ .def(long() > self)
+ .def(self > other<amount_t>())
+ .def(other<amount_t>() > self)
+
+ .def(self >= self)
+ .def(self >= long())
+ .def(long() >= self)
+ .def(self >= other<amount_t>())
+ .def(other<amount_t>() >= self)
+
+ .def(self += self)
+ .def(self += long())
+ .def(self += other<amount_t>())
+ .def(self += other<balance_t>())
+
+ .def(self + self)
+ .def(self + long())
+ .def(long() + self)
+ .def(self + other<amount_t>())
+ .def(other<amount_t>() + self)
+ .def(self + other<balance_t>())
+
+ .def(self -= self)
+ .def(self -= long())
+ .def(self -= other<amount_t>())
+ .def(self -= other<balance_t>())
+
+ .def(self - self)
+ .def(self - long())
+ .def(long() - self)
+ .def(self - other<amount_t>())
+ .def(other<amount_t>() - self)
+ .def(self - other<balance_t>())
+
+ .def(self *= self)
+ .def(self *= long())
+ .def(self *= other<amount_t>())
+
+ .def(self * self)
+ .def(self * long())
+ .def(long() * self)
+ .def(self * other<amount_t>())
+ .def(other<amount_t>() * self)
+
+ .def(self /= self)
+ .def(self /= long())
+ .def(self /= other<amount_t>())
+
+ .def(self / self)
+ .def(self / long())
+ .def(long() / self)
+ .def(self / other<amount_t>())
+ .def(other<amount_t>() / self)
+
+ .def("negated", &value_t::negated)
+ .def("in_place_negate", &value_t::in_place_negate)
+ .def("in_place_not", &value_t::in_place_not)
+ .def(- self)
+
+ .def("abs", &value_t::abs)
+ .def("__abs__", &value_t::abs)
+
+ .def("rounded", &value_t::rounded)
+ .def("in_place_round", &value_t::in_place_round)
+ .def("truncated", &value_t::truncated)
+ .def("in_place_truncate", &value_t::in_place_truncate)
+ .def("floored", &value_t::floored)
+ .def("in_place_floor", &value_t::in_place_floor)
+ .def("unrounded", &value_t::unrounded)
+ .def("in_place_unround", &value_t::in_place_unround)
+ .def("reduced", &value_t::reduced)
+ .def("in_place_reduce", &value_t::in_place_reduce)
+ .def("unreduced", &value_t::unreduced)
+ .def("in_place_unreduce", &value_t::in_place_unreduce)
+
+ .def("value", py_value_0)
+ .def("value", py_value_1, args("in_terms_of"))
+ .def("value", py_value_2, args("in_terms_of", "moment"))
+
+ .def("value", &value_t::value, value_overloads())
+ .def("price", &value_t::price)
+ .def("exchange_commodities", &value_t::exchange_commodities,
+ exchange_commodities_overloads())
+
+ .def("__nonzero__", &value_t::is_nonzero)
+ .def("is_nonzero", &value_t::is_nonzero)
+ .def("is_realzero", &value_t::is_realzero)
+ .def("is_zero", &value_t::is_zero)
+ .def("is_null", &value_t::is_null)
+
+ .def("type", &value_t::type)
+ .def("is_type", &value_t::is_type)
+
+ .def("is_boolean", &value_t::is_boolean)
+ .def("set_boolean", &value_t::set_boolean)
+
+ .def("is_datetime", &value_t::is_datetime)
+ .def("set_datetime", &value_t::set_datetime)
+
+ .def("is_date", &value_t::is_date)
+ .def("set_date", &value_t::set_date)
+
+ .def("is_long", &value_t::is_long)
+ .def("set_long", &value_t::set_long)
+
+ .def("is_amount", &value_t::is_amount)
+ .def("is_amount", &value_t::is_amount)
+
+ .def("is_balance", &value_t::is_balance)
+ .def("is_balance", &value_t::is_balance)
+
+ .def("is_string", &value_t::is_string)
+ .def("set_string", py_set_string)
+
+ .def("is_mask", &value_t::is_mask)
+ .def("is_mask", &value_t::is_mask)
+
+ .def("is_sequence", &value_t::is_sequence)
+ .def("set_sequence", &value_t::set_sequence)
+
+ .def("to_boolean", &value_t::to_boolean)
+ .def("to_long", &value_t::to_long)
+ .def("__int__", &value_t::to_long)
+ .def("to_datetime", &value_t::to_datetime)
+ .def("to_date", &value_t::to_date)
+ .def("to_amount", &value_t::to_amount)
+ .def("to_balance", &value_t::to_balance)
+ .def("__str__", &value_t::to_string)
+ .def("__unicode__", py_value_unicode)
+ .def("to_string", &value_t::to_string)
+ .def("to_mask", &value_t::to_mask)
+ .def("to_sequence", &value_t::to_sequence)
+
+ .def("__unicode__", py_dump_relaxed)
+ .def("__repr__", py_dump)
+
+ .def("casted", &value_t::casted)
+ .def("in_place_cast", &value_t::in_place_cast)
+ .def("simplified", &value_t::simplified)
+ .def("in_place_simplify", &value_t::in_place_simplify)
+
+ .def("number", &value_t::number)
+
+ .def("annotate", &value_t::annotate)
+ .def("has_annotation", &value_t::has_annotation)
+ .add_property("annotation",
+ make_function(py_value_annotation,
+ return_internal_reference<>()))
+ .def("strip_annotations", py_strip_annotations_0)
+ .def("strip_annotations", py_strip_annotations_1)
+
+#if 0
+ .def("__getitem__", &value_t::operator[])
+#endif
+ .def("push_back", &value_t::push_back)
+ .def("pop_back", &value_t::pop_back)
+ .def("size", &value_t::size)
+
+ .def("label", &value_t::label)
+
+ .def("valid", &value_t::valid)
+
+ .def("basetype", py_base_type)
+ ;
+
+ class_< value_t::sequence_t > ("ValueSequence")
+ .def(vector_indexing_suite< value_t::sequence_t >());
+ ;
+
+ scope().attr("NULL_VALUE") = NULL_VALUE;
+ scope().attr("string_value") = &string_value;
+ scope().attr("mask_value") = &mask_value;
+ scope().attr("value_context") = &value_context;
+
+ register_optional_to_python<value_t>();
+
+ implicitly_convertible<long, value_t>();
+ implicitly_convertible<string, value_t>();
+ implicitly_convertible<amount_t, value_t>();
+ implicitly_convertible<balance_t, value_t>();
+ implicitly_convertible<mask_t, value_t>();
+ implicitly_convertible<date_t, value_t>();
+ implicitly_convertible<datetime_t, value_t>();
+
+#define EXC_TRANSLATE(type) \
+ register_exception_translator<type>(&exc_translate_ ## type);
+
+ EXC_TRANSLATE(value_error);
+}
+
+} // namespace ledger
diff --git a/src/py_xact.cc b/src/py_xact.cc
new file mode 100644
index 00000000..59c599d9
--- /dev/null
+++ b/src/py_xact.cc
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "pyinterp.h"
+#include "pyutils.h"
+#include "xact.h"
+#include "post.h"
+
+namespace ledger {
+
+using namespace boost::python;
+
+namespace {
+
+ long posts_len(xact_base_t& xact)
+ {
+ return xact.posts.size();
+ }
+
+ post_t& posts_getitem(xact_base_t& xact, long i)
+ {
+ static long last_index = 0;
+ static xact_base_t * last_xact = NULL;
+ static posts_list::iterator elem;
+
+ long len = xact.posts.size();
+
+ if (labs(i) >= len) {
+ PyErr_SetString(PyExc_IndexError, _("Index out of range"));
+ throw_error_already_set();
+ }
+
+ if (&xact == last_xact && i == last_index + 1) {
+ last_index = i;
+ return **++elem;
+ }
+
+ long x = i < 0 ? len + i : i;
+ elem = xact.posts.begin();
+ while (--x >= 0)
+ elem++;
+
+ last_xact = &xact;
+ last_index = i;
+
+ return **elem;
+ }
+
+} // unnamed namespace
+
+using namespace boost::python;
+
+void export_xact()
+{
+ class_< xact_base_t, bases<item_t> > ("TransactionBase")
+ .add_property("journal",
+ make_getter(&xact_base_t::journal,
+ return_internal_reference<>()),
+ make_setter(&xact_base_t::journal,
+ with_custodian_and_ward<1, 2>()))
+
+ .def("__len__", posts_len)
+ .def("__getitem__", posts_getitem,
+ return_internal_reference<>())
+
+ .def("add_post", &xact_base_t::add_post, with_custodian_and_ward<1, 2>())
+ .def("remove_post", &xact_base_t::add_post)
+
+ .def("finalize", &xact_base_t::finalize)
+
+ .def("__iter__", range<return_internal_reference<> >
+ (&xact_t::posts_begin, &xact_t::posts_end))
+ .def("posts", range<return_internal_reference<> >
+ (&xact_t::posts_begin, &xact_t::posts_end))
+
+ .def("valid", &xact_base_t::valid)
+ ;
+
+ class_< xact_t, bases<xact_base_t> > ("Transaction")
+ .add_property("code",
+ make_getter(&xact_t::code),
+ make_setter(&xact_t::code))
+ .add_property("payee",
+ make_getter(&xact_t::payee),
+ make_setter(&xact_t::payee))
+
+ .def("add_post", &xact_t::add_post, with_custodian_and_ward<1, 2>())
+
+ .def("magnitude", &xact_t::magnitude)
+ .def("idstring", &xact_t::idstring)
+ .def("id", &xact_t::id)
+
+ .def("lookup", &xact_t::lookup)
+
+ .def("has_xdata", &xact_t::has_xdata)
+ .def("clear_xdata", &xact_t::clear_xdata)
+
+ .def("valid", &xact_t::valid)
+ ;
+
+ class_< auto_xact_t, bases<xact_base_t> > ("AutomatedTransaction")
+ .def(init<predicate_t>())
+
+ .add_property("predicate",
+ make_getter(&auto_xact_t::predicate),
+ make_setter(&auto_xact_t::predicate))
+
+ .def("extend_xact", &auto_xact_t::extend_xact)
+ ;
+
+ class_< period_xact_t, bases<xact_base_t> > ("PeriodicTransaction")
+ .def(init<string>())
+
+ .add_property("period",
+ make_getter(&period_xact_t::period),
+ make_setter(&period_xact_t::period))
+ .add_property("period_string",
+ make_getter(&period_xact_t::period_string),
+ make_setter(&period_xact_t::period_string))
+ ;
+}
+
+} // namespace ledger
diff --git a/src/pyfstream.h b/src/pyfstream.h
new file mode 100644
index 00000000..3da37523
--- /dev/null
+++ b/src/pyfstream.h
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _PYFSTREAM_H
+#define _PYFSTREAM_H
+
+// pyofstream
+// - a stream that writes on a Python file object
+
+class pyoutbuf : public boost::noncopyable, public std::streambuf
+{
+ pyoutbuf();
+
+protected:
+ PyFileObject * fo; // Python file object
+
+public:
+ // constructor
+ pyoutbuf(PyFileObject * _fo) : fo(_fo) {
+ TRACE_CTOR(pyoutbuf, "PyFileObject *");
+ }
+ ~pyoutbuf() throw() {
+ TRACE_DTOR(pyoutbuf);
+ }
+
+protected:
+ // write one character
+ virtual int_type overflow (int_type c) {
+ if (c != EOF) {
+ char z[2];
+ z[0] = static_cast<char>(c);
+ z[1] = '\0';
+ if (PyFile_WriteString(z, reinterpret_cast<PyObject *>(fo)) < 0) {
+ return EOF;
+ }
+ }
+ return c;
+ }
+
+ // write multiple characters
+ virtual std::streamsize xsputn (const char* s, std::streamsize num) {
+ char * buf = new char[num + 1];
+ std::strncpy(buf, s, num);
+ buf[num] = '\0';
+ if (PyFile_WriteString(buf, reinterpret_cast<PyObject *>(fo)) < 0)
+ num = 0;
+ boost::checked_array_delete(buf);
+ return num;
+ }
+};
+
+class pyofstream : public boost::noncopyable, public std::ostream
+{
+ pyofstream();
+
+protected:
+ pyoutbuf buf;
+
+public:
+ pyofstream (PyFileObject * fo) : std::ostream(0), buf(fo) {
+ TRACE_CTOR(pyofstream, "PyFileObject *");
+ rdbuf(&buf);
+ }
+ ~pyofstream() throw() {
+ TRACE_DTOR(pyofstream);
+ }
+};
+
+// pyifstream
+// - a stream that reads on a file descriptor
+
+class pyinbuf : public boost::noncopyable, public std::streambuf
+{
+ pyinbuf();
+
+protected:
+ PyFileObject * fo; // Python file object
+
+protected:
+ /* data buffer:
+ * - at most, pbSize characters in putback area plus
+ * - at most, bufSize characters in ordinary read buffer
+ */
+ static const size_t pbSize = 4; // size of putback area
+ static const size_t 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()
+ */
+ pyinbuf (PyFileObject * _fo) : fo(_fo) {
+ TRACE_CTOR(pyinbuf, "PyFileObject *");
+
+ setg (buffer+pbSize, // beginning of putback area
+ buffer+pbSize, // read position
+ buffer+pbSize); // end position
+ }
+ ~pyinbuf() throw() {
+ TRACE_DTOR(pyinbuf);
+ }
+
+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
+ */
+ size_t 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
+ PyObject *line = PyFile_GetLine(reinterpret_cast<PyObject *>(fo), bufSize);
+ if (! line || ! PyString_Check(line)) {
+ // ERROR or EOF
+ return EOF;
+ }
+
+ Py_ssize_t num = PyString_Size(line);
+ if (num == 0)
+ return EOF;
+
+ memmove (buffer+pbSize, PyString_AsString(line), num);
+
+ // 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 pyifstream : public boost::noncopyable, public std::istream
+{
+ pyifstream();
+
+protected:
+ pyinbuf buf;
+
+public:
+ pyifstream (PyFileObject * fo) : std::istream(0), buf(fo) {
+ TRACE_CTOR(pyifstream, "PyFileObject *");
+ rdbuf(&buf);
+ }
+ ~pyifstream() throw() {
+ TRACE_DTOR(pyifstream);
+ }
+};
+
+#endif // _PYFSTREAM_H
diff --git a/src/pyinterp.cc b/src/pyinterp.cc
new file mode 100644
index 00000000..0701176f
--- /dev/null
+++ b/src/pyinterp.cc
@@ -0,0 +1,505 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "pyinterp.h"
+#include "account.h"
+#include "xact.h"
+#include "post.h"
+
+namespace ledger {
+
+using namespace python;
+
+shared_ptr<python_interpreter_t> python_session;
+
+char * argv0;
+
+void export_account();
+void export_amount();
+void export_balance();
+void export_commodity();
+void export_expr();
+void export_format();
+void export_item();
+void export_journal();
+void export_post();
+void export_times();
+void export_utils();
+void export_value();
+void export_xact();
+
+void initialize_for_python()
+{
+ export_times();
+ export_utils();
+ export_commodity();
+ export_amount();
+ export_value();
+ export_account();
+ export_balance();
+ export_expr();
+ export_format();
+ export_item();
+ export_post();
+ export_xact();
+ export_journal();
+}
+
+struct python_run
+{
+ object result;
+
+ python_run(python_interpreter_t * intepreter,
+ const string& str, int input_mode)
+ : result(handle<>(borrowed(PyRun_String(str.c_str(), input_mode,
+ intepreter->main_nspace.ptr(),
+ intepreter->main_nspace.ptr())))) {}
+ operator object() {
+ return result;
+ }
+};
+
+void python_interpreter_t::initialize()
+{
+ TRACE_START(python_init, 1, "Initialized Python");
+
+ try {
+ DEBUG("python.interp", "Initializing Python");
+
+ Py_Initialize();
+ assert(Py_IsInitialized());
+
+ hack_system_paths();
+
+ object main_module = python::import("__main__");
+ if (! main_module)
+ throw_(std::runtime_error,
+ _("Python failed to initialize (couldn't find __main__)"));
+
+ main_nspace = extract<dict>(main_module.attr("__dict__"));
+ if (! main_nspace)
+ throw_(std::runtime_error,
+ _("Python failed to initialize (couldn't find __dict__)"));
+
+ python::detail::init_module("ledger", &initialize_for_python);
+
+ is_initialized = true;
+ }
+ catch (const error_already_set&) {
+ PyErr_Print();
+ throw_(std::runtime_error, _("Python failed to initialize"));
+ }
+
+ TRACE_FINISH(python_init, 1);
+}
+
+void python_interpreter_t::hack_system_paths()
+{
+ // Hack ledger.__path__ so it points to a real location
+ python::object sys_module = python::import("sys");
+ python::object sys_dict = sys_module.attr("__dict__");
+
+ python::list paths(sys_dict["path"]);
+
+#if defined(DEBUG_ON)
+ bool path_initialized = false;
+#endif
+ int n = python::extract<int>(paths.attr("__len__")());
+ for (int i = 0; i < n; i++) {
+ python::extract<std::string> str(paths[i]);
+ path pathname(str);
+ DEBUG("python.interp", "sys.path = " << pathname);
+
+ if (exists(pathname / "ledger" / "__init__.py")) {
+ if (python::object module_ledger = python::import("ledger")) {
+ DEBUG("python.interp",
+ "Setting ledger.__path__ = " << (pathname / "ledger"));
+
+ python::object ledger_dict = module_ledger.attr("__dict__");
+ python::list temp_list;
+ temp_list.append((pathname / "ledger").string());
+
+ ledger_dict["__path__"] = temp_list;
+ } else {
+ throw_(std::runtime_error,
+ _("Python failed to initialize (couldn't find ledger)"));
+ }
+#if defined(DEBUG_ON)
+ path_initialized = true;
+#endif
+ break;
+ }
+ }
+#if defined(DEBUG_ON)
+ if (! path_initialized)
+ DEBUG("python.init",
+ "Ledger failed to find 'ledger/__init__.py' on the PYTHONPATH");
+#endif
+}
+
+object python_interpreter_t::import_into_main(const string& str)
+{
+ if (! is_initialized)
+ initialize();
+
+ try {
+ object mod = python::import(str.c_str());
+ if (! mod)
+ throw_(std::runtime_error,
+ _("Failed to import Python module %1") << str);
+
+ // Import all top-level entries directly into the main namespace
+ main_nspace.update(mod.attr("__dict__"));
+
+ return mod;
+ }
+ catch (const error_already_set&) {
+ PyErr_Print();
+ }
+ return object();
+}
+
+object python_interpreter_t::import_option(const string& str)
+{
+ path file(str);
+
+ python::object sys_module = python::import("sys");
+ python::object sys_dict = sys_module.attr("__dict__");
+
+ python::list paths(sys_dict["path"]);
+
+#if BOOST_VERSION >= 103700
+ paths.insert(0, file.parent_path().string());
+ sys_dict["path"] = paths;
+
+ string name = file.filename();
+ if (contains(name, ".py"))
+ name = file.stem();
+#else // BOOST_VERSION >= 103700
+ paths.insert(0, file.branch_path().string());
+ sys_dict["path"] = paths;
+
+ string name = file.leaf();
+#endif // BOOST_VERSION >= 103700
+
+ return python::import(python::str(name.c_str()));
+}
+
+object python_interpreter_t::eval(std::istream& in, py_eval_mode_t mode)
+{
+ bool first = true;
+ string buffer;
+ buffer.reserve(4096);
+
+ while (! in.eof()) {
+ char buf[256];
+ in.getline(buf, 255);
+ if (buf[0] == '!')
+ break;
+ if (first)
+ first = false;
+ else
+ buffer += "\n";
+ buffer += buf;
+ }
+
+ if (! is_initialized)
+ initialize();
+
+ try {
+ int input_mode = -1;
+ switch (mode) {
+ case PY_EVAL_EXPR: input_mode = Py_eval_input; break;
+ case PY_EVAL_STMT: input_mode = Py_single_input; break;
+ case PY_EVAL_MULTI: input_mode = Py_file_input; break;
+ }
+
+ return python_run(this, buffer, input_mode);
+ }
+ catch (const error_already_set&) {
+ PyErr_Print();
+ throw_(std::runtime_error, _("Failed to evaluate Python code"));
+ }
+ return object();
+}
+
+object python_interpreter_t::eval(const string& str, py_eval_mode_t mode)
+{
+ if (! is_initialized)
+ initialize();
+
+ try {
+ int input_mode = -1;
+ switch (mode) {
+ case PY_EVAL_EXPR: input_mode = Py_eval_input; break;
+ case PY_EVAL_STMT: input_mode = Py_single_input; break;
+ case PY_EVAL_MULTI: input_mode = Py_file_input; break;
+ }
+
+ return python_run(this, str, input_mode);
+ }
+ catch (const error_already_set&) {
+ PyErr_Print();
+ throw_(std::runtime_error, _("Failed to evaluate Python code"));
+ }
+ return object();
+}
+
+value_t python_interpreter_t::python_command(call_scope_t& args)
+{
+ if (! is_initialized)
+ initialize();
+
+ char ** argv(new char *[args.size() + 1]);
+
+ argv[0] = new char[std::strlen(argv0) + 1];
+ std::strcpy(argv[0], argv0);
+
+ for (std::size_t i = 0; i < args.size(); i++) {
+ string arg = args[i].as_string();
+ argv[i + 1] = new char[arg.length() + 1];
+ std::strcpy(argv[i + 1], arg.c_str());
+ }
+
+ int status = 1;
+
+ try {
+ status = Py_Main(static_cast<int>(args.size()) + 1, argv);
+ }
+ catch (const error_already_set&) {
+ PyErr_Print();
+ throw_(std::runtime_error, _("Failed to execute Python module"));
+ }
+ catch (...) {
+ for (std::size_t i = 0; i < args.size() + 1; i++)
+ delete[] argv[i];
+ delete[] argv;
+ throw;
+ }
+
+ for (std::size_t i = 0; i < args.size() + 1; i++)
+ delete[] argv[i];
+ delete[] argv;
+
+ if (status != 0)
+ throw status;
+
+ return NULL_VALUE;
+}
+
+value_t python_interpreter_t::server_command(call_scope_t& args)
+{
+ if (! is_initialized)
+ initialize();
+
+ python::object server_module;
+
+ try {
+ server_module = python::import("ledger.server");
+ if (! server_module)
+ throw_(std::runtime_error,
+ _("Could not import ledger.server; please check your PYTHONPATH"));
+ }
+ catch (const error_already_set&) {
+ PyErr_Print();
+ throw_(std::runtime_error,
+ _("Could not import ledger.server; please check your PYTHONPATH"));
+ }
+
+ if (python::object main_function = server_module.attr("main")) {
+ functor_t func(main_function, "main");
+ try {
+ func(args);
+ }
+ catch (const error_already_set&) {
+ PyErr_Print();
+ throw_(std::runtime_error,
+ _("Error while invoking ledger.server's main() function"));
+ }
+ return true;
+ } else {
+ throw_(std::runtime_error,
+ _("The ledger.server module is missing its main() function!"));
+ }
+
+ return false;
+}
+
+option_t<python_interpreter_t> *
+python_interpreter_t::lookup_option(const char * p)
+{
+ switch (*p) {
+ case 'i':
+ OPT(import_);
+ break;
+ }
+ return NULL;
+}
+
+expr_t::ptr_op_t python_interpreter_t::lookup(const symbol_t::kind_t kind,
+ const string& name)
+{
+ // Give our superclass first dibs on symbol definitions
+ if (expr_t::ptr_op_t op = session_t::lookup(kind, name))
+ return op;
+
+ switch (kind) {
+ case symbol_t::FUNCTION:
+ if (option_t<python_interpreter_t> * handler = lookup_option(name.c_str()))
+ return MAKE_OPT_FUNCTOR(python_interpreter_t, handler);
+
+ if (is_initialized && main_nspace.has_key(name.c_str())) {
+ DEBUG("python.interp", "Python lookup: " << name);
+
+ if (python::object obj = main_nspace.get(name.c_str()))
+ return WRAP_FUNCTOR(functor_t(obj, name));
+ }
+ break;
+
+ case symbol_t::OPTION:
+ if (option_t<python_interpreter_t> * handler = lookup_option(name.c_str()))
+ return MAKE_OPT_HANDLER(python_interpreter_t, handler);
+ break;
+
+ case symbol_t::PRECOMMAND: {
+ const char * p = name.c_str();
+ switch (*p) {
+ case 'p':
+ if (is_eq(p, "python"))
+ return MAKE_FUNCTOR(python_interpreter_t::python_command);
+ break;
+
+ case 's':
+ if (is_eq(p, "server"))
+ return MAKE_FUNCTOR(python_interpreter_t::server_command);
+ break;
+ }
+ }
+
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+namespace {
+ void append_value(list& lst, const value_t& value)
+ {
+ if (value.is_scope()) {
+ const scope_t * scope = value.as_scope();
+ if (const post_t * post = dynamic_cast<const post_t *>(scope))
+ lst.append(ptr(post));
+ else if (const xact_t * xact = dynamic_cast<const xact_t *>(scope))
+ lst.append(ptr(xact));
+ else if (const account_t * account =
+ dynamic_cast<const account_t *>(scope))
+ lst.append(ptr(account));
+ else if (const period_xact_t * period_xact =
+ dynamic_cast<const period_xact_t *>(scope))
+ lst.append(ptr(period_xact));
+ else if (const auto_xact_t * auto_xact =
+ dynamic_cast<const auto_xact_t *>(scope))
+ lst.append(ptr(auto_xact));
+ else
+ throw_(std::logic_error,
+ _("Cannot downcast scoped object to specific type"));
+ } else {
+ lst.append(value);
+ }
+ }
+}
+
+value_t python_interpreter_t::functor_t::operator()(call_scope_t& args)
+{
+ try {
+ std::signal(SIGINT, SIG_DFL);
+
+ if (! PyCallable_Check(func.ptr())) {
+ extract<value_t> val(func);
+ std::signal(SIGINT, sigint_handler);
+ if (val.check())
+ return val();
+ return NULL_VALUE;
+ }
+ else if (args.size() > 0) {
+ list arglist;
+ // jww (2009-11-05): What about a single argument which is a sequence,
+ // rather than a sequence of arguments?
+ if (args.value().is_sequence())
+ foreach (const value_t& value, args.value().as_sequence())
+ append_value(arglist, value);
+ else
+ append_value(arglist, args.value());
+
+ if (PyObject * val =
+ PyObject_CallObject(func.ptr(), python::tuple(arglist).ptr())) {
+ extract<value_t> xval(val);
+ value_t result;
+ if (xval.check()) {
+ result = xval();
+ Py_DECREF(val);
+ } else {
+ Py_DECREF(val);
+ throw_(calc_error,
+ _("Could not evaluate Python variable '%1'") << name);
+ }
+ std::signal(SIGINT, sigint_handler);
+ return result;
+ }
+ else if (PyErr_Occurred()) {
+ PyErr_Print();
+ throw_(calc_error, _("Failed call to Python function '%1'") << name);
+ } else {
+ assert(false);
+ }
+ }
+ else {
+ std::signal(SIGINT, sigint_handler);
+ return call<value_t>(func.ptr());
+ }
+ }
+ catch (const error_already_set&) {
+ std::signal(SIGINT, sigint_handler);
+ PyErr_Print();
+ throw_(calc_error, _("Failed call to Python function '%1'") << name);
+ }
+ catch (...) {
+ std::signal(SIGINT, sigint_handler);
+ }
+ std::signal(SIGINT, sigint_handler);
+
+ return NULL_VALUE;
+}
+
+} // namespace ledger
diff --git a/src/pyinterp.h b/src/pyinterp.h
new file mode 100644
index 00000000..f2d7b760
--- /dev/null
+++ b/src/pyinterp.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _PYINTERP_H
+#define _PYINTERP_H
+
+#include "session.h"
+
+#if defined(HAVE_BOOST_PYTHON)
+
+namespace ledger {
+
+class python_interpreter_t : public session_t
+{
+public:
+ python::dict main_nspace;
+ bool is_initialized;
+
+ python_interpreter_t()
+ : session_t(), main_nspace(), is_initialized(false) {
+ TRACE_CTOR(python_interpreter_t, "");
+ }
+
+ virtual ~python_interpreter_t() {
+ TRACE_DTOR(python_interpreter_t);
+
+ if (is_initialized)
+ Py_Finalize();
+ }
+
+ void initialize();
+ void hack_system_paths();
+
+ python::object import_into_main(const string& name);
+ python::object import_option(const string& name);
+
+ enum py_eval_mode_t {
+ PY_EVAL_EXPR,
+ PY_EVAL_STMT,
+ PY_EVAL_MULTI
+ };
+
+ python::object eval(std::istream& in,
+ py_eval_mode_t mode = PY_EVAL_EXPR);
+ python::object eval(const string& str,
+ py_eval_mode_t mode = PY_EVAL_EXPR);
+ python::object eval(const char * c_str,
+ py_eval_mode_t mode = PY_EVAL_EXPR) {
+ string str(c_str);
+ return eval(str, mode);
+ }
+
+ value_t python_command(call_scope_t& scope);
+ value_t server_command(call_scope_t& args);
+
+ class functor_t {
+ functor_t();
+
+ protected:
+ python::object func;
+
+ public:
+ string name;
+
+ functor_t(python::object _func, const string& _name)
+ : func(_func), name(_name) {
+ TRACE_CTOR(functor_t, "python::object, const string&");
+ }
+ functor_t(const functor_t& other)
+ : func(other.func), name(other.name) {
+ TRACE_CTOR(functor_t, "copy");
+ }
+ virtual ~functor_t() throw() {
+ TRACE_DTOR(functor_t);
+ }
+ virtual value_t operator()(call_scope_t& args);
+ };
+
+ option_t<python_interpreter_t> * lookup_option(const char * p);
+
+ virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind,
+ const string& name);
+
+ OPTION_(python_interpreter_t, import_, DO_(scope) {
+ interactive_t args(scope, "ss");
+ parent->import_option(args.get<string>(1));
+ });
+};
+
+extern shared_ptr<python_interpreter_t> python_session;
+
+} // namespace ledger
+
+#endif // HAVE_BOOST_PYTHON
+
+#endif // _PYINTERP_H
diff --git a/src/pyledger.cc b/src/pyledger.cc
new file mode 100644
index 00000000..cf7e1527
--- /dev/null
+++ b/src/pyledger.cc
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "pyinterp.h"
+
+using namespace boost::python;
+
+namespace ledger {
+ extern void initialize_for_python();
+}
+
+BOOST_PYTHON_MODULE(ledger)
+{
+ using namespace ledger;
+
+ if (! python_session.get())
+ python_session.reset(new python_interpreter_t);
+
+ set_session_context(python_session.get());
+
+ initialize_for_python();
+}
diff --git a/src/pyutils.h b/src/pyutils.h
new file mode 100644
index 00000000..54d6fa28
--- /dev/null
+++ b/src/pyutils.h
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _PY_UTILS_H
+#define _PY_UTILS_H
+
+template <typename T, typename TfromPy>
+struct object_from_python
+{
+ object_from_python() {
+ boost::python::converter::registry::insert
+ (&TfromPy::convertible, &TfromPy::construct,
+ boost::python::type_id<T>());
+ }
+};
+
+template <typename T, typename TtoPy, typename TfromPy>
+struct register_python_conversion
+{
+ register_python_conversion() {
+ boost::python::to_python_converter<T, TtoPy>();
+ object_from_python<T, TfromPy>();
+ }
+};
+
+template <typename T>
+struct register_optional_to_python : public boost::noncopyable
+{
+ struct optional_to_python
+ {
+ static PyObject * convert(const boost::optional<T>& value)
+ {
+ return boost::python::incref
+ (value ? boost::python::to_python_value<T>()(*value) :
+ boost::python::detail::none());
+ }
+ };
+
+ struct optional_from_python
+ {
+ static void * convertible(PyObject * source)
+ {
+ using namespace boost::python::converter;
+
+ if (source == Py_None)
+ return source;
+
+ const registration& converters(registered<T>::converters);
+
+ if (implicit_rvalue_convertible_from_python(source, converters)) {
+ rvalue_from_python_stage1_data data =
+ rvalue_from_python_stage1(source, converters);
+ return rvalue_from_python_stage2(source, data, converters);
+ }
+ return NULL;
+ }
+
+ static void construct(PyObject * source,
+ boost::python::converter::rvalue_from_python_stage1_data * data)
+ {
+ using namespace boost::python::converter;
+
+ void * const storage =
+ reinterpret_cast<rvalue_from_python_storage<T> *>(data)->storage.bytes;
+
+ if (data->convertible == source) // == None
+ new (storage) boost::optional<T>(); // A Boost uninitialized value
+ else
+ new (storage) boost::optional<T>(*reinterpret_cast<T *>(data->convertible));
+
+ data->convertible = storage;
+ }
+ };
+
+ explicit register_optional_to_python() {
+ register_python_conversion<boost::optional<T>,
+ optional_to_python, optional_from_python>();
+ }
+};
+
+template <typename T1, typename T2>
+struct PairToTupleConverter
+{
+ static PyObject * convert(const std::pair<T1, T2>& pair) {
+ return boost::python::incref
+ (boost::python::make_tuple(pair.first, pair.second).ptr());
+ }
+};
+
+template <typename MapType>
+struct map_value_type_converter
+{
+ map_value_type_converter() {
+ boost::python::to_python_converter
+ <typename MapType::value_type,
+ PairToTupleConverter<const typename MapType::key_type,
+ typename MapType::mapped_type> >();
+ }
+};
+
+template <typename T>
+PyObject * str_to_py_unicode(const T& str)
+{
+ using namespace boost::python;
+ PyObject * pstr = PyString_FromString(str.c_str());
+ PyObject * uni = PyUnicode_FromEncodedObject(pstr, "UTF-8", NULL);
+ return object(handle<>(borrowed(uni))).ptr();
+}
+
+namespace boost { namespace python {
+
+// Use expr to create the PyObject corresponding to x
+# define BOOST_PYTHON_RETURN_TO_PYTHON_BY_VALUE(T, expr, pytype)\
+ template <> struct to_python_value<T&> \
+ : detail::builtin_to_python \
+ { \
+ inline PyObject* operator()(T const& x) const \
+ { \
+ return (expr); \
+ } \
+ inline PyTypeObject const* get_pytype() const \
+ { \
+ return (pytype); \
+ } \
+ }; \
+ template <> struct to_python_value<T const&> \
+ : detail::builtin_to_python \
+ { \
+ inline PyObject* operator()(T const& x) const \
+ { \
+ return (expr); \
+ } \
+ inline PyTypeObject const* get_pytype() const \
+ { \
+ return (pytype); \
+ } \
+ };
+
+# define BOOST_PYTHON_ARG_TO_PYTHON_BY_VALUE(T, expr) \
+ namespace converter \
+ { \
+ template <> struct arg_to_python< T > \
+ : handle<> \
+ { \
+ arg_to_python(T const& x) \
+ : python::handle<>(expr) {} \
+ }; \
+ }
+
+// Specialize argument and return value converters for T using expr
+# define BOOST_PYTHON_TO_PYTHON_BY_VALUE(T, expr, pytype) \
+ BOOST_PYTHON_RETURN_TO_PYTHON_BY_VALUE(T,expr, pytype) \
+ BOOST_PYTHON_ARG_TO_PYTHON_BY_VALUE(T,expr)
+
+BOOST_PYTHON_TO_PYTHON_BY_VALUE(ledger::string, ::PyUnicode_FromEncodedObject(::PyString_FromString(x.c_str()), "UTF-8", NULL), &PyUnicode_Type)
+
+} } // namespace boost::python
+
+//boost::python::register_ptr_to_python< boost::shared_ptr<Base> >();
+
+#endif // _PY_UTILS_H
diff --git a/src/query.cc b/src/query.cc
new file mode 100644
index 00000000..cfa321b0
--- /dev/null
+++ b/src/query.cc
@@ -0,0 +1,455 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "query.h"
+#include "op.h"
+
+namespace ledger {
+
+query_t::lexer_t::token_t query_t::lexer_t::next_token()
+{
+ if (token_cache.kind != token_t::UNKNOWN) {
+ token_t tok = token_cache;
+ token_cache = token_t();
+ return tok;
+ }
+
+ if (arg_i == arg_end) {
+ if (begin == end || ++begin == end) {
+ return token_t(token_t::END_REACHED);
+ } else {
+ arg_i = (*begin).as_string().begin();
+ arg_end = (*begin).as_string().end();
+ }
+ }
+
+ if (consume_next_arg) {
+ consume_next_arg = false;
+ arg_i = arg_end;
+ return token_t(token_t::TERM, (*begin).as_string());
+ }
+
+ resume:
+ bool consume_next = false;
+ switch (*arg_i) {
+ case ' ':
+ case '\t':
+ case '\r':
+ case '\n':
+ if (++arg_i == arg_end)
+ return next_token();
+ goto resume;
+
+ case '/': {
+ string pat;
+ bool found_end_slash = false;
+ for (++arg_i; arg_i != arg_end; ++arg_i) {
+ if (*arg_i == '\\') {
+ if (++arg_i == arg_end)
+ throw_(parse_error, _("Unexpected '\\' at end of pattern"));
+ }
+ else if (*arg_i == '/') {
+ ++arg_i;
+ found_end_slash = true;
+ break;
+ }
+ pat.push_back(*arg_i);
+ }
+ if (! found_end_slash)
+ throw_(parse_error, _("Expected '/' at end of pattern"));
+ if (pat.empty())
+ throw_(parse_error, _("Match pattern is empty"));
+
+ return token_t(token_t::TERM, pat);
+ }
+
+ case '(': ++arg_i; return token_t(token_t::LPAREN);
+ case ')': ++arg_i; return token_t(token_t::RPAREN);
+ case '&': ++arg_i; return token_t(token_t::TOK_AND);
+ case '|': ++arg_i; return token_t(token_t::TOK_OR);
+ case '!': ++arg_i; return token_t(token_t::TOK_NOT);
+ case '@': ++arg_i; return token_t(token_t::TOK_PAYEE);
+ case '#': ++arg_i; return token_t(token_t::TOK_CODE);
+ case '%': ++arg_i; return token_t(token_t::TOK_META);
+ case '=': ++arg_i; return token_t(token_t::TOK_EQ);
+
+ case '\\':
+ consume_next = true;
+ ++arg_i;
+ // fall through...
+ default: {
+ string ident;
+ string::const_iterator beg = arg_i;
+ for (; arg_i != arg_end; ++arg_i) {
+ switch (*arg_i) {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r':
+ if (! consume_whitespace)
+ goto test_ident;
+ else
+ ident.push_back(*arg_i);
+ break;
+ case '(':
+ case ')':
+ case '&':
+ case '|':
+ case '!':
+ case '@':
+ case '#':
+ case '%':
+ case '=':
+ if (! consume_next)
+ goto test_ident;
+ // fall through...
+ default:
+ ident.push_back(*arg_i);
+ break;
+ }
+ }
+ consume_whitespace = false;
+
+test_ident:
+ if (ident == "and")
+ return token_t(token_t::TOK_AND);
+ else if (ident == "or")
+ return token_t(token_t::TOK_OR);
+ else if (ident == "not")
+ return token_t(token_t::TOK_NOT);
+ else if (ident == "code")
+ return token_t(token_t::TOK_CODE);
+ else if (ident == "desc")
+ return token_t(token_t::TOK_PAYEE);
+ else if (ident == "payee")
+ return token_t(token_t::TOK_PAYEE);
+ else if (ident == "note")
+ return token_t(token_t::TOK_NOTE);
+ else if (ident == "tag")
+ return token_t(token_t::TOK_META);
+ else if (ident == "meta")
+ return token_t(token_t::TOK_META);
+ else if (ident == "data")
+ return token_t(token_t::TOK_META);
+ else if (ident == "show") {
+ // The "show" keyword is special, and separates a limiting predicate
+ // from a display predicate.
+ DEBUG("pred.show", "string = " << (*begin).as_string());
+ return token_t(token_t::END_REACHED);
+ }
+#if 0
+ // jww (2009-11-06): This is disabled for the time being.
+ else if (ident == "date") {
+ // The date keyword takes the whole of the next string as its argument.
+ consume_whitespace = true;
+ return token_t(token_t::TOK_DATE);
+ }
+#endif
+ else if (ident == "expr") {
+ // The expr keyword takes the whole of the next string as its argument.
+ consume_next_arg = true;
+ return token_t(token_t::TOK_EXPR);
+ }
+ else
+ return token_t(token_t::TERM, ident);
+ break;
+ }
+ }
+
+ return token_t(token_t::UNKNOWN);
+}
+
+void query_t::lexer_t::token_t::unexpected()
+{
+ kind_t prev_kind = kind;
+
+ kind = UNKNOWN;
+
+ switch (prev_kind) {
+ case END_REACHED:
+ throw_(parse_error, _("Unexpected end of expression"));
+ case TERM:
+ throw_(parse_error, _("Unexpected string '%1'") << *value);
+ default:
+ throw_(parse_error, _("Unexpected token '%1'") << symbol());
+ }
+}
+
+void query_t::lexer_t::token_t::expected(char wanted, char c)
+{
+ kind = UNKNOWN;
+
+ if (c == '\0' || c == -1) {
+ if (wanted == '\0' || wanted == -1)
+ throw_(parse_error, _("Unexpected end"));
+ else
+ throw_(parse_error, _("Missing '%1'") << wanted);
+ } else {
+ if (wanted == '\0' || wanted == -1)
+ throw_(parse_error, _("Invalid char '%1'") << c);
+ else
+ throw_(parse_error, _("Invalid char '%1' (wanted '%2')") << c << wanted);
+ }
+}
+
+expr_t::ptr_op_t
+query_t::parser_t::parse_query_term(query_t::lexer_t::token_t::kind_t tok_context)
+{
+ expr_t::ptr_op_t node;
+
+ lexer_t::token_t tok = lexer.next_token();
+ switch (tok.kind) {
+ case lexer_t::token_t::END_REACHED:
+ break;
+
+ case lexer_t::token_t::TOK_DATE:
+ case lexer_t::token_t::TOK_CODE:
+ case lexer_t::token_t::TOK_PAYEE:
+ case lexer_t::token_t::TOK_NOTE:
+ case lexer_t::token_t::TOK_ACCOUNT:
+ case lexer_t::token_t::TOK_META:
+ case lexer_t::token_t::TOK_EXPR:
+ node = parse_query_term(tok.kind);
+ if (! node)
+ throw_(parse_error,
+ _("%1 operator not followed by argument") << tok.symbol());
+ break;
+
+ case lexer_t::token_t::TERM:
+ assert(tok.value);
+ switch (tok_context) {
+ case lexer_t::token_t::TOK_DATE: {
+ expr_t::ptr_op_t ident = new expr_t::op_t(expr_t::op_t::IDENT);
+ ident->set_ident("date");
+
+ date_interval_t interval(*tok.value);
+
+ if (interval.start) {
+ node = new expr_t::op_t(expr_t::op_t::O_GTE);
+ node->set_left(ident);
+
+ expr_t::ptr_op_t arg1 = new expr_t::op_t(expr_t::op_t::VALUE);
+ arg1->set_value(*interval.start);
+ node->set_right(arg1);
+ }
+
+ if (interval.finish) {
+ expr_t::ptr_op_t lt = new expr_t::op_t(expr_t::op_t::O_LT);
+ lt->set_left(ident);
+
+ expr_t::ptr_op_t arg1 = new expr_t::op_t(expr_t::op_t::VALUE);
+ arg1->set_value(*interval.finish);
+ lt->set_right(arg1);
+
+ if (node) {
+ expr_t::ptr_op_t prev(node);
+ node = new expr_t::op_t(expr_t::op_t::O_AND);
+ node->set_left(prev);
+ node->set_right(lt);
+ } else {
+ node = lt;
+ }
+ }
+ break;
+ }
+
+ case lexer_t::token_t::TOK_EXPR:
+ node = expr_t(*tok.value).get_op();
+ break;
+
+ case lexer_t::token_t::TOK_META: {
+ node = new expr_t::op_t(expr_t::op_t::O_CALL);
+
+ expr_t::ptr_op_t ident = new expr_t::op_t(expr_t::op_t::IDENT);
+ ident->set_ident("has_tag");
+ node->set_left(ident);
+
+ expr_t::ptr_op_t arg1 = new expr_t::op_t(expr_t::op_t::VALUE);
+ arg1->set_value(mask_t(*tok.value));
+
+ tok = lexer.peek_token();
+ if (tok.kind == lexer_t::token_t::TOK_EQ) {
+ tok = lexer.next_token();
+ tok = lexer.next_token();
+ if (tok.kind != lexer_t::token_t::TERM)
+ throw_(parse_error,
+ _("Metadata equality operator not followed by term"));
+
+ expr_t::ptr_op_t arg2 = new expr_t::op_t(expr_t::op_t::VALUE);
+ assert(tok.value);
+ arg2->set_value(mask_t(*tok.value));
+
+ node->set_right(expr_t::op_t::new_node
+ (expr_t::op_t::O_SEQ,
+ expr_t::op_t::new_node
+ (expr_t::op_t::O_CONS, arg1, arg2)));
+ } else {
+ node->set_right(arg1);
+ }
+ break;
+ }
+
+ default: {
+ node = new expr_t::op_t(expr_t::op_t::O_MATCH);
+
+ expr_t::ptr_op_t ident = new expr_t::op_t(expr_t::op_t::IDENT);
+ switch (tok_context) {
+ case lexer_t::token_t::TOK_ACCOUNT:
+ ident->set_ident("account"); break;
+ case lexer_t::token_t::TOK_PAYEE:
+ ident->set_ident("payee"); break;
+ case lexer_t::token_t::TOK_CODE:
+ ident->set_ident("code"); break;
+ case lexer_t::token_t::TOK_NOTE:
+ ident->set_ident("note"); break;
+ default:
+ assert(false); break;
+ }
+
+ expr_t::ptr_op_t mask = new expr_t::op_t(expr_t::op_t::VALUE);
+ DEBUG("query.mask", "Mask from string: " << *tok.value);
+ mask->set_value(mask_t(*tok.value));
+ DEBUG("query.mask", "Mask is: " << mask->as_value().as_mask().str());
+
+ node->set_left(ident);
+ node->set_right(mask);
+ }
+ }
+ break;
+
+ case lexer_t::token_t::LPAREN:
+ node = parse_query_expr(tok_context);
+ tok = lexer.next_token();
+ if (tok.kind != lexer_t::token_t::RPAREN)
+ tok.expected(')');
+ break;
+
+ default:
+ lexer.push_token(tok);
+ break;
+ }
+
+ return node;
+}
+
+expr_t::ptr_op_t
+query_t::parser_t::parse_unary_expr(lexer_t::token_t::kind_t tok_context)
+{
+ expr_t::ptr_op_t node;
+
+ lexer_t::token_t tok = lexer.next_token();
+ switch (tok.kind) {
+ case lexer_t::token_t::TOK_NOT: {
+ expr_t::ptr_op_t term(parse_query_term(tok_context));
+ if (! term)
+ throw_(parse_error,
+ _("%1 operator not followed by argument") << tok.symbol());
+
+ node = new expr_t::op_t(expr_t::op_t::O_NOT);
+ node->set_left(term);
+ break;
+ }
+
+ default:
+ lexer.push_token(tok);
+ node = parse_query_term(tok_context);
+ break;
+ }
+
+ return node;
+}
+
+expr_t::ptr_op_t
+query_t::parser_t::parse_and_expr(lexer_t::token_t::kind_t tok_context)
+{
+ if (expr_t::ptr_op_t node = parse_unary_expr(tok_context)) {
+ while (true) {
+ lexer_t::token_t tok = lexer.next_token();
+ if (tok.kind == lexer_t::token_t::TOK_AND) {
+ expr_t::ptr_op_t prev(node);
+ node = new expr_t::op_t(expr_t::op_t::O_AND);
+ node->set_left(prev);
+ node->set_right(parse_unary_expr(tok_context));
+ if (! node->right())
+ throw_(parse_error,
+ _("%1 operator not followed by argument") << tok.symbol());
+ } else {
+ lexer.push_token(tok);
+ break;
+ }
+ }
+ return node;
+ }
+ return expr_t::ptr_op_t();
+}
+
+expr_t::ptr_op_t
+query_t::parser_t::parse_or_expr(lexer_t::token_t::kind_t tok_context)
+{
+ if (expr_t::ptr_op_t node = parse_and_expr(tok_context)) {
+ while (true) {
+ lexer_t::token_t tok = lexer.next_token();
+ if (tok.kind == lexer_t::token_t::TOK_OR) {
+ expr_t::ptr_op_t prev(node);
+ node = new expr_t::op_t(expr_t::op_t::O_OR);
+ node->set_left(prev);
+ node->set_right(parse_and_expr(tok_context));
+ if (! node->right())
+ throw_(parse_error,
+ _("%1 operator not followed by argument") << tok.symbol());
+ } else {
+ lexer.push_token(tok);
+ break;
+ }
+ }
+ return node;
+ }
+ return expr_t::ptr_op_t();
+}
+
+expr_t::ptr_op_t
+query_t::parser_t::parse_query_expr(lexer_t::token_t::kind_t tok_context)
+{
+ if (expr_t::ptr_op_t node = parse_or_expr(tok_context)) {
+ if (expr_t::ptr_op_t next = parse_query_expr(tok_context)) {
+ expr_t::ptr_op_t prev(node);
+ node = new expr_t::op_t(expr_t::op_t::O_OR);
+ node->set_left(prev);
+ node->set_right(next);
+ }
+ return node;
+ }
+ return expr_t::ptr_op_t();
+}
+
+} // namespace ledger
diff --git a/src/query.h b/src/query.h
new file mode 100644
index 00000000..ebc14020
--- /dev/null
+++ b/src/query.h
@@ -0,0 +1,305 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup expr
+ */
+
+/**
+ * @file predicate.h
+ * @author John Wiegley
+ *
+ * @ingroup expr
+ */
+#ifndef _QUERY_H
+#define _QUERY_H
+
+#include "predicate.h"
+
+namespace ledger {
+
+class query_t : public predicate_t
+{
+public:
+ class lexer_t
+ {
+ friend class query_t;
+ friend class parser_t;
+
+ value_t::sequence_t::const_iterator begin;
+ value_t::sequence_t::const_iterator end;
+
+ string::const_iterator arg_i;
+ string::const_iterator arg_end;
+
+ bool consume_whitespace;
+ bool consume_next_arg;
+
+ public:
+ struct token_t
+ {
+ enum kind_t {
+ UNKNOWN,
+
+ LPAREN,
+ RPAREN,
+
+ TOK_NOT,
+ TOK_AND,
+ TOK_OR,
+ TOK_EQ,
+
+ TOK_DATE,
+ TOK_CODE,
+ TOK_PAYEE,
+ TOK_NOTE,
+ TOK_ACCOUNT,
+ TOK_META,
+ TOK_EXPR,
+
+ TERM,
+
+ END_REACHED
+
+ } kind;
+
+ optional<string> value;
+
+ explicit token_t(kind_t _kind = UNKNOWN,
+ const optional<string>& _value = none)
+ : kind(_kind), value(_value) {
+ TRACE_CTOR(query_t::lexer_t::token_t, "");
+ }
+ token_t(const token_t& tok)
+ : kind(tok.kind), value(tok.value) {
+ TRACE_CTOR(query_t::lexer_t::token_t, "copy");
+ }
+ ~token_t() throw() {
+ TRACE_DTOR(query_t::lexer_t::token_t);
+ }
+
+ token_t& operator=(const token_t& tok) {
+ if (this != &tok) {
+ kind = tok.kind;
+ value = tok.value;
+ }
+ return *this;
+ }
+
+ operator bool() const {
+ return kind != END_REACHED;
+ }
+
+ string to_string() const {
+ switch (kind) {
+ case UNKNOWN: return "UNKNOWN";
+ case LPAREN: return "LPAREN";
+ case RPAREN: return "RPAREN";
+ case TOK_NOT: return "TOK_NOT";
+ case TOK_AND: return "TOK_AND";
+ case TOK_OR: return "TOK_OR";
+ case TOK_EQ: return "TOK_EQ";
+ case TOK_DATE: return "TOK_DATE";
+ case TOK_CODE: return "TOK_CODE";
+ case TOK_PAYEE: return "TOK_PAYEE";
+ case TOK_NOTE: return "TOK_NOTE";
+ case TOK_ACCOUNT: return "TOK_ACCOUNT";
+ case TOK_META: return "TOK_META";
+ case TOK_EXPR: return "TOK_EXPR";
+ case TERM: return string("TERM(") + *value + ")";
+ case END_REACHED: return "END_REACHED";
+ }
+ assert(false);
+ return empty_string;
+ }
+
+ string symbol() const {
+ switch (kind) {
+ case LPAREN: return "(";
+ case RPAREN: return ")";
+ case TOK_NOT: return "not";
+ case TOK_AND: return "and";
+ case TOK_OR: return "or";
+ case TOK_EQ: return "=";
+ case TOK_DATE: return "date";
+ case TOK_CODE: return "code";
+ case TOK_PAYEE: return "payee";
+ case TOK_NOTE: return "note";
+ case TOK_ACCOUNT: return "account";
+ case TOK_META: return "meta";
+ case TOK_EXPR: return "expr";
+
+ case END_REACHED: return "<EOF>";
+
+ case TERM:
+ assert(false);
+ return "<TERM>";
+
+ case UNKNOWN:
+ default:
+ assert(false);
+ return "<UNKNOWN>";
+ }
+ }
+
+ void unexpected();
+ void expected(char wanted, char c = '\0');
+ };
+
+ token_t token_cache;
+
+ lexer_t(value_t::sequence_t::const_iterator _begin,
+ value_t::sequence_t::const_iterator _end)
+ : begin(_begin), end(_end),
+ consume_whitespace(false),
+ consume_next_arg(false)
+ {
+ TRACE_CTOR(query_t::lexer_t, "");
+ assert(begin != end);
+ arg_i = (*begin).as_string().begin();
+ arg_end = (*begin).as_string().end();
+ }
+ lexer_t(const lexer_t& lexer)
+ : begin(lexer.begin), end(lexer.end),
+ arg_i(lexer.arg_i), arg_end(lexer.arg_end),
+ consume_whitespace(lexer.consume_whitespace),
+ consume_next_arg(lexer.consume_next_arg),
+ token_cache(lexer.token_cache)
+ {
+ TRACE_CTOR(query_t::lexer_t, "copy");
+ }
+ ~lexer_t() throw() {
+ TRACE_DTOR(query_t::lexer_t);
+ }
+
+ token_t next_token();
+ void push_token(token_t tok) {
+ assert(token_cache.kind == token_t::UNKNOWN);
+ token_cache = tok;
+ }
+ token_t peek_token() {
+ if (token_cache.kind == token_t::UNKNOWN)
+ token_cache = next_token();
+ return token_cache;
+ }
+ };
+
+protected:
+ class parser_t
+ {
+ friend class query_t;
+
+ value_t args;
+ lexer_t lexer;
+
+ expr_t::ptr_op_t parse_query_term(lexer_t::token_t::kind_t tok_context);
+ expr_t::ptr_op_t parse_unary_expr(lexer_t::token_t::kind_t tok_context);
+ expr_t::ptr_op_t parse_and_expr(lexer_t::token_t::kind_t tok_context);
+ expr_t::ptr_op_t parse_or_expr(lexer_t::token_t::kind_t tok_context);
+ expr_t::ptr_op_t parse_query_expr(lexer_t::token_t::kind_t tok_context);
+
+ public:
+ parser_t(const value_t& _args)
+ : args(_args), lexer(args.begin(), args.end()) {
+ TRACE_CTOR(query_t::parser_t, "");
+ }
+ parser_t(const parser_t& parser)
+ : args(parser.args), lexer(parser.lexer) {
+ TRACE_CTOR(query_t::parser_t, "copy");
+ }
+ ~parser_t() throw() {
+ TRACE_DTOR(query_t::parser_t);
+ }
+
+ expr_t::ptr_op_t parse() {
+ return parse_query_expr(lexer_t::token_t::TOK_ACCOUNT);
+ }
+
+ bool tokens_remaining() {
+ lexer_t::token_t tok = lexer.peek_token();
+ assert(tok.kind != lexer_t::token_t::UNKNOWN);
+ return tok.kind != lexer_t::token_t::END_REACHED;
+ }
+ };
+
+ optional<parser_t> parser;
+
+public:
+ query_t() {
+ TRACE_CTOR(query_t, "");
+ }
+ query_t(const query_t& other)
+ : predicate_t(other) {
+ TRACE_CTOR(query_t, "copy");
+ }
+ query_t(const string& arg,
+ const keep_details_t& _what_to_keep = keep_details_t())
+ : predicate_t(_what_to_keep) {
+ TRACE_CTOR(query_t, "string, keep_details_t");
+ if (! arg.empty()) {
+ value_t temp(string_value(arg));
+ parse_args(temp.to_sequence());
+ }
+ }
+ query_t(const value_t& args,
+ const keep_details_t& _what_to_keep = keep_details_t())
+ : predicate_t(_what_to_keep) {
+ TRACE_CTOR(query_t, "value_t, keep_details_t");
+ if (! args.empty())
+ parse_args(args);
+ }
+ virtual ~query_t() {
+ TRACE_DTOR(query_t);
+ }
+
+ void parse_args(const value_t& args) {
+ if (! parser)
+ parser = parser_t(args);
+ ptr = parser->parse(); // expr_t::ptr
+ }
+
+ void parse_again() {
+ assert(parser);
+ ptr = parser->parse(); // expr_t::ptr
+ }
+
+ bool tokens_remaining() {
+ return parser && parser->tokens_remaining();
+ }
+
+ virtual string text() {
+ return print_to_str();
+ }
+};
+
+} // namespace ledger
+
+#endif // _QUERY_H
diff --git a/src/quotes.cc b/src/quotes.cc
new file mode 100644
index 00000000..f892e93f
--- /dev/null
+++ b/src/quotes.cc
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "amount.h"
+#include "commodity.h"
+#include "pool.h"
+#include "quotes.h"
+
+namespace ledger {
+
+optional<price_point_t>
+commodity_quote_from_script(commodity_t& commodity,
+ const optional<commodity_t&>& exchange_commodity)
+{
+ DEBUG("commodity.download", "downloading quote for symbol " << commodity.symbol());
+#if defined(DEBUG_ON)
+ if (exchange_commodity)
+ DEBUG("commodity.download",
+ " in terms of commodity " << exchange_commodity->symbol());
+#endif
+
+ char buf[256];
+ buf[0] = '\0';
+
+ string getquote_cmd("getquote \"");
+ getquote_cmd += commodity.symbol();
+ getquote_cmd += "\" \"";
+ if (exchange_commodity)
+ getquote_cmd += exchange_commodity->symbol();
+ getquote_cmd += "\"";
+
+ DEBUG("commodity.download", "invoking command: " << getquote_cmd);
+
+ bool success = true;
+ if (FILE * fp = popen(getquote_cmd.c_str(), "r")) {
+ if (std::feof(fp) || ! std::fgets(buf, 255, fp))
+ success = false;
+ if (pclose(fp) != 0)
+ success = false;
+ } else {
+ success = false;
+ }
+
+ if (success && buf[0]) {
+ if (char * p = std::strchr(buf, '\n')) *p = '\0';
+ DEBUG("commodity.download", "downloaded quote: " << buf);
+
+ if (optional<std::pair<commodity_t *, price_point_t> > point =
+ commodity_pool_t::current_pool->parse_price_directive(buf)) {
+ if (commodity_pool_t::current_pool->price_db) {
+#if defined(__GNUG__) && __GNUG__ < 3
+ ofstream database(*commodity_pool_t::current_pool->price_db,
+ ios::out | ios::app);
+#else
+ ofstream database(*commodity_pool_t::current_pool->price_db,
+ std::ios_base::out | std::ios_base::app);
+#endif
+ database << "P "
+ << format_datetime(point->second.when, FMT_WRITTEN)
+ << " " << commodity.symbol()
+ << " " << point->second.price
+ << std::endl;
+ }
+ return point->second;
+ }
+ } else {
+ DEBUG("commodity.download",
+ "Failed to download price for '" << commodity.symbol() <<
+ "' (command: \"getquote " << commodity.symbol() <<
+ " " << (exchange_commodity ?
+ exchange_commodity->symbol() : "''") << "\")");
+
+ // Don't try to download this commodity again.
+ commodity.add_flags(COMMODITY_NOMARKET);
+ }
+ return none;
+}
+
+} // namespace ledger
diff --git a/src/quotes.h b/src/quotes.h
new file mode 100644
index 00000000..d00c5bfd
--- /dev/null
+++ b/src/quotes.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup extra
+ */
+
+/**
+ * @file quotes.h
+ * @author John Wiegley
+ *
+ * @ingroup extra
+ */
+#ifndef _QUOTES_H
+#define _QUOTES_H
+
+namespace ledger {
+
+optional<price_point_t>
+commodity_quote_from_script(commodity_t& commodity,
+ const optional<commodity_t&>& exchange_commodity);
+
+} // namespace ledger
+
+#endif // _QUOTES_H
diff --git a/src/report.cc b/src/report.cc
new file mode 100644
index 00000000..2d9d7cc6
--- /dev/null
+++ b/src/report.cc
@@ -0,0 +1,1307 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "report.h"
+#include "session.h"
+#include "pool.h"
+#include "format.h"
+#include "query.h"
+#include "output.h"
+#include "iterators.h"
+#include "filters.h"
+#include "precmd.h"
+#include "stats.h"
+#include "generate.h"
+#include "draft.h"
+#include "xml.h"
+#include "emacs.h"
+
+namespace ledger {
+
+void report_t::normalize_options(const string& verb)
+{
+ // Patch up some of the reporting options based on what kind of
+ // command it was.
+
+#ifdef HAVE_ISATTY
+ if (! HANDLED(force_color)) {
+ if (! HANDLED(no_color) && isatty(STDOUT_FILENO))
+ HANDLER(color).on_only(string("?normalize"));
+ if (HANDLED(color) && ! isatty(STDOUT_FILENO))
+ HANDLER(color).off();
+ }
+ if (! HANDLED(force_pager)) {
+ if (HANDLED(pager_) && ! isatty(STDOUT_FILENO))
+ HANDLER(pager_).off();
+ }
+#endif
+
+ item_t::use_effective_date = (HANDLED(effective) &&
+ ! HANDLED(actual_dates));
+
+ commodity_pool_t::current_pool->keep_base = HANDLED(base);
+ commodity_pool_t::current_pool->get_quotes = session.HANDLED(download);
+
+ if (session.HANDLED(price_exp_))
+ commodity_pool_t::current_pool->quote_leeway =
+ session.HANDLER(price_exp_).value.as_long();
+
+ if (session.HANDLED(price_db_))
+ commodity_pool_t::current_pool->price_db = session.HANDLER(price_db_).str();
+ else
+ commodity_pool_t::current_pool->price_db = none;
+
+ if (HANDLED(date_format_))
+ set_date_format(HANDLER(date_format_).str().c_str());
+ if (HANDLED(datetime_format_))
+ set_datetime_format(HANDLER(datetime_format_).str().c_str());
+ if (HANDLED(start_of_week_)) {
+ if (optional<date_time::weekdays> weekday =
+ string_to_day_of_week(HANDLER(start_of_week_).str()))
+ start_of_week = *weekday;
+ }
+
+ if (verb == "print" || verb == "xact" || verb == "dump") {
+ HANDLER(related).on_only(string("?normalize"));
+ HANDLER(related_all).on_only(string("?normalize"));
+ }
+ else if (verb == "equity") {
+ HANDLER(equity).on_only(string("?normalize"));
+ }
+
+ if (verb == "print")
+ HANDLER(limit_).on(string("?normalize"), "actual");
+
+ if (! HANDLED(empty))
+ HANDLER(display_).on(string("?normalize"), "amount|(!post&total)");
+
+ if (verb[0] != 'b' && verb[0] != 'r')
+ HANDLER(base).on_only(string("?normalize"));
+
+ // If a time period was specified with -p, check whether it also gave a
+ // begin and/or end to the report period (though these can be overridden
+ // using -b or -e). Then, if no _duration_ was specified (such as monthly),
+ // then ignore the period since the begin/end are the only interesting
+ // details.
+ if (HANDLED(period_)) {
+ if (! HANDLED(sort_all_))
+ HANDLER(sort_xacts_).on_only(string("?normalize"));
+
+ date_interval_t interval(HANDLER(period_).str());
+
+ optional<date_t> begin = interval.begin(session.current_year);
+ optional<date_t> end = interval.end(session.current_year);
+
+ if (! HANDLED(begin_) && begin) {
+ string predicate = "date>=[" + to_iso_extended_string(*begin) + "]";
+ HANDLER(limit_).on(string("?normalize"), predicate);
+ }
+ if (! HANDLED(end_) && end) {
+ string predicate = "date<[" + to_iso_extended_string(*end) + "]";
+ HANDLER(limit_).on(string("?normalize"), predicate);
+ }
+
+ if (! interval.duration)
+ HANDLER(period_).off();
+ }
+
+ // If -j or -J were specified, set the appropriate format string now so as
+ // to avoid option ordering issues were we to have done it during the
+ // initial parsing of the options.
+ if (HANDLED(amount_data)) {
+ HANDLER(format_)
+ .on_with(string("?normalize"), HANDLER(plot_amount_format_).value);
+ }
+ else if (HANDLED(total_data)) {
+ HANDLER(format_)
+ .on_with(string("?normalize"), HANDLER(plot_total_format_).value);
+ }
+
+ // If the --exchange (-X) option was used, parse out any final price
+ // settings that may be there.
+ if (HANDLED(exchange_) &&
+ HANDLER(exchange_).str().find('=') != string::npos) {
+ value_t(0L).exchange_commodities(HANDLER(exchange_).str(), true,
+ terminus);
+ }
+
+ long cols = 0;
+ if (HANDLED(columns_))
+ cols = HANDLER(columns_).value.to_long();
+ else if (const char * columns = std::getenv("COLUMNS"))
+ cols = lexical_cast<long>(columns);
+ else
+ cols = 80L;
+
+ if (cols > 0) {
+ DEBUG("auto.columns", "cols = " << cols);
+
+ if (! HANDLER(date_width_).specified)
+ HANDLER(date_width_)
+ .on_with(none, static_cast<long>(format_date(CURRENT_DATE(),
+ FMT_PRINTED).length()));
+
+ long date_width = HANDLER(date_width_).value.to_long();
+ long payee_width = (HANDLER(payee_width_).specified ?
+ HANDLER(payee_width_).value.to_long() :
+ int(double(cols) * 0.263157));
+ long account_width = (HANDLER(account_width_).specified ?
+ HANDLER(account_width_).value.to_long() :
+ int(double(cols) * 0.302631));
+ long amount_width = (HANDLER(amount_width_).specified ?
+ HANDLER(amount_width_).value.to_long() :
+ int(double(cols) * 0.157894));
+ long total_width = (HANDLER(total_width_).specified ?
+ HANDLER(total_width_).value.to_long() :
+ amount_width);
+
+ DEBUG("auto.columns", "date_width = " << date_width);
+ DEBUG("auto.columns", "payee_width = " << payee_width);
+ DEBUG("auto.columns", "account_width = " << account_width);
+ DEBUG("auto.columns", "amount_width = " << amount_width);
+ DEBUG("auto.columns", "total_width = " << total_width);
+
+ if (! HANDLER(date_width_).specified &&
+ ! HANDLER(payee_width_).specified &&
+ ! HANDLER(account_width_).specified &&
+ ! HANDLER(amount_width_).specified &&
+ ! HANDLER(total_width_).specified) {
+ long total = (4 /* the spaces between */ + date_width + payee_width +
+ account_width + amount_width + total_width);
+ if (total > cols) {
+ DEBUG("auto.columns", "adjusting account down");
+ account_width -= total - cols;
+ DEBUG("auto.columns", "account_width now = " << account_width);
+ }
+ }
+
+ if (! HANDLER(date_width_).specified)
+ HANDLER(date_width_).on_with(string("?normalize"), date_width);
+ if (! HANDLER(payee_width_).specified)
+ HANDLER(payee_width_).on_with(string("?normalize"), payee_width);
+ if (! HANDLER(account_width_).specified)
+ HANDLER(account_width_).on_with(string("?normalize"), account_width);
+ if (! HANDLER(amount_width_).specified)
+ HANDLER(amount_width_).on_with(string("?normalize"), amount_width);
+ if (! HANDLER(total_width_).specified)
+ HANDLER(total_width_).on_with(string("?normalize"), total_width);
+ }
+}
+
+void report_t::parse_query_args(const value_t& args, const string& whence)
+{
+ query_t query(args, what_to_keep());
+ if (query) {
+ HANDLER(limit_).on(whence, query.text());
+
+ DEBUG("report.predicate",
+ "Predicate = " << HANDLER(limit_).str());
+ }
+
+ if (query.tokens_remaining()) {
+ query.parse_again();
+ if (query) {
+ HANDLER(display_).on(whence, query.text());
+
+ DEBUG("report.predicate",
+ "Display predicate = " << HANDLER(display_).str());
+ }
+ }
+}
+
+void report_t::posts_report(post_handler_ptr handler)
+{
+ journal_posts_iterator walker(*session.journal.get());
+ pass_down_posts(chain_post_handlers(*this, handler), walker);
+ session.journal->clear_xdata();
+}
+
+void report_t::generate_report(post_handler_ptr handler)
+{
+ HANDLER(limit_).on(string("#generate"), "actual");
+
+ generate_posts_iterator walker
+ (session, HANDLED(seed_) ?
+ static_cast<unsigned int>(HANDLER(seed_).value.to_long()) : 0,
+ HANDLED(head_) ?
+ static_cast<unsigned int>(HANDLER(head_).value.to_long()) : 50);
+
+ pass_down_posts(chain_post_handlers(*this, handler), walker);
+}
+
+void report_t::xact_report(post_handler_ptr handler, xact_t& xact)
+{
+ xact_posts_iterator walker(xact);
+ pass_down_posts(chain_post_handlers(*this, handler), walker);
+ xact.clear_xdata();
+}
+
+void report_t::accounts_report(acct_handler_ptr handler)
+{
+ journal_posts_iterator walker(*session.journal.get());
+
+ // The lifetime of the chain object controls the lifetime of all temporary
+ // objects created within it during the call to pass_down_posts, which will
+ // be needed later by the pass_down_accounts.
+ post_handler_ptr chain =
+ chain_post_handlers(*this, post_handler_ptr(new ignore_posts), true);
+ pass_down_posts(chain, walker);
+
+ HANDLER(amount_).expr.mark_uncompiled();
+ HANDLER(total_).expr.mark_uncompiled();
+ HANDLER(display_amount_).expr.mark_uncompiled();
+ HANDLER(display_total_).expr.mark_uncompiled();
+ HANDLER(revalued_total_).expr.mark_uncompiled();
+
+ scoped_ptr<accounts_iterator> iter;
+ if (! HANDLED(sort_)) {
+ iter.reset(new basic_accounts_iterator(*session.journal->master));
+ } else {
+ expr_t sort_expr(HANDLER(sort_).str());
+ sort_expr.set_context(this);
+ iter.reset(new sorted_accounts_iterator(*session.journal->master,
+ sort_expr, HANDLED(flat)));
+ }
+
+ if (HANDLED(display_))
+ pass_down_accounts(handler, *iter.get(),
+ predicate_t(HANDLER(display_).str(), what_to_keep()),
+ *this);
+ else
+ pass_down_accounts(handler, *iter.get());
+
+ session.journal->clear_xdata();
+}
+
+void report_t::commodities_report(post_handler_ptr handler)
+{
+ posts_commodities_iterator walker(*session.journal.get());
+ pass_down_posts(chain_post_handlers(*this, handler), walker);
+ session.journal->clear_xdata();
+}
+
+value_t report_t::fn_amount_expr(call_scope_t& scope)
+{
+ return HANDLER(amount_).expr.calc(scope);
+}
+
+value_t report_t::fn_total_expr(call_scope_t& scope)
+{
+ return HANDLER(total_).expr.calc(scope);
+}
+
+value_t report_t::fn_display_amount(call_scope_t& scope)
+{
+ return HANDLER(display_amount_).expr.calc(scope);
+}
+
+value_t report_t::fn_display_total(call_scope_t& scope)
+{
+ return HANDLER(display_total_).expr.calc(scope);
+}
+
+value_t report_t::fn_market(call_scope_t& scope)
+{
+ interactive_t args(scope, "a&ts");
+
+ value_t result;
+ optional<datetime_t> moment = (args.has(1) ?
+ args.get<datetime_t>(1) :
+ optional<datetime_t>());
+ if (args.has(2))
+ result = args.value_at(0).exchange_commodities(args.get<string>(2),
+ /* add_prices= */ false,
+ moment);
+ else
+ result = args.value_at(0).value(true, moment);
+
+ if (! result.is_null())
+ return result;
+
+ return args.value_at(0);
+}
+
+value_t report_t::fn_get_at(call_scope_t& scope)
+{
+ interactive_t args(scope, "Sl");
+
+ DEBUG("report.get_at", "get_at[0] = " << args.value_at(0));
+ DEBUG("report.get_at", "get_at[1] = " << args.value_at(1));
+
+ if (args.get<long>(1) == 0) {
+ if (! args.value_at(0).is_sequence())
+ return args.value_at(0);
+ } else {
+ if (! args.value_at(0).is_sequence())
+ throw_(std::runtime_error,
+ _("Attempting to get argument at index %1 from %2")
+ << args.get<long>(1) << args.value_at(0).label());
+ }
+ return args.get<const value_t::sequence_t&>(0)[args.get<long>(1)];
+}
+
+value_t report_t::fn_is_seq(call_scope_t& scope)
+{
+ return scope.value().is_sequence();
+}
+
+value_t report_t::fn_strip(call_scope_t& args)
+{
+ return args.value().strip_annotations(what_to_keep());
+}
+
+value_t report_t::fn_trim(call_scope_t& args)
+{
+ string temp(args.value().to_string());
+ scoped_array<char> buf(new char[temp.length() + 1]);
+ std::strcpy(buf.get(), temp.c_str());
+
+ const char * p = buf.get();
+ while (*p && std::isspace(*p))
+ p++;
+
+ const char * e = buf.get() + temp.length();
+ while (e > p && std::isspace(*e))
+ e--;
+
+ if (e == p) {
+ return string_value(empty_string);
+ }
+ else if (e < p) {
+ assert(false);
+ return string_value(empty_string);
+ }
+ else {
+ return string_value(string(p, e - p));
+ }
+}
+
+value_t report_t::fn_scrub(call_scope_t& args)
+{
+ value_t temp(args.value().strip_annotations(what_to_keep()));
+ if (HANDLED(base))
+ return temp;
+ else
+ return temp.unreduced();
+}
+
+value_t report_t::fn_rounded(call_scope_t& args)
+{
+ return args.value().rounded();
+}
+
+value_t report_t::fn_unrounded(call_scope_t& args)
+{
+ return args.value().unrounded();
+}
+
+value_t report_t::fn_quantity(call_scope_t& scope)
+{
+ interactive_t args(scope, "a");
+ return args.get<amount_t>(0).number();
+}
+
+value_t report_t::fn_floor(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ return args.value_at(0).floored();
+}
+
+value_t report_t::fn_abs(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ return args.value_at(0).abs();
+}
+
+value_t report_t::fn_truncated(call_scope_t& scope)
+{
+ interactive_t args(scope, "v&ll");
+ return string_value(format_t::truncate
+ (args.get<string>(0),
+ args.has(1) && args.get<int>(1) > 0 ? args.get<int>(1) : 0,
+ args.has(2) ? args.get<int>(2) : 0));
+}
+
+value_t report_t::fn_justify(call_scope_t& scope)
+{
+ interactive_t args(scope, "vl&lbb");
+ std::ostringstream out;
+ args.value_at(0)
+ .print(out, args.get<int>(1),
+ args.has(2) ? args.get<int>(2) : -1,
+ args.has(3) ? args.get<bool>(3) : false,
+ args.has(4) ? args.get<bool>(4) : false);
+ return string_value(out.str());
+}
+
+value_t report_t::fn_quoted(call_scope_t& scope)
+{
+ interactive_t args(scope, "s");
+ std::ostringstream out;
+
+ out << '"';
+ foreach (const char ch, args.get<string>(0)) {
+ if (ch == '"')
+ out << "\\\"";
+ else
+ out << ch;
+ }
+ out << '"';
+
+ return string_value(out.str());
+}
+
+value_t report_t::fn_join(call_scope_t& scope)
+{
+ interactive_t args(scope, "s");
+ std::ostringstream out;
+
+ foreach (const char ch, args.get<string>(0)) {
+ if (ch != '\n')
+ out << ch;
+ else
+ out << "\\n";
+ }
+ return string_value(out.str());
+}
+
+value_t report_t::fn_format_date(call_scope_t& scope)
+{
+ interactive_t args(scope, "d&s");
+ if (args.has(1))
+ return string_value(format_date(args.get<date_t>(0), FMT_CUSTOM,
+ args.get<string>(1).c_str()));
+ else
+ return string_value(format_date(args.get<date_t>(0), FMT_PRINTED));
+}
+
+value_t report_t::fn_ansify_if(call_scope_t& scope)
+{
+ interactive_t args(scope, "v&s");
+
+ if (args.has(1)) {
+ string color = args.get<string>(1);
+ std::ostringstream buf;
+ if (color == "black") buf << "\033[30m";
+ else if (color == "red") buf << "\033[31m";
+ else if (color == "green") buf << "\033[32m";
+ else if (color == "yellow") buf << "\033[33m";
+ else if (color == "blue") buf << "\033[34m";
+ else if (color == "magenta") buf << "\033[35m";
+ else if (color == "cyan") buf << "\033[36m";
+ else if (color == "white") buf << "\033[37m";
+ else if (color == "bold") buf << "\033[1m";
+ else if (color == "underline") buf << "\033[4m";
+ else if (color == "blink") buf << "\033[5m";
+ buf << args.value_at(0);
+ buf << "\033[0m";
+ return string_value(buf.str());
+ } else {
+ return args.value_at(0);
+ }
+}
+
+value_t report_t::fn_percent(call_scope_t& scope)
+{
+ interactive_t args(scope, "aa");
+ return (amount_t("100.00%") *
+ (args.get<amount_t>(0) / args.get<amount_t>(1)).number());
+}
+
+value_t report_t::fn_price(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ return args.value_at(0).price();
+}
+
+value_t report_t::fn_lot_date(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ if (args.value_at(0).has_annotation()) {
+ const annotation_t& details(args.value_at(0).annotation());
+ if (details.date)
+ return *details.date;
+ }
+ return NULL_VALUE;
+}
+
+value_t report_t::fn_lot_price(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ if (args.value_at(0).has_annotation()) {
+ const annotation_t& details(args.value_at(0).annotation());
+ if (details.price)
+ return *details.price;
+ }
+ return NULL_VALUE;
+}
+
+value_t report_t::fn_lot_tag(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ if (args.value_at(0).has_annotation()) {
+ const annotation_t& details(args.value_at(0).annotation());
+ if (details.tag)
+ return string_value(*details.tag);
+ }
+ return NULL_VALUE;
+}
+
+value_t report_t::fn_to_boolean(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ args.value_at(0).in_place_cast(value_t::BOOLEAN);
+ return args.value_at(0);
+}
+
+value_t report_t::fn_to_int(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ args.value_at(0).in_place_cast(value_t::INTEGER);
+ return args.value_at(0);
+}
+
+value_t report_t::fn_to_datetime(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ args.value_at(0).in_place_cast(value_t::DATETIME);
+ return args.value_at(0);
+}
+
+value_t report_t::fn_to_date(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ args.value_at(0).in_place_cast(value_t::DATE);
+ return args.value_at(0);
+}
+
+value_t report_t::fn_to_amount(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ args.value_at(0).in_place_cast(value_t::AMOUNT);
+ return args.value_at(0);
+}
+
+value_t report_t::fn_to_balance(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ args.value_at(0).in_place_cast(value_t::BALANCE);
+ return args.value_at(0);
+}
+
+value_t report_t::fn_to_string(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ args.value_at(0).in_place_cast(value_t::STRING);
+ return args.value_at(0);
+}
+
+value_t report_t::fn_to_mask(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ args.value_at(0).in_place_cast(value_t::MASK);
+ return args.value_at(0);
+}
+
+value_t report_t::fn_to_sequence(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ args.value_at(0).in_place_cast(value_t::SEQUENCE);
+ return args.value_at(0);
+}
+
+namespace {
+ value_t fn_black(call_scope_t&) {
+ return string_value("black");
+ }
+ value_t fn_blink(call_scope_t&) {
+ return string_value("blink");
+ }
+ value_t fn_blue(call_scope_t&) {
+ return string_value("blue");
+ }
+ value_t fn_bold(call_scope_t&) {
+ return string_value("bold");
+ }
+ value_t fn_cyan(call_scope_t&) {
+ return string_value("cyan");
+ }
+ value_t fn_green(call_scope_t&) {
+ return string_value("green");
+ }
+ value_t fn_magenta(call_scope_t&) {
+ return string_value("magenta");
+ }
+ value_t fn_red(call_scope_t&) {
+ return string_value("red");
+ }
+ value_t fn_underline(call_scope_t&) {
+ return string_value("underline");
+ }
+ value_t fn_white(call_scope_t&) {
+ return string_value("white");
+ }
+ value_t fn_yellow(call_scope_t&) {
+ return string_value("yellow");
+ }
+ value_t fn_false(call_scope_t&) {
+ return false;
+ }
+ value_t fn_null(call_scope_t&) {
+ return NULL_VALUE;
+ }
+}
+
+value_t report_t::reload_command(call_scope_t&)
+{
+ session.close_journal_files();
+ session.read_journal_files();
+ return true;
+}
+
+value_t report_t::echo_command(call_scope_t& scope)
+{
+ interactive_t args(scope, "s");
+ std::ostream& out(output_stream);
+ out << args.get<string>(0) << std::endl;
+ return true;
+}
+
+option_t<report_t> * report_t::lookup_option(const char * p)
+{
+ switch (*p) {
+ case '%':
+ OPT_CH(percent);
+ break;
+ case 'A':
+ OPT_CH(average);
+ break;
+ case 'B':
+ OPT_CH(basis);
+ break;
+ case 'C':
+ OPT_CH(cleared);
+ break;
+ case 'D':
+ OPT_CH(daily);
+ break;
+ case 'E':
+ OPT_CH(empty);
+ break;
+ case 'F':
+ OPT_CH(format_);
+ break;
+ case 'G':
+ OPT_CH(gain);
+ break;
+ case 'I':
+ OPT_CH(price);
+ break;
+ case 'J':
+ OPT_CH(total_data);
+ break;
+ case 'L':
+ OPT_CH(actual);
+ break;
+ case 'M':
+ OPT_CH(monthly);
+ break;
+ case 'O':
+ OPT_CH(quantity);
+ break;
+ case 'P':
+ OPT_CH(by_payee);
+ break;
+ case 'R':
+ OPT_CH(real);
+ break;
+ case 'S':
+ OPT_CH(sort_);
+ break;
+ case 'T':
+ OPT_CH(total_);
+ break;
+ case 'U':
+ OPT_CH(uncleared);
+ break;
+ case 'V':
+ OPT_CH(market);
+ break;
+ case 'W':
+ OPT_CH(weekly);
+ break;
+ case 'X':
+ OPT_CH(exchange_);
+ break;
+ case 'Y':
+ OPT_CH(yearly);
+ break;
+ case 'a':
+ OPT(abbrev_len_);
+ else OPT_(account_);
+ else OPT(actual);
+ else OPT(actual_dates);
+ else OPT(add_budget);
+ else OPT(amount_);
+ else OPT(amount_data);
+ else OPT(anon);
+ else OPT_ALT(color, ansi);
+ else OPT(average);
+ else OPT(account_width_);
+ else OPT(amount_width_);
+ break;
+ case 'b':
+ OPT(balance_format_);
+ else OPT(base);
+ else OPT_ALT(basis, cost);
+ else OPT_(begin_);
+ else OPT(budget);
+ else OPT(by_payee);
+ break;
+ case 'c':
+ OPT(csv_format_);
+ else OPT(cleared);
+ else OPT(collapse);
+ else OPT(collapse_if_zero);
+ else OPT(color);
+ else OPT(columns_);
+ else OPT_ALT(basis, cost);
+ else OPT_(current);
+ break;
+ case 'd':
+ OPT(daily);
+ else OPT(date_);
+ else OPT(date_format_);
+ else OPT(datetime_format_);
+ else OPT(depth_);
+ else OPT(deviation);
+ else OPT_(display_);
+ else OPT(display_amount_);
+ else OPT(display_total_);
+ else OPT_ALT(dow, days-of-week);
+ else OPT(date_width_);
+ break;
+ case 'e':
+ OPT(effective);
+ else OPT(empty);
+ else OPT_(end_);
+ else OPT(equity);
+ else OPT(exact);
+ else OPT(exchange_);
+ break;
+ case 'f':
+ OPT(flat);
+ else OPT_ALT(forecast_while_, forecast_);
+ else OPT(forecast_years_);
+ else OPT(format_);
+ else OPT(force_color);
+ else OPT(force_pager);
+ else OPT_ALT(head_, first_);
+ break;
+ case 'g':
+ OPT(gain);
+ break;
+ case 'h':
+ OPT(head_);
+ break;
+ case 'i':
+ OPT(invert);
+ break;
+ case 'j':
+ OPT_CH(amount_data);
+ break;
+ case 'l':
+ OPT_(limit_);
+ else OPT(lot_dates);
+ else OPT(lot_prices);
+ else OPT(lot_tags);
+ else OPT(lots);
+ else OPT(lots_actual);
+ else OPT_ALT(tail_, last_);
+ break;
+ case 'm':
+ OPT(market);
+ else OPT(monthly);
+ break;
+ case 'n':
+ OPT_CH(collapse);
+ else OPT(no_color);
+ else OPT(no_total);
+ else OPT(now_);
+ break;
+ case 'o':
+ OPT(only_);
+ else OPT_(output_);
+ break;
+ case 'p':
+ OPT(pager_);
+ else OPT(payee_);
+ else OPT(pending);
+ else OPT(percent);
+ else OPT_(period_);
+ else OPT_ALT(sort_xacts_, period_sort_);
+ else OPT(plot_amount_format_);
+ else OPT(plot_total_format_);
+ else OPT(price);
+ else OPT(prices_format_);
+ else OPT(pricedb_format_);
+ else OPT(print_format_);
+ else OPT(payee_width_);
+ else OPT(prepend_format_);
+ break;
+ case 'q':
+ OPT(quantity);
+ else OPT(quarterly);
+ break;
+ case 'r':
+ OPT(raw);
+ else OPT(real);
+ else OPT(register_format_);
+ else OPT_(related);
+ else OPT(related_all);
+ else OPT(revalued);
+ else OPT(revalued_only);
+ else OPT(revalued_total_);
+ break;
+ case 's':
+ OPT(sort_);
+ else OPT(sort_all_);
+ else OPT(sort_xacts_);
+ else OPT_(subtotal);
+ else OPT(start_of_week_);
+ else OPT(seed_);
+ break;
+ case 't':
+ OPT_CH(amount_);
+ else OPT(tail_);
+ else OPT(total_);
+ else OPT(total_data);
+ else OPT(truncate_);
+ else OPT(total_width_);
+ break;
+ case 'u':
+ OPT(unbudgeted);
+ else OPT(uncleared);
+ else OPT(unrealized);
+ else OPT(unrealized_gains_);
+ else OPT(unrealized_losses_);
+ else OPT(unround);
+ else OPT(unsorted);
+ break;
+ case 'w':
+ OPT(weekly);
+ else OPT_(wide);
+ break;
+ case 'y':
+ OPT_CH(date_format_);
+ else OPT(yearly);
+ break;
+ }
+ return NULL;
+}
+
+void report_t::define(const symbol_t::kind_t kind, const string& name,
+ expr_t::ptr_op_t def)
+{
+ session.define(kind, name, def);
+}
+
+expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
+ const string& name)
+{
+ if (expr_t::ptr_op_t def = session.lookup(kind, name))
+ return def;
+
+ const char * p = name.c_str();
+
+ switch (kind) {
+ case symbol_t::FUNCTION:
+ // Support 2.x's single-letter value expression names.
+ if (*(p + 1) == '\0') {
+ switch (*p) {
+ case 'd':
+ case 'm':
+ return MAKE_FUNCTOR(report_t::fn_now);
+ case 'P':
+ return MAKE_FUNCTOR(report_t::fn_market);
+ case 't':
+ return MAKE_FUNCTOR(report_t::fn_display_amount);
+ case 'T':
+ return MAKE_FUNCTOR(report_t::fn_display_total);
+ case 'U':
+ return MAKE_FUNCTOR(report_t::fn_abs);
+ case 'S':
+ return MAKE_FUNCTOR(report_t::fn_strip);
+ case 'i':
+ throw_(std::runtime_error,
+ _("The i value expression variable is no longer supported"));
+ case 'A':
+ throw_(std::runtime_error,
+ _("The A value expression variable is no longer supported"));
+ case 'v':
+ case 'V':
+ throw_(std::runtime_error,
+ _("The V and v value expression variables are no longer supported"));
+ case 'I':
+ case 'B':
+ throw_(std::runtime_error,
+ _("The I and B value expression variables are no longer supported"));
+ case 'g':
+ case 'G':
+ throw_(std::runtime_error,
+ _("The G and g value expression variables are no longer supported"));
+ default:
+ return NULL;
+ }
+ }
+
+ switch (*p) {
+ case 'a':
+ if (is_eq(p, "amount_expr"))
+ return MAKE_FUNCTOR(report_t::fn_amount_expr);
+ else if (is_eq(p, "ansify_if"))
+ return MAKE_FUNCTOR(report_t::fn_ansify_if);
+ else if (is_eq(p, "abs"))
+ return MAKE_FUNCTOR(report_t::fn_abs);
+ break;
+
+ case 'b':
+ if (is_eq(p, "black"))
+ return WRAP_FUNCTOR(fn_black);
+ else if (is_eq(p, "blink"))
+ return WRAP_FUNCTOR(fn_blink);
+ else if (is_eq(p, "blue"))
+ return WRAP_FUNCTOR(fn_blue);
+ else if (is_eq(p, "bold"))
+ return WRAP_FUNCTOR(fn_bold);
+ break;
+
+ case 'c':
+ if (is_eq(p, "cyan"))
+ return WRAP_FUNCTOR(fn_cyan);
+ break;
+
+ case 'd':
+ if (is_eq(p, "display_amount"))
+ return MAKE_FUNCTOR(report_t::fn_display_amount);
+ else if (is_eq(p, "display_total"))
+ return MAKE_FUNCTOR(report_t::fn_display_total);
+ else if (is_eq(p, "date"))
+ return MAKE_FUNCTOR(report_t::fn_now);
+ break;
+
+ case 'f':
+ if (is_eq(p, "format_date"))
+ return MAKE_FUNCTOR(report_t::fn_format_date);
+ else if (is_eq(p, "floor"))
+ return MAKE_FUNCTOR(report_t::fn_floor);
+ break;
+
+ case 'g':
+ if (is_eq(p, "get_at"))
+ return MAKE_FUNCTOR(report_t::fn_get_at);
+ else if (is_eq(p, "green"))
+ return WRAP_FUNCTOR(fn_green);
+ break;
+
+ case 'i':
+ if (is_eq(p, "is_seq"))
+ return MAKE_FUNCTOR(report_t::fn_is_seq);
+ break;
+
+ case 'j':
+ if (is_eq(p, "justify"))
+ return MAKE_FUNCTOR(report_t::fn_justify);
+ else if (is_eq(p, "join"))
+ return MAKE_FUNCTOR(report_t::fn_join);
+ break;
+
+ case 'm':
+ if (is_eq(p, "market"))
+ return MAKE_FUNCTOR(report_t::fn_market);
+ else if (is_eq(p, "magenta"))
+ return WRAP_FUNCTOR(fn_magenta);
+ break;
+
+ case 'n':
+ if (is_eq(p, "null"))
+ return WRAP_FUNCTOR(fn_null);
+ else if (is_eq(p, "now"))
+ return MAKE_FUNCTOR(report_t::fn_now);
+ break;
+
+ case 'o':
+ if (is_eq(p, "options"))
+ return MAKE_FUNCTOR(report_t::fn_options);
+ break;
+
+ case 'p':
+ if (is_eq(p, "post"))
+ return WRAP_FUNCTOR(fn_false);
+ else if (is_eq(p, "percent"))
+ return MAKE_FUNCTOR(report_t::fn_percent);
+ else if (is_eq(p, "price"))
+ return MAKE_FUNCTOR(report_t::fn_price);
+ break;
+
+ case 'q':
+ if (is_eq(p, "quoted"))
+ return MAKE_FUNCTOR(report_t::fn_quoted);
+ else if (is_eq(p, "quantity"))
+ return MAKE_FUNCTOR(report_t::fn_quantity);
+ break;
+
+ case 'r':
+ if (is_eq(p, "rounded"))
+ return MAKE_FUNCTOR(report_t::fn_rounded);
+ else if (is_eq(p, "red"))
+ return WRAP_FUNCTOR(fn_red);
+ break;
+
+ case 's':
+ if (is_eq(p, "scrub"))
+ return MAKE_FUNCTOR(report_t::fn_scrub);
+ else if (is_eq(p, "strip"))
+ return MAKE_FUNCTOR(report_t::fn_strip);
+ break;
+
+ case 't':
+ if (is_eq(p, "truncated"))
+ return MAKE_FUNCTOR(report_t::fn_truncated);
+ else if (is_eq(p, "total_expr"))
+ return MAKE_FUNCTOR(report_t::fn_total_expr);
+ else if (is_eq(p, "today"))
+ return MAKE_FUNCTOR(report_t::fn_today);
+ else if (is_eq(p, "t"))
+ return MAKE_FUNCTOR(report_t::fn_display_amount);
+ else if (is_eq(p, "trim"))
+ return MAKE_FUNCTOR(report_t::fn_trim);
+ else if (is_eq(p, "to_boolean"))
+ return MAKE_FUNCTOR(report_t::fn_to_boolean);
+ else if (is_eq(p, "to_int"))
+ return MAKE_FUNCTOR(report_t::fn_to_int);
+ else if (is_eq(p, "to_datetime"))
+ return MAKE_FUNCTOR(report_t::fn_to_datetime);
+ else if (is_eq(p, "to_date"))
+ return MAKE_FUNCTOR(report_t::fn_to_date);
+ else if (is_eq(p, "to_amount"))
+ return MAKE_FUNCTOR(report_t::fn_to_amount);
+ else if (is_eq(p, "to_balance"))
+ return MAKE_FUNCTOR(report_t::fn_to_balance);
+ else if (is_eq(p, "to_string"))
+ return MAKE_FUNCTOR(report_t::fn_to_string);
+ else if (is_eq(p, "to_mask"))
+ return MAKE_FUNCTOR(report_t::fn_to_mask);
+ else if (is_eq(p, "to_sequence"))
+ return MAKE_FUNCTOR(report_t::fn_to_sequence);
+ break;
+
+ case 'T':
+ if (is_eq(p, "T"))
+ return MAKE_FUNCTOR(report_t::fn_display_total);
+ break;
+
+ case 'u':
+ if (is_eq(p, "underline"))
+ return WRAP_FUNCTOR(fn_underline);
+ else if (is_eq(p, "unrounded"))
+ return MAKE_FUNCTOR(report_t::fn_unrounded);
+ break;
+
+ case 'w':
+ if (is_eq(p, "white"))
+ return WRAP_FUNCTOR(fn_white);
+ break;
+
+ case 'y':
+ if (is_eq(p, "yellow"))
+ return WRAP_FUNCTOR(fn_yellow);
+ break;
+ }
+
+ // Check if they are trying to access an option's setting or value.
+ if (option_t<report_t> * handler = lookup_option(p))
+ return MAKE_OPT_FUNCTOR(report_t, handler);
+ break;
+
+ case symbol_t::OPTION:
+ if (option_t<report_t> * handler = lookup_option(p))
+ return MAKE_OPT_HANDLER(report_t, handler);
+ break;
+
+ case symbol_t::COMMAND:
+ switch (*p) {
+ case 'b':
+ if (*(p + 1) == '\0' || is_eq(p, "bal") || is_eq(p, "balance")) {
+ return expr_t::op_t::wrap_functor
+ (reporter<account_t, acct_handler_ptr, &report_t::accounts_report>
+ (new format_accounts(*this, report_format(HANDLER(balance_format_)),
+ maybe_format(HANDLER(prepend_format_))),
+ *this, "#balance"));
+ }
+ else if (is_eq(p, "budget")) {
+ HANDLER(amount_).set_expr(string("#budget"), "(amount, 0)");
+
+ budget_flags |= BUDGET_WRAP_VALUES;
+ if (! (budget_flags & ~BUDGET_WRAP_VALUES))
+ budget_flags |= BUDGET_BUDGETED;
+
+ return expr_t::op_t::wrap_functor
+ (reporter<account_t, acct_handler_ptr, &report_t::accounts_report>
+ (new format_accounts(*this, report_format(HANDLER(budget_format_)),
+ maybe_format(HANDLER(prepend_format_))),
+ *this, "#budget"));
+ }
+ break;
+
+ case 'c':
+ if (is_eq(p, "csv")) {
+ return WRAP_FUNCTOR
+ (reporter<>
+ (new format_posts(*this, report_format(HANDLER(csv_format_)),
+ maybe_format(HANDLER(prepend_format_))),
+ *this, "#csv"));
+ }
+ else if (is_eq(p, "cleared")) {
+ HANDLER(amount_).set_expr(string("#cleared"),
+ "(amount, cleared ? amount : 0)");
+
+ return expr_t::op_t::wrap_functor
+ (reporter<account_t, acct_handler_ptr, &report_t::accounts_report>
+ (new format_accounts(*this, report_format(HANDLER(cleared_format_)),
+ maybe_format(HANDLER(prepend_format_))),
+ *this, "#cleared"));
+ }
+ break;
+
+ case 'e':
+ if (is_eq(p, "equity"))
+ return WRAP_FUNCTOR
+ (reporter<>
+ (new format_posts(*this, report_format(HANDLER(print_format_))),
+ *this, "#equity"));
+ else if (is_eq(p, "entry"))
+ return WRAP_FUNCTOR(xact_command);
+ else if (is_eq(p, "emacs"))
+ return WRAP_FUNCTOR
+ (reporter<>(new format_emacs_posts(output_stream), *this, "#emacs"));
+ else if (is_eq(p, "echo"))
+ return MAKE_FUNCTOR(report_t::echo_command);
+ break;
+
+ case 'p':
+ if (*(p + 1) == '\0' || is_eq(p, "print"))
+ return WRAP_FUNCTOR
+ (reporter<>
+ (new format_posts(*this, report_format(HANDLER(print_format_)),
+ HANDLED(raw)), *this, "#print"));
+ else if (is_eq(p, "prices"))
+ return expr_t::op_t::wrap_functor
+ (reporter<post_t, post_handler_ptr, &report_t::commodities_report>
+ (new format_posts(*this, report_format(HANDLER(prices_format_)),
+ maybe_format(HANDLER(prepend_format_))),
+ *this, "#prices"));
+ else if (is_eq(p, "pricedb"))
+ return expr_t::op_t::wrap_functor
+ (reporter<post_t, post_handler_ptr, &report_t::commodities_report>
+ (new format_posts(*this, report_format(HANDLER(pricedb_format_)),
+ maybe_format(HANDLER(prepend_format_))),
+ *this, "#pricedb"));
+ break;
+
+ case 'r':
+ if (*(p + 1) == '\0' || is_eq(p, "reg") || is_eq(p, "register"))
+ return WRAP_FUNCTOR
+ (reporter<>
+ (new format_posts(*this, report_format(HANDLER(register_format_)),
+ false, maybe_format(HANDLER(prepend_format_))),
+ *this, "#register"));
+ else if (is_eq(p, "reload"))
+ return MAKE_FUNCTOR(report_t::reload_command);
+ break;
+
+ case 's':
+ if (is_eq(p, "stats") || is_eq(p, "stat"))
+ return WRAP_FUNCTOR(report_statistics);
+ break;
+
+ case 'x':
+ if (is_eq(p, "xact"))
+ return WRAP_FUNCTOR(xact_command);
+ else if (is_eq(p, "xml"))
+ return WRAP_FUNCTOR(reporter<>(new format_xml(*this), *this, "#xml"));
+ break;
+ }
+ break;
+
+ case symbol_t::PRECOMMAND:
+ switch (*p) {
+ case 'a':
+ if (is_eq(p, "args"))
+ return WRAP_FUNCTOR(args_command);
+ break;
+ case 'e':
+ if (is_eq(p, "eval"))
+ return WRAP_FUNCTOR(eval_command);
+ break;
+ case 'f':
+ if (is_eq(p, "format"))
+ return WRAP_FUNCTOR(format_command);
+ break;
+ case 'g':
+ if (is_eq(p, "generate"))
+ return expr_t::op_t::wrap_functor
+ (reporter<post_t, post_handler_ptr, &report_t::generate_report>
+ (new format_posts(*this, report_format(HANDLER(print_format_)),
+ false), *this, "#generate"));
+ case 'p':
+ if (is_eq(p, "parse"))
+ return WRAP_FUNCTOR(parse_command);
+ else if (is_eq(p, "period"))
+ return WRAP_FUNCTOR(period_command);
+ break;
+ case 't':
+ if (is_eq(p, "template"))
+ return WRAP_FUNCTOR(template_command);
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+} // namespace ledger
diff --git a/src/report.h b/src/report.h
new file mode 100644
index 00000000..94d39215
--- /dev/null
+++ b/src/report.h
@@ -0,0 +1,958 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup report
+ */
+
+/**
+ * @file report.h
+ * @author John Wiegley
+ *
+ * @ingroup report
+ */
+#ifndef _REPORT_H
+#define _REPORT_H
+
+#include "interactive.h"
+#include "expr.h"
+#include "query.h"
+#include "chain.h"
+#include "stream.h"
+#include "option.h"
+#include "commodity.h"
+#include "annotate.h"
+#include "session.h"
+#include "format.h"
+
+namespace ledger {
+
+class session_t;
+class xact_t;
+
+// 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. Posting or commodity iteration. In this mode, all the journal's
+// xacts, the postings of a specific xact, or all the journal's
+// commodities are walked. In the first two cases, it's the underlying
+// postings 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
+// posting 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 postings of an xact: walk_postings.
+// b. The xacts of a journal: walk_xacts.
+// 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 scope_t
+{
+ report_t();
+
+public:
+ session_t& session;
+ output_stream_t output_stream;
+
+#define BUDGET_NO_BUDGET 0x00
+#define BUDGET_BUDGETED 0x01
+#define BUDGET_UNBUDGETED 0x02
+#define BUDGET_WRAP_VALUES 0x04
+
+ datetime_t terminus;
+ uint_least8_t budget_flags;
+
+ explicit report_t(session_t& _session)
+ : session(_session), terminus(CURRENT_TIME()),
+ budget_flags(BUDGET_NO_BUDGET) {}
+
+ virtual ~report_t() {
+ output_stream.close();
+ }
+
+ void normalize_options(const string& verb);
+ void parse_query_args(const value_t& args, const string& whence);
+
+ void posts_report(post_handler_ptr handler);
+ void generate_report(post_handler_ptr handler);
+ void xact_report(post_handler_ptr handler, xact_t& xact);
+ void accounts_report(acct_handler_ptr handler);
+ void commodities_report(post_handler_ptr handler);
+
+ value_t fn_amount_expr(call_scope_t& scope);
+ value_t fn_total_expr(call_scope_t& scope);
+ value_t fn_display_amount(call_scope_t& scope);
+ value_t fn_display_total(call_scope_t& scope);
+ value_t fn_market(call_scope_t& scope);
+ value_t fn_get_at(call_scope_t& scope);
+ value_t fn_is_seq(call_scope_t& scope);
+ value_t fn_strip(call_scope_t& scope);
+ value_t fn_trim(call_scope_t& scope);
+ value_t fn_scrub(call_scope_t& scope);
+ value_t fn_quantity(call_scope_t& scope);
+ value_t fn_rounded(call_scope_t& scope);
+ value_t fn_unrounded(call_scope_t& scope);
+ value_t fn_truncated(call_scope_t& scope);
+ value_t fn_floor(call_scope_t& scope);
+ value_t fn_abs(call_scope_t& scope);
+ value_t fn_justify(call_scope_t& scope);
+ value_t fn_quoted(call_scope_t& scope);
+ value_t fn_join(call_scope_t& scope);
+ value_t fn_format_date(call_scope_t& scope);
+ value_t fn_ansify_if(call_scope_t& scope);
+ value_t fn_percent(call_scope_t& scope);
+ value_t fn_price(call_scope_t& scope);
+ value_t fn_lot_date(call_scope_t& scope);
+ value_t fn_lot_price(call_scope_t& scope);
+ value_t fn_lot_tag(call_scope_t& scope);
+ value_t fn_to_boolean(call_scope_t& scope);
+ value_t fn_to_int(call_scope_t& scope);
+ value_t fn_to_datetime(call_scope_t& scope);
+ value_t fn_to_date(call_scope_t& scope);
+ value_t fn_to_amount(call_scope_t& scope);
+ value_t fn_to_balance(call_scope_t& scope);
+ value_t fn_to_string(call_scope_t& scope);
+ value_t fn_to_mask(call_scope_t& scope);
+ value_t fn_to_sequence(call_scope_t& scope);
+
+ value_t fn_now(call_scope_t&) {
+ return terminus;
+ }
+ value_t fn_today(call_scope_t&) {
+ return terminus.date();
+ }
+
+ value_t fn_options(call_scope_t&) {
+ return value_t(static_cast<scope_t *>(this));
+ }
+
+ string report_format(option_t<report_t>& option) {
+ if (HANDLED(format_))
+ return HANDLER(format_).str();
+ return option.str();
+ }
+
+ optional<string> maybe_format(option_t<report_t>& option) {
+ if (option)
+ return option.str();
+ return none;
+ }
+
+ value_t reload_command(call_scope_t&);
+ value_t echo_command(call_scope_t& scope);
+
+ keep_details_t what_to_keep() {
+ bool lots = HANDLED(lots) || HANDLED(lots_actual);
+ return keep_details_t(lots || HANDLED(lot_prices),
+ lots || HANDLED(lot_dates),
+ lots || HANDLED(lot_tags),
+ HANDLED(lots_actual));
+ }
+
+ void report_options(std::ostream& out)
+ {
+ HANDLER(abbrev_len_).report(out);
+ HANDLER(account_).report(out);
+ HANDLER(actual).report(out);
+ HANDLER(actual_dates).report(out);
+ HANDLER(add_budget).report(out);
+ HANDLER(amount_).report(out);
+ HANDLER(amount_data).report(out);
+ HANDLER(anon).report(out);
+ HANDLER(average).report(out);
+ HANDLER(balance_format_).report(out);
+ HANDLER(base).report(out);
+ HANDLER(basis).report(out);
+ HANDLER(begin_).report(out);
+ HANDLER(budget).report(out);
+ HANDLER(budget_format_).report(out);
+ HANDLER(by_payee).report(out);
+ HANDLER(cleared).report(out);
+ HANDLER(cleared_format_).report(out);
+ HANDLER(color).report(out);
+ HANDLER(collapse).report(out);
+ HANDLER(collapse_if_zero).report(out);
+ HANDLER(columns_).report(out);
+ HANDLER(csv_format_).report(out);
+ HANDLER(current).report(out);
+ HANDLER(daily).report(out);
+ HANDLER(date_).report(out);
+ HANDLER(date_format_).report(out);
+ HANDLER(datetime_format_).report(out);
+ HANDLER(depth_).report(out);
+ HANDLER(deviation).report(out);
+ HANDLER(display_).report(out);
+ HANDLER(display_amount_).report(out);
+ HANDLER(display_total_).report(out);
+ HANDLER(dow).report(out);
+ HANDLER(effective).report(out);
+ HANDLER(empty).report(out);
+ HANDLER(end_).report(out);
+ HANDLER(equity).report(out);
+ HANDLER(exact).report(out);
+ HANDLER(exchange_).report(out);
+ HANDLER(flat).report(out);
+ HANDLER(force_color).report(out);
+ HANDLER(force_pager).report(out);
+ HANDLER(forecast_while_).report(out);
+ HANDLER(forecast_years_).report(out);
+ HANDLER(format_).report(out);
+ HANDLER(gain).report(out);
+ HANDLER(head_).report(out);
+ HANDLER(invert).report(out);
+ HANDLER(limit_).report(out);
+ HANDLER(lot_dates).report(out);
+ HANDLER(lot_prices).report(out);
+ HANDLER(lot_tags).report(out);
+ HANDLER(lots).report(out);
+ HANDLER(lots_actual).report(out);
+ HANDLER(market).report(out);
+ HANDLER(monthly).report(out);
+ HANDLER(no_total).report(out);
+ HANDLER(now_).report(out);
+ HANDLER(only_).report(out);
+ HANDLER(output_).report(out);
+ HANDLER(pager_).report(out);
+ HANDLER(payee_).report(out);
+ HANDLER(pending).report(out);
+ HANDLER(percent).report(out);
+ HANDLER(period_).report(out);
+ HANDLER(plot_amount_format_).report(out);
+ HANDLER(plot_total_format_).report(out);
+ HANDLER(prepend_format_).report(out);
+ HANDLER(price).report(out);
+ HANDLER(prices_format_).report(out);
+ HANDLER(pricedb_format_).report(out);
+ HANDLER(print_format_).report(out);
+ HANDLER(quantity).report(out);
+ HANDLER(quarterly).report(out);
+ HANDLER(raw).report(out);
+ HANDLER(real).report(out);
+ HANDLER(register_format_).report(out);
+ HANDLER(related).report(out);
+ HANDLER(related_all).report(out);
+ HANDLER(revalued).report(out);
+ HANDLER(revalued_only).report(out);
+ HANDLER(revalued_total_).report(out);
+ HANDLER(seed_).report(out);
+ HANDLER(sort_).report(out);
+ HANDLER(sort_all_).report(out);
+ HANDLER(sort_xacts_).report(out);
+ HANDLER(start_of_week_).report(out);
+ HANDLER(subtotal).report(out);
+ HANDLER(tail_).report(out);
+ HANDLER(total_).report(out);
+ HANDLER(total_data).report(out);
+ HANDLER(truncate_).report(out);
+ HANDLER(unbudgeted).report(out);
+ HANDLER(uncleared).report(out);
+ HANDLER(unrealized).report(out);
+ HANDLER(unrealized_gains_).report(out);
+ HANDLER(unrealized_losses_).report(out);
+ HANDLER(unround).report(out);
+ HANDLER(unsorted).report(out);
+ HANDLER(weekly).report(out);
+ HANDLER(wide).report(out);
+ HANDLER(yearly).report(out);
+ HANDLER(date_width_).report(out);
+ HANDLER(payee_width_).report(out);
+ HANDLER(account_width_).report(out);
+ HANDLER(amount_width_).report(out);
+ HANDLER(total_width_).report(out);
+ }
+
+ option_t<report_t> * lookup_option(const char * p);
+
+ virtual void define(const symbol_t::kind_t kind, const string& name,
+ expr_t::ptr_op_t def);
+
+ virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind,
+ const string& name);
+
+ /**
+ * Option handlers
+ */
+
+ OPTION__(report_t, abbrev_len_,
+ CTOR(report_t, abbrev_len_) { on_with(none, 2L); });
+ OPTION(report_t, account_);
+
+ OPTION_(report_t, actual, DO() { // -L
+ parent->HANDLER(limit_).on(string("--actual"), "actual");
+ });
+
+ OPTION(report_t, actual_dates);
+
+ OPTION_(report_t, add_budget, DO() {
+ parent->budget_flags |= BUDGET_BUDGETED | BUDGET_UNBUDGETED;
+ });
+
+ OPTION__
+ (report_t, amount_, // -t
+ expr_t expr;
+ CTOR(report_t, amount_) {
+ set_expr(none, "amount");
+ }
+ void set_expr(const optional<string>& whence, const string& str) {
+ expr = str;
+ on(whence, str);
+ }
+ DO_(args) {
+ set_expr(args[0].to_string(), args[1].to_string());
+ });
+
+ OPTION(report_t, amount_data); // -j
+ OPTION(report_t, anon);
+
+ OPTION_(report_t, average, DO() { // -A
+ parent->HANDLER(display_total_)
+ .set_expr(string("--average"), "total_expr/count");
+ });
+
+ OPTION__(report_t, balance_format_, CTOR(report_t, balance_format_) {
+ on(none,
+ "%(justify(scrub(display_total), 20, -1, true, color))"
+ " %(!options.flat ? depth_spacer : \"\")"
+ "%-(ansify_if(partial_account(options.flat), blue if color))\n%/"
+ "%$1\n%/"
+ "--------------------\n");
+ });
+
+ OPTION(report_t, base);
+
+ OPTION_(report_t, basis, DO() { // -B
+ parent->HANDLER(revalued).on_only(string("--basis"));
+ parent->HANDLER(amount_).set_expr(string("--basis"), "rounded(cost)");
+ });
+
+ OPTION_(report_t, begin_, DO_(args) { // -b
+ date_interval_t interval(args[1].to_string());
+ optional<date_t> begin = interval.begin(parent->session.current_year);
+ if (! begin)
+ throw_(std::invalid_argument,
+ _("Could not determine beginning of period '%1'")
+ << args[1].to_string());
+
+ string predicate = "date>=[" + to_iso_extended_string(*begin) + "]";
+ parent->HANDLER(limit_).on(string("--begin"), predicate);
+ });
+
+ OPTION_(report_t, budget, DO() {
+ parent->budget_flags |= BUDGET_BUDGETED;
+ });
+
+ OPTION__(report_t, budget_format_, CTOR(report_t, budget_format_) {
+ on(none,
+ "%(justify(scrub(get_at(total_expr, 0)), 12, -1, true, color))"
+ " %(justify(scrub(- get_at(total_expr, 1)), 12, "
+ " 12 + 1 + 12, true, color))"
+ " %(justify(scrub(get_at(total_expr, 1) + "
+ " get_at(total_expr, 0)), 12, "
+ " 12 + 1 + 12 + 1 + 12, true, color))"
+ " %(ansify_if("
+ " justify((get_at(total_expr, 1) ? "
+ " scrub((100% * get_at(total_expr, 0)) / "
+ " - get_at(total_expr, 1)) : 0), "
+ " 5, -1, true, false),"
+ " magenta if (color and get_at(total_expr, 1) and "
+ " (abs(quantity(get_at(total_expr, 0)) / "
+ " quantity(get_at(total_expr, 1))) >= 1))))"
+ " %(!options.flat ? depth_spacer : \"\")"
+ "%-(ansify_if(partial_account(options.flat), blue if color))\n"
+ "%/%$1 %$2 %$3 %$4\n%/"
+ "------------ ------------ ------------ -----\n");
+ });
+
+ OPTION(report_t, by_payee); // -P
+
+ OPTION_(report_t, cleared, DO() { // -C
+ parent->HANDLER(limit_).on(string("--cleared"), "cleared");
+ });
+
+ OPTION__(report_t, cleared_format_, CTOR(report_t, cleared_format_) {
+ on(none,
+ "%(justify(scrub(get_at(total_expr, 0)), 16, -1, true, color))"
+ " %(justify(scrub(get_at(total_expr, 1)), 16, -1, true, color))"
+ " %(latest_cleared ? format_date(latest_cleared) : \" \")"
+ " %(!options.flat ? depth_spacer : \"\")"
+ "%-(ansify_if(partial_account(options.flat), blue if color))\n%/"
+ "%$1 %$2 %$3\n%/"
+ "---------------- ---------------- ---------\n");
+ });
+
+ OPTION(report_t, color);
+
+ OPTION_(report_t, collapse, DO() { // -n
+ // Make sure that balance reports are collapsed too, but only apply it
+ // to account xacts
+ parent->HANDLER(display_).on(string("--collapse"), "post|depth<=1");
+ });
+
+ OPTION_(report_t, collapse_if_zero, DO() {
+ parent->HANDLER(collapse).on_only(string("--collapse-if-zero"));
+ });
+
+ OPTION(report_t, columns_);
+
+ OPTION__(report_t, csv_format_, CTOR(report_t, csv_format_) {
+ on(none,
+ "%(quoted(date)),"
+ "%(quoted(payee)),"
+ "%(quoted(account)),"
+ "%(quoted(scrub(display_amount))),"
+ "%(quoted(cleared ? \"*\" : (pending ? \"!\" : \"\"))),"
+ "%(quoted(code)),"
+ "%(quoted(join(note | xact.note)))\n");
+ });
+
+ OPTION_(report_t, current, DO() { // -c
+ parent->HANDLER(limit_).on(string("--current"), "date<=today");
+ });
+
+ OPTION_(report_t, daily, DO() { // -D
+ parent->HANDLER(period_).on(string("--daily"), "daily");
+ });
+
+ OPTION(report_t, date_);
+ OPTION(report_t, date_format_);
+ OPTION(report_t, datetime_format_);
+
+ OPTION_(report_t, depth_, DO_(scope) {
+ interactive_t args(scope, "sl");
+ parent->HANDLER(display_).on(string("--depth"),
+ string("depth<=") + args.get<string>(1));
+ });
+
+ OPTION_(report_t, deviation, DO() {
+ parent->HANDLER(display_total_)
+ .set_expr(string("--deviation"), "amount_expr-total_expr/count");
+ });
+
+ OPTION__
+ (report_t, display_, // -d
+ CTOR(report_t, display_) {}
+ virtual void on_with(const optional<string>& whence, const value_t& text) {
+ if (! handled)
+ option_t<report_t>::on_with(whence, text);
+ else
+ option_t<report_t>::on_with(whence,
+ string_value(string("(") + str() + ")&(" +
+ text.as_string() + ")"));
+ });
+
+ OPTION__
+ (report_t, display_amount_,
+ expr_t expr;
+ CTOR(report_t, display_amount_) {
+ set_expr(none, "amount_expr");
+ }
+ void set_expr(const optional<string>& whence, const string& str) {
+ expr = str;
+ on(whence, str);
+ }
+ DO_(args) {
+ set_expr(args[0].to_string(), args[1].to_string());
+ });
+
+ OPTION__
+ (report_t, display_total_,
+ expr_t expr;
+ CTOR(report_t, display_total_) {
+ set_expr(none, "total_expr");
+ }
+ void set_expr(const optional<string>& whence, const string& str) {
+ expr = str;
+ on(whence, str);
+ }
+ DO_(args) {
+ set_expr(args[0].to_string(), args[1].to_string());
+ });
+
+ OPTION(report_t, dow);
+ OPTION(report_t, effective);
+ OPTION(report_t, empty); // -E
+
+ OPTION_(report_t, end_, DO_(args) { // -e
+ date_interval_t interval(args[1].to_string());
+ // Use begin() here so that if the user says --end=2008, we end on
+ // 2008/01/01 instead of 2009/01/01 (which is what end() would return).
+ optional<date_t> end = interval.begin(parent->session.current_year);
+ if (! end)
+ throw_(std::invalid_argument,
+ _("Could not determine end of period '%1'")
+ << args[1].to_string());
+
+ string predicate = "date<[" + to_iso_extended_string(*end) + "]";
+ parent->HANDLER(limit_).on(string("--end"), predicate);
+
+ parent->terminus = datetime_t(*end);
+ });
+
+ OPTION(report_t, equity);
+ OPTION(report_t, exact);
+
+ OPTION_(report_t, exchange_, DO_(args) { // -X
+ on_with(args[0].as_string(), args[1]);
+ call_scope_t no_args(*parent);
+ no_args.push_back(args[0]);
+ parent->HANDLER(market).parent = parent;
+ parent->HANDLER(market).handler(no_args);
+ });
+
+ OPTION(report_t, flat);
+ OPTION(report_t, force_color);
+ OPTION(report_t, force_pager);
+ OPTION(report_t, forecast_while_);
+ OPTION(report_t, forecast_years_);
+ OPTION(report_t, format_); // -F
+
+ OPTION_(report_t, gain, DO() { // -G
+ parent->HANDLER(revalued).on_only(string("--gain"));
+ parent->HANDLER(amount_).set_expr(string("--gain"), "(amount, cost)");
+ // Since we are displaying the amounts of revalued postings, they
+ // will end up being composite totals, and hence a pair of pairs.
+ parent->HANDLER(display_amount_)
+ .set_expr(string("--gain"),
+ "use_direct_amount ? amount :"
+ " (is_seq(get_at(amount_expr, 0)) ?"
+ " get_at(get_at(amount_expr, 0), 0) :"
+ " market(get_at(amount_expr, 0), date, exchange)"
+ " - get_at(amount_expr, 1))");
+ parent->HANDLER(revalued_total_)
+ .set_expr(string("--gain"),
+ "(market(get_at(total_expr, 0), date, exchange), "
+ "get_at(total_expr, 1))");
+ parent->HANDLER(display_total_)
+ .set_expr(string("--gain"),
+ "use_direct_amount ? total_expr :"
+ " market(get_at(total_expr, 0), date, exchange)"
+ " - get_at(total_expr, 1)");
+ });
+
+ OPTION(report_t, head_);
+
+ OPTION_(report_t, invert, DO() {
+ parent->HANDLER(amount_).set_expr(string("--invert"), "-amount");
+ });
+
+ OPTION__
+ (report_t, limit_, // -l
+ CTOR(report_t, limit_) {}
+ virtual void on_with(const optional<string>& whence, const value_t& text) {
+ if (! handled)
+ option_t<report_t>::on_with(whence, text);
+ else
+ option_t<report_t>::on_with(whence,
+ string_value(string("(") + str() + ")&(" +
+ text.as_string() + ")"));
+ });
+
+ OPTION(report_t, lot_dates);
+ OPTION(report_t, lot_prices);
+ OPTION(report_t, lot_tags);
+ OPTION(report_t, lots);
+ OPTION(report_t, lots_actual);
+
+ OPTION_(report_t, market, DO() { // -V
+ parent->HANDLER(revalued).on_only(string("--market"));
+ parent->HANDLER(display_amount_)
+ .set_expr(string("--market"), "market(amount_expr, date, exchange)");
+ parent->HANDLER(display_total_)
+ .set_expr(string("--market"), "market(total_expr, date, exchange)");
+ });
+
+ OPTION_(report_t, monthly, DO() { // -M
+ parent->HANDLER(period_).on(string("--monthly"), "monthly");
+ });
+
+ OPTION_(report_t, no_color, DO() {
+ parent->HANDLER(color).off();
+ });
+
+ OPTION(report_t, no_total);
+
+ OPTION_(report_t, now_, DO_(args) {
+ date_interval_t interval(args[1].to_string());
+ optional<date_t> begin = interval.begin(parent->session.current_year);
+ if (! begin)
+ throw_(std::invalid_argument,
+ _("Could not determine beginning of period '%1'")
+ << args[1].to_string());
+ ledger::epoch = parent->terminus = datetime_t(*begin);
+ parent->session.current_year = ledger::epoch->date().year();
+ });
+
+ OPTION__
+ (report_t, only_,
+ CTOR(report_t, only_) {}
+ virtual void on_with(const optional<string>& whence, const value_t& text) {
+ if (! handled)
+ option_t<report_t>::on_with(whence, text);
+ else
+ option_t<report_t>::on_with(whence,
+ string_value(string("(") + str() + ")&(" +
+ text.as_string() + ")"));
+ });
+
+ OPTION(report_t, output_); // -o
+
+#ifdef HAVE_ISATTY
+ OPTION__
+ (report_t, pager_,
+ CTOR(report_t, pager_) {
+ if (! std::getenv("PAGER") && isatty(STDOUT_FILENO)) {
+ bool have_less = false;
+ if (exists(path("/opt/local/bin/less")) ||
+ exists(path("/usr/local/bin/less")) ||
+ exists(path("/usr/bin/less")))
+ have_less = true;
+
+ if (have_less) {
+ on(none, "less");
+ setenv("LESS", "-FRSX", 0); // don't overwrite
+ }
+ }
+ }
+ virtual void on_with(const optional<string>& whence, const value_t& text) {
+ string cmd(text.to_string());
+ if (cmd == "" || cmd == "false" || cmd == "off" ||
+ cmd == "none" || cmd == "no" || cmd == "disable")
+ option_t<report_t>::off();
+ else
+ option_t<report_t>::on_with(whence, text);
+ });
+#else // HAVE_ISATTY
+ OPTION__
+ (report_t, pager_,
+ CTOR(report_t, pager_) {
+ }
+ virtual void on_with(const optional<string>& whence, const value_t& text) {
+ string cmd(text.to_string());
+ if (cmd == "" || cmd == "false" || cmd == "off" ||
+ cmd == "none" || cmd == "no" || cmd == "disable")
+ option_t<report_t>::off();
+ else
+ option_t<report_t>::on_with(whence, text);
+ });
+#endif // HAVE_ISATTY
+
+ OPTION(report_t, payee_);
+
+ OPTION_(report_t, pending, DO() { // -C
+ parent->HANDLER(limit_).on(string("--pending"), "pending");
+ });
+
+ OPTION_(report_t, percent, DO() { // -%
+ parent->HANDLER(total_)
+ .set_expr(string("--percent"),
+ "is_account&parent&parent.total&percent(total, parent.total)");
+ });
+
+ OPTION__
+ (report_t, period_, // -p
+ CTOR(report_t, period_) {}
+ virtual void on_with(const optional<string>& whence, const value_t& text) {
+ if (! handled)
+ option_t<report_t>::on_with(whence, text);
+ else
+ option_t<report_t>::on_with(whence,
+ string_value(text.as_string() + " " + str()));
+ });
+
+ OPTION__(report_t, plot_amount_format_, CTOR(report_t, plot_amount_format_) {
+ on(none,
+ "%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_amount)))\n");
+ });
+
+ OPTION__(report_t, plot_total_format_, CTOR(report_t, plot_total_format_) {
+ on(none,
+ "%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_total)))\n");
+ });
+
+ OPTION(report_t, prepend_format_);
+
+ OPTION_(report_t, price, DO() { // -I
+ parent->HANDLER(display_amount_)
+ .set_expr(string("--price"), "price(amount_expr)");
+ parent->HANDLER(display_total_)
+ .set_expr(string("--price"), "price(total_expr)");
+ });
+
+ OPTION__(report_t, prices_format_, CTOR(report_t, prices_format_) {
+ on(none,
+ "%(date) %-8(account) %(justify(scrub(display_amount), 12, "
+ " 2 + 9 + 8 + 12, true, color))\n");
+ });
+
+ OPTION__(report_t, pricedb_format_, CTOR(report_t, pricedb_format_) {
+ on(none,
+ "P %(datetime) %(account) %(scrub(display_amount))\n");
+ });
+
+ OPTION__(report_t, print_format_, CTOR(report_t, print_format_) {
+ on(none,
+ "%(xact.date)"
+ "%(!effective & xact.effective_date ?"
+ " \"=\" + xact.effective_date : \"\")"
+ "%(xact.cleared ? \" *\" : (xact.pending ? \" !\" : \"\"))"
+ "%(code ? \" (\" + code + \")\" :"
+ " \"\") %(payee)%(xact.comment)\n"
+ " %(xact.uncleared ?"
+ " (cleared ? \"* \" : (pending ? \"! \" : \"\")) : \"\")"
+ "%(calculated ? account : justify(account, 34, -1, false))"
+ "%(calculated ? \"\" : \" \" + justify(scrub(amount), 12, -1, true))"
+ "%(has_cost & !cost_calculated ?"
+ " \" @ \" + justify(scrub(abs(cost / amount)), 0) : \"\")"
+ "%(comment)\n%/"
+ " %$7%$8%$9%$A%$B\n%/\n");
+ });
+
+ OPTION_(report_t, quantity, DO() { // -O
+ parent->HANDLER(revalued).off();
+ parent->HANDLER(amount_).set_expr(string("--quantity"), "amount");
+ parent->HANDLER(total_).set_expr(string("--quantity"), "total");
+ });
+
+ OPTION_(report_t, quarterly, DO() {
+ parent->HANDLER(period_).on(string("--quarterly"), "quarterly");
+ });
+
+ OPTION(report_t, raw);
+
+ OPTION_(report_t, real, DO() { // -R
+ parent->HANDLER(limit_).on(string("--real"), "real");
+ });
+
+ OPTION__(report_t, register_format_, CTOR(report_t, register_format_) {
+ on(none,
+ "%(ansify_if(justify(format_date(date), date_width), green "
+ " if color & date > today))"
+ " %(ansify_if(justify(truncated(payee, payee_width), payee_width), "
+ " bold if color & !cleared & actual))"
+ " %(ansify_if(justify(truncated(account, account_width, abbrev_len), "
+ " account_width), blue if color))"
+ " %(justify(scrub(display_amount), amount_width, "
+ " 3 + date_width + payee_width + account_width + amount_width, "
+ " true, color))"
+ " %(justify(scrub(display_total), total_width, "
+ " 4 + date_width + payee_width + account_width + amount_width "
+ " + total_width, true, color))\n%/"
+ "%(justify(\" \", 2 + date_width + payee_width))%$3 %$4 %$5\n");
+ });
+
+ OPTION(report_t, related); // -r
+
+ OPTION_(report_t, related_all, DO() {
+ parent->HANDLER(related).on_only(string("--related-all"));
+ });
+
+ OPTION(report_t, revalued);
+ OPTION(report_t, revalued_only);
+
+ OPTION__
+ (report_t, revalued_total_,
+ expr_t expr;
+ CTOR(report_t, revalued_total_) {}
+ void set_expr(const optional<string>& whence, const string& str) {
+ expr = str;
+ on(whence, str);
+ }
+ DO_(args) {
+ set_expr(args[0].to_string(), args[1].to_string());
+ });
+
+ OPTION(report_t, seed_);
+
+ OPTION_(report_t, sort_, DO_(args) { // -S
+ on_with(args[0].as_string(), args[1]);
+ parent->HANDLER(sort_xacts_).off();
+ parent->HANDLER(sort_all_).off();
+ });
+
+ OPTION_(report_t, sort_all_, DO_(args) {
+ parent->HANDLER(sort_).on_with(string("--sort-all"), args[1]);
+ parent->HANDLER(sort_xacts_).off();
+ });
+
+ OPTION_(report_t, sort_xacts_, DO_(args) {
+ parent->HANDLER(sort_).on_with(string("--sort-xacts"), args[1]);
+ parent->HANDLER(sort_all_).off();
+ });
+
+ OPTION(report_t, start_of_week_);
+ OPTION(report_t, subtotal); // -s
+ OPTION(report_t, tail_);
+
+ OPTION__
+ (report_t, total_, // -T
+ expr_t expr;
+ CTOR(report_t, total_) {
+ set_expr(none, "total");
+ }
+ void set_expr(const optional<string>& whence, const string& str) {
+ expr = str;
+ on(whence, str);
+ }
+ DO_(args) {
+ set_expr(args[0].to_string(), args[1].to_string());
+ });
+
+ OPTION(report_t, total_data); // -J
+
+ OPTION_(report_t, truncate_, DO_(args) {
+ string style(args[1].to_string());
+ if (style == "leading")
+ format_t::default_style = format_t::TRUNCATE_LEADING;
+ else if (style == "middle")
+ format_t::default_style = format_t::TRUNCATE_MIDDLE;
+ else if (style == "trailing")
+ format_t::default_style = format_t::TRUNCATE_TRAILING;
+ else
+ throw_(std::invalid_argument,
+ _("Unrecognized truncation style: '%1'") << style);
+ format_t::default_style_changed = true;
+ });
+
+ OPTION_(report_t, unbudgeted, DO() {
+ parent->budget_flags |= BUDGET_UNBUDGETED;
+ });
+
+ OPTION_(report_t, uncleared, DO() { // -U
+ parent->HANDLER(limit_).on(string("--uncleared"), "uncleared|pending");
+ });
+
+ OPTION(report_t, unrealized);
+
+ OPTION(report_t, unrealized_gains_);
+ OPTION(report_t, unrealized_losses_);
+
+ OPTION_(report_t, unround, DO() {
+ parent->HANDLER(display_amount_)
+ .set_expr(string("--unround"), "unrounded(amount_expr)");
+ parent->HANDLER(display_total_)
+ .set_expr(string("--unround"), "unrounded(total_expr)");
+ });
+
+ OPTION(report_t, unsorted);
+
+ OPTION_(report_t, weekly, DO() { // -W
+ parent->HANDLER(period_).on(string("--weekly"), "weekly");
+ });
+
+ OPTION_(report_t, wide, DO() { // -w
+ parent->HANDLER(columns_).on_with(string("--wide"), 132L);
+ });
+
+ OPTION_(report_t, yearly, DO() { // -Y
+ parent->HANDLER(period_).on(string("--yearly"), "yearly");
+ });
+
+ OPTION__(report_t, date_width_,
+ bool specified;
+ CTOR(report_t, date_width_) { specified = false; }
+ DO_(args) { value = args[1].to_long(); specified = true; });
+ OPTION__(report_t, payee_width_,
+ bool specified;
+ CTOR(report_t, payee_width_) { specified = false; }
+ DO_(args) { value = args[1].to_long(); specified = true; });
+ OPTION__(report_t, account_width_,
+ bool specified;
+ CTOR(report_t, account_width_) { specified = false; }
+ DO_(args) { value = args[1].to_long(); specified = true; });
+ OPTION__(report_t, amount_width_,
+ bool specified;
+ CTOR(report_t, amount_width_) { specified = false; }
+ DO_(args) { value = args[1].to_long(); specified = true; });
+ OPTION__(report_t, total_width_,
+ bool specified;
+ CTOR(report_t, total_width_) { specified = false; }
+ DO_(args) { value = args[1].to_long(); specified = true; });
+};
+
+
+template <class Type = post_t,
+ class handler_ptr = post_handler_ptr,
+ void (report_t::*report_method)(handler_ptr) =
+ &report_t::posts_report>
+class reporter
+{
+ shared_ptr<item_handler<Type> > handler;
+
+ report_t& report;
+ string whence;
+
+public:
+ reporter(item_handler<Type> * _handler,
+ report_t& _report, const string& _whence)
+ : handler(_handler), report(_report), whence(_whence) {}
+
+ value_t operator()(call_scope_t& args)
+ {
+ if (args.size() > 0)
+ report.parse_query_args(args.value(), whence);
+
+ (report.*report_method)(handler_ptr(handler));
+
+ return true;
+ }
+};
+
+} // namespace ledger
+
+#endif // _REPORT_H
diff --git a/src/scope.cc b/src/scope.cc
new file mode 100644
index 00000000..64736ca3
--- /dev/null
+++ b/src/scope.cc
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "scope.h"
+
+namespace ledger {
+
+scope_t * scope_t::default_scope = NULL;
+
+void symbol_scope_t::define(const symbol_t::kind_t kind,
+ const string& name, expr_t::ptr_op_t def)
+{
+ DEBUG("scope.symbols", "Defining '" << name << "' = " << def);
+
+ if (! symbols)
+ symbols = symbol_map();
+
+ std::pair<symbol_map::iterator, bool> result
+ = symbols->insert(symbol_map::value_type(symbol_t(kind, name, def), def));
+ if (! result.second) {
+ symbol_map::iterator i = symbols->find(symbol_t(kind, name));
+ assert(i != symbols->end());
+ symbols->erase(i);
+
+ result = symbols->insert(symbol_map::value_type(symbol_t(kind, name, def),
+ def));
+ if (! result.second)
+ throw_(compile_error,
+ _("Redefinition of '%1' in the same scope") << name);
+ }
+}
+
+expr_t::ptr_op_t symbol_scope_t::lookup(const symbol_t::kind_t kind,
+ const string& name)
+{
+ if (symbols) {
+ symbol_map::const_iterator i = symbols->find(symbol_t(kind, name));
+ if (i != symbols->end())
+ return (*i).second;
+ }
+ return child_scope_t::lookup(kind, name);
+}
+
+} // namespace ledger
diff --git a/src/scope.h b/src/scope.h
new file mode 100644
index 00000000..93f80230
--- /dev/null
+++ b/src/scope.h
@@ -0,0 +1,348 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup expr
+ */
+
+/**
+ * @file scope.h
+ * @author John Wiegley
+ *
+ * @ingroup expr
+ */
+#ifndef _SCOPE_H
+#define _SCOPE_H
+
+#include "op.h"
+
+namespace ledger {
+
+struct symbol_t
+{
+ enum kind_t {
+ UNKNOWN,
+ FUNCTION,
+ OPTION,
+ PRECOMMAND,
+ COMMAND,
+ DIRECTIVE,
+ FORMAT
+ };
+
+ kind_t kind;
+ string name;
+ expr_t::ptr_op_t definition;
+
+ symbol_t() : kind(UNKNOWN), name(""), definition(NULL) {
+ TRACE_CTOR(symbol_t, "");
+ }
+ symbol_t(kind_t _kind, string _name, expr_t::ptr_op_t _definition = NULL)
+ : kind(_kind), name(_name), definition(_definition) {
+ TRACE_CTOR(symbol_t, "symbol_t::kind_t, string");
+ }
+ symbol_t(const symbol_t& sym)
+ : kind(sym.kind), name(sym.name),
+ definition(sym.definition) {
+ TRACE_CTOR(symbol_t, "copy");
+ }
+ ~symbol_t() throw() {
+ TRACE_DTOR(symbol_t);
+ }
+
+ bool operator<(const symbol_t& sym) const {
+ return kind < sym.kind || name < sym.name;
+ }
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & kind;
+ ar & name;
+ ar & definition;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+class scope_t
+{
+public:
+ static scope_t * default_scope;
+
+ explicit scope_t() {
+ TRACE_CTOR(scope_t, "");
+ }
+ virtual ~scope_t() {
+ TRACE_DTOR(scope_t);
+ }
+
+ virtual void define(const symbol_t::kind_t, const string&,
+ expr_t::ptr_op_t) {}
+ virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind,
+ const string& name) = 0;
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive&, const unsigned int /* version */) {}
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+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 void define(const symbol_t::kind_t kind,
+ const string& name, expr_t::ptr_op_t def) {
+ if (parent)
+ parent->define(kind, name, def);
+ }
+
+ virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind,
+ const string& name) {
+ if (parent)
+ return parent->lookup(kind, name);
+ return NULL;
+ }
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & boost::serialization::base_object<scope_t>(*this);
+ ar & parent;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+class symbol_scope_t : public child_scope_t
+{
+ typedef std::map<symbol_t, expr_t::ptr_op_t> symbol_map;
+
+ optional<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);
+ }
+
+ virtual void define(const symbol_t::kind_t kind, const string& name,
+ expr_t::ptr_op_t def);
+
+ virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind,
+ const string& name);
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & boost::serialization::base_object<child_scope_t>(*this);
+ ar & symbols;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+class call_scope_t : public child_scope_t
+{
+ value_t args;
+
+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 std::size_t index) {
+ return args[index];
+ }
+ const value_t& operator[](const std::size_t index) const {
+ return args[index];
+ }
+
+ void push_front(const value_t& val) {
+ args.push_front(val);
+ }
+ void push_back(const value_t& val) {
+ args.push_back(val);
+ }
+ void pop_back() {
+ args.pop_back();
+ }
+
+ value_t::sequence_t::const_iterator begin() const {
+ return args.begin();
+ }
+ value_t::sequence_t::const_iterator end() const {
+ return args.end();
+ }
+
+ std::size_t size() const {
+ return args.size();
+ }
+ bool empty() const {
+ return args.size() == 0;
+ }
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ explicit call_scope_t() {
+ TRACE_CTOR(call_scope_t, "");
+ }
+
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & boost::serialization::base_object<child_scope_t>(*this);
+ ar & args;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+class bind_scope_t : public child_scope_t
+{
+ bind_scope_t();
+
+public:
+ scope_t& grandchild;
+
+ explicit bind_scope_t(scope_t& _parent,
+ scope_t& _grandchild)
+ : child_scope_t(_parent), grandchild(_grandchild) {
+ TRACE_CTOR(bind_scope_t, "scope_t&, scope_t&");
+ }
+ virtual ~bind_scope_t() {
+ TRACE_DTOR(bind_scope_t);
+ }
+
+ virtual void define(const symbol_t::kind_t kind, const string& name,
+ expr_t::ptr_op_t def) {
+ parent->define(kind, name, def);
+ grandchild.define(kind, name, def);
+ }
+
+ virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind,
+ const string& name) {
+ if (expr_t::ptr_op_t def = grandchild.lookup(kind, name))
+ return def;
+ return child_scope_t::lookup(kind, name);
+ }
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & boost::serialization::base_object<child_scope_t>(*this);
+ ar & grandchild;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+template <typename T>
+T * search_scope(scope_t * ptr)
+{
+ if (T * sought = dynamic_cast<T *>(ptr))
+ return sought;
+
+ if (bind_scope_t * scope = dynamic_cast<bind_scope_t *>(ptr)) {
+ if (T * sought = search_scope<T>(&scope->grandchild))
+ return sought;
+ return search_scope<T>(scope->parent);
+ }
+ else if (child_scope_t * scope = dynamic_cast<child_scope_t *>(ptr)) {
+ return search_scope<T>(scope->parent);
+ }
+ return NULL;
+}
+
+template <typename T>
+inline T& find_scope(child_scope_t& scope, bool skip_this = true)
+{
+ if (T * sought = search_scope<T>(skip_this ? scope.parent : &scope))
+ return *sought;
+
+ throw_(std::runtime_error, _("Could not find scope"));
+ return reinterpret_cast<T&>(scope); // never executed
+}
+
+} // namespace ledger
+
+#endif // _SCOPE_H
diff --git a/src/session.cc b/src/session.cc
new file mode 100644
index 00000000..1882e554
--- /dev/null
+++ b/src/session.cc
@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "session.h"
+#include "xact.h"
+#include "account.h"
+#include "journal.h"
+#include "iterators.h"
+#include "filters.h"
+#include "archive.h"
+
+namespace ledger {
+
+void set_session_context(session_t * session)
+{
+ if (session) {
+ times_initialize();
+ amount_t::initialize();
+
+ amount_t::parse_conversion("1.0m", "60s");
+ amount_t::parse_conversion("1.0h", "60m");
+
+ value_t::initialize();
+ }
+ else if (! session) {
+ value_t::shutdown();
+ amount_t::shutdown();
+ times_shutdown();
+ }
+}
+
+session_t::session_t()
+ : flush_on_next_data_file(false),
+ current_year(CURRENT_DATE().year()),
+ journal(new journal_t)
+{
+ TRACE_CTOR(session_t, "");
+
+ if (const char * home_var = std::getenv("HOME"))
+ HANDLER(price_db_).on(none, (path(home_var) / ".pricedb").string());
+ else
+ HANDLER(price_db_).on(none, path("./.pricedb").string());
+}
+
+std::size_t session_t::read_data(const string& master_account)
+{
+ bool populated_data_files = false;
+
+ if (HANDLER(file_).data_files.empty()) {
+ path file;
+ if (const char * home_var = std::getenv("HOME"))
+ file = path(home_var) / ".ledger";
+
+ if (! file.empty() && exists(file))
+ HANDLER(file_).data_files.push_back(file);
+ else
+ throw_(parse_error, "No journal file was specified (please use -f)");
+
+ populated_data_files = true;
+ }
+
+ std::size_t xact_count = 0;
+
+ account_t * acct = journal->master;
+ if (! master_account.empty())
+ acct = journal->find_account(master_account);
+
+ optional<path> price_db_path;
+ if (HANDLED(price_db_))
+ price_db_path = resolve_path(HANDLER(price_db_).str());
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+ optional<archive_t> cache;
+ if (HANDLED(cache_) && master_account.empty())
+ cache = archive_t(HANDLED(cache_).str());
+
+ if (! (cache &&
+ cache->should_load(HANDLER(file_).data_files) &&
+ cache->load(*journal.get()))) {
+#endif // HAVE_BOOST_SERIALIZATION
+ if (price_db_path) {
+ if (exists(*price_db_path)) {
+ if (journal->read(*price_db_path) > 0)
+ throw_(parse_error, _("Transactions not allowed in price history file"));
+ }
+ }
+
+ foreach (const path& pathname, HANDLER(file_).data_files) {
+ if (pathname == "-") {
+ // To avoid problems with stdin and pipes, etc., we read the entire
+ // file in beforehand into a memory buffer, and then parcel it out
+ // from there.
+ std::ostringstream buffer;
+
+ while (std::cin.good() && ! std::cin.eof()) {
+ char line[8192];
+ std::cin.read(line, 8192);
+ std::streamsize count = std::cin.gcount();
+ buffer.write(line, count);
+ }
+ buffer.flush();
+
+ std::istringstream buf_in(buffer.str());
+
+ xact_count += journal->read(buf_in, "/dev/stdin", acct);
+ journal->sources.push_back(journal_t::fileinfo_t());
+ } else {
+ xact_count += journal->read(pathname, acct);
+ }
+ }
+
+ assert(xact_count == journal->xacts.size());
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+ if (cache && cache->should_save(*journal.get()))
+ cache->save(*journal.get());
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+
+ if (populated_data_files)
+ HANDLER(file_).data_files.clear();
+
+ VERIFY(journal->valid());
+
+ return journal->xacts.size();
+}
+
+void session_t::read_journal_files()
+{
+ INFO_START(journal, "Read journal file");
+
+ string master_account;
+ if (HANDLED(master_account_))
+ master_account = HANDLER(master_account_).str();
+
+ std::size_t count = read_data(master_account);
+ if (count == 0)
+ throw_(parse_error,
+ _("Failed to locate any transactions; did you specify a valid file with -f?"));
+
+ INFO_FINISH(journal);
+
+ INFO("Found " << count << " transactions");
+}
+
+void session_t::close_journal_files()
+{
+ journal.reset();
+ amount_t::shutdown();
+
+ journal.reset(new journal_t);
+ amount_t::initialize();
+}
+
+option_t<session_t> * session_t::lookup_option(const char * p)
+{
+ switch (*p) {
+ case 'Q':
+ OPT_CH(download); // -Q
+ break;
+ case 'Z':
+ OPT_CH(price_exp_);
+ break;
+ case 'c':
+ OPT(cache_);
+ break;
+ case 'd':
+ OPT(download); // -Q
+ break;
+ case 'e':
+ OPT(european);
+ break;
+ case 'f':
+ OPT_(file_); // -f
+ break;
+ case 'i':
+ OPT(input_date_format_);
+ break;
+ case 'l':
+ OPT_ALT(price_exp_, leeway_);
+ break;
+ case 'm':
+ OPT(master_account_);
+ break;
+ case 'p':
+ OPT(price_db_);
+ else OPT(price_exp_);
+ break;
+ case 's':
+ OPT(strict);
+ break;
+ }
+ return NULL;
+}
+
+expr_t::ptr_op_t session_t::lookup(const symbol_t::kind_t kind,
+ const string& name)
+{
+ switch (kind) {
+ case symbol_t::FUNCTION:
+ // Check if they are trying to access an option's setting or value.
+ if (option_t<session_t> * handler = lookup_option(name.c_str()))
+ return MAKE_OPT_FUNCTOR(session_t, handler);
+ break;
+
+ case symbol_t::OPTION:
+ if (option_t<session_t> * handler = lookup_option(name.c_str()))
+ return MAKE_OPT_HANDLER(session_t, handler);
+ break;
+
+ default:
+ break;
+ }
+
+ return symbol_scope_t::lookup(kind, name);
+}
+
+} // namespace ledger
diff --git a/src/session.h b/src/session.h
new file mode 100644
index 00000000..5c4612a0
--- /dev/null
+++ b/src/session.h
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @defgroup report Reporting
+ */
+
+/**
+ * @file session.h
+ * @author John Wiegley
+ *
+ * @ingroup report
+ */
+#ifndef _SESSION_H
+#define _SESSION_H
+
+#include "interactive.h"
+#include "account.h"
+#include "journal.h"
+#include "option.h"
+#include "commodity.h"
+
+namespace ledger {
+
+class commodity_pool_t;
+class xact_t;
+
+class session_t : public symbol_scope_t
+{
+ friend void set_session_context(session_t * session);
+
+public:
+ bool flush_on_next_data_file;
+ date_t::year_type current_year;
+ std::auto_ptr<journal_t> journal;
+
+ explicit session_t();
+ virtual ~session_t() {
+ TRACE_DTOR(session_t);
+ }
+
+ void set_flush_on_next_data_file(const bool truth) {
+ flush_on_next_data_file = truth;
+ }
+
+ std::size_t read_data(const string& master_account = "");
+
+ void read_journal_files();
+ void close_journal_files();
+
+ void report_options(std::ostream& out)
+ {
+ HANDLER(cache_).report(out);
+ HANDLER(download).report(out);
+ HANDLER(european).report(out);
+ HANDLER(file_).report(out);
+ HANDLER(input_date_format_).report(out);
+ HANDLER(master_account_).report(out);
+ HANDLER(price_db_).report(out);
+ HANDLER(price_exp_).report(out);
+ HANDLER(strict).report(out);
+ }
+
+ option_t<session_t> * lookup_option(const char * p);
+
+ virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind,
+ const string& name);
+
+ /**
+ * Option handlers
+ */
+
+ OPTION(session_t, cache_);
+ OPTION(session_t, download); // -Q
+
+ OPTION_(session_t, european, DO() {
+ commodity_t::european_by_default = true;
+ });
+
+ OPTION__
+ (session_t, price_exp_, // -Z
+ CTOR(session_t, price_exp_) { value = 24L * 3600L; }
+ DO_(args) {
+ value = args[1].to_long() * 60L;
+ });
+
+ OPTION__
+ (session_t, file_, // -f
+ std::list<path> data_files;
+ CTOR(session_t, file_) {}
+ DO_(args) {
+ assert(args.size() == 2);
+ if (parent->flush_on_next_data_file) {
+ data_files.clear();
+ parent->flush_on_next_data_file = false;
+ }
+ data_files.push_back(args[1].as_string());
+ });
+
+ OPTION_(session_t, input_date_format_, DO_(args) {
+ // This changes static variables inside times.h, which affects the basic
+ // date parser.
+ set_input_date_format(args[1].as_string().c_str());
+ });
+
+ OPTION(session_t, master_account_);
+ OPTION(session_t, price_db_);
+ OPTION(session_t, strict);
+};
+
+/**
+ * Set 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 an 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);
+
+} // namespace ledger
+
+#endif // _SESSION_H
diff --git a/src/stats.cc b/src/stats.cc
new file mode 100644
index 00000000..b89a5949
--- /dev/null
+++ b/src/stats.cc
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "draft.h"
+#include "xact.h"
+#include "post.h"
+#include "account.h"
+#include "report.h"
+#include "session.h"
+
+namespace ledger {
+
+value_t report_statistics(call_scope_t& args)
+{
+ report_t& report(find_scope<report_t>(args));
+ std::ostream& out(report.output_stream);
+
+ const account_t::xdata_t::details_t&
+ statistics(report.session.journal->master->family_details(true));
+
+ if (! is_valid(statistics.earliest_post) &&
+ ! is_valid(statistics.latest_post))
+ return NULL_VALUE;
+
+ assert(is_valid(statistics.earliest_post));
+ assert(is_valid(statistics.latest_post));
+
+ {
+ straccstream accum;
+ out << ACCUM(accum << _("Time period: %1 to %2 (%3 days)")
+ << format_date(statistics.earliest_post)
+ << format_date(statistics.latest_post)
+ << (statistics.latest_post -
+ statistics.earliest_post).days())
+ << std::endl << std::endl;
+ }
+
+ out << _(" Files these postings came from:") << std::endl;
+
+ foreach (const path& pathname, statistics.filenames)
+ if (! pathname.empty())
+ out << " " << pathname.string() << std::endl;
+ out << std::endl;
+
+ out << _(" Unique payees: ");
+ out.width(6);
+ out << statistics.payees_referenced.size() << std::endl;
+
+ out << _(" Unique accounts: ");
+ out.width(6);
+ out << statistics.accounts_referenced.size() << std::endl;
+
+ out << std::endl;
+
+ out << _(" Number of postings: ");
+ out.width(6);
+ out << statistics.posts_count;
+
+ out << " (";
+ out.precision(2);
+ out << (double((statistics.latest_post - statistics.earliest_post).days()) /
+ double(statistics.posts_count)) << _(" per day)") << std::endl;
+
+ out << _(" Uncleared postings: ");
+ out.width(6);
+ out << (statistics.posts_count -
+ statistics.posts_cleared_count) << std::endl;
+
+ out << std::endl;
+
+ out << _(" Days since last post: ");
+ out.width(6);
+ out << (CURRENT_DATE() - statistics.latest_post).days()
+ << std::endl;
+
+ out << _(" Posts in last 7 days: ");
+ out.width(6);
+ out << statistics.posts_last_7_count << std::endl;
+ out << _(" Posts in last 30 days: ");
+ out.width(6);
+ out << statistics.posts_last_30_count << std::endl;
+ out << _(" Posts seen this month: ");
+ out.width(6);
+ out << statistics.posts_this_month_count << std::endl;
+
+ out.flush();
+
+ return NULL_VALUE;
+}
+
+} // namespace ledger
diff --git a/src/stats.h b/src/stats.h
new file mode 100644
index 00000000..d2d10de6
--- /dev/null
+++ b/src/stats.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup stats
+ */
+
+/**
+ * @file stats.h
+ * @author John Wiegley
+ *
+ * @ingroup report
+ */
+#ifndef _STATS_H
+#define _STATS_H
+
+#include "value.h"
+
+namespace ledger {
+
+class call_scope_t;
+
+value_t report_statistics(call_scope_t& scope);
+
+} // namespace ledger
+
+#endif // _STATS_H
diff --git a/src/stream.cc b/src/stream.cc
new file mode 100644
index 00000000..e39b74e0
--- /dev/null
+++ b/src/stream.cc
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "stream.h"
+
+namespace ledger {
+
+namespace {
+ /**
+ * @brief Forks a child process so that Ledger may handle running a
+ * pager
+ *
+ * In order for the pager option to work, Ledger has to run the pager
+ * itself, which requires Ledger to fork a new process in order to run
+ * the pager. This function does the necessary fork. After the fork,
+ * two processes exist. One of them is exec'd to create the pager;
+ * the other is still Ledger.
+ *
+ * This function returns only for the process that is still Ledger.
+ *
+ * @param out Pointer to a pointer to the output stream. This The
+ * pointer to the output stream is changed so that the stream is
+ * connected to the stdin of the pager. The caller is responsible for
+ * cleaning this up.
+ *
+ * @param pager_path Path to the pager command.
+ *
+ * @return The file descriptor of the pipe to the pager. The caller
+ * is responsible for cleaning this up.
+ *
+ * @exception std::logic_error Some problem was encountered, such as
+ * failure to create a pipe or failure to fork a child process.
+ */
+ int do_fork(std::ostream ** os, const path& pager_path)
+ {
+ int pfd[2];
+
+ int 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.
+#if BOOST_VERSION >= 103700
+ path basename(pager_path.filename());
+#else
+ path basename(pager_path.leaf());
+#endif
+ execlp(pager_path.string().c_str(), basename.string().c_str(), NULL);
+
+ // We should never, ever reach here
+ perror((std::string("execlp: ") + pager_path.string()).c_str());
+ exit(1);
+ }
+ else { // parent
+ close(pfd[0]);
+ typedef iostreams::stream<iostreams::file_descriptor_sink> fdstream;
+ *os = new fdstream(pfd[1]);
+ }
+ return pfd[1];
+ }
+}
+
+void output_stream_t::initialize(const optional<path>& output_file,
+ const optional<path>& pager_path)
+{
+ if (output_file && *output_file != "-")
+ os = new ofstream(*output_file);
+ else if (pager_path)
+ pipe_to_pager_fd = do_fork(&os, *pager_path);
+ else
+ os = &std::cout;
+}
+
+void output_stream_t::close()
+{
+ if (os != &std::cout) {
+ checked_delete(os);
+ os = &std::cout;
+ }
+
+ if (pipe_to_pager_fd != -1) {
+ ::close(pipe_to_pager_fd);
+ pipe_to_pager_fd = -1;
+
+ int status;
+ wait(&status);
+ if (! WIFEXITED(status) || WEXITSTATUS(status) != 0)
+ throw std::logic_error(_("Error in the pager"));
+ }
+}
+
+} // namespace ledger
diff --git a/src/stream.h b/src/stream.h
new file mode 100644
index 00000000..e87da67a
--- /dev/null
+++ b/src/stream.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup util
+ */
+
+/**
+ * @file stream.h
+ * @author John Wiegley, Omari Norman
+ *
+ * @ingroup util
+ *
+ * @brief A utility class for abstracting an output stream.
+ *
+ * Because Ledger might send output to a file, the console, or a pager
+ * child process, different cleanup is needed for each scenario. This
+ * file abstracts those various needs.
+ */
+#ifndef _STREAM_H
+#define _STREAM_H
+
+#include "utils.h"
+
+namespace ledger {
+
+/**
+ * @brief An output stream
+ *
+ * A stream to output in Ledger may be going to one of three places:
+ * to stdout, to a file, or to a pager. Construct an output_stream_t and
+ * the stream will automatically be cleaned up upon destruction.
+ *
+ * This class suffers from "else-if-heimer's disease," see Marshall
+ * Cline's "C++ FAQ Lite". Arguably this should be three different
+ * classes, but that introduces additional unneeded complications.
+ */
+class output_stream_t
+{
+ output_stream_t& operator=(const output_stream_t&);
+
+private:
+ int pipe_to_pager_fd;
+
+public:
+ /**
+ * A pointer to the ostream. Don't delete this; the output_stream_t
+ * class takes care of this.
+ */
+ std::ostream * os;
+
+ /**
+ * Construct a new output_stream_t.
+ */
+ output_stream_t() : pipe_to_pager_fd(-1), os(&std::cout) {
+ TRACE_CTOR(output_stream_t, "");
+ }
+
+ /**
+ * When copy-constructed, make the copy just be a new output stream. This
+ * allows large classes to rely on their default copy-constructor without
+ * worrying about pointer copying within output_stream_t.
+ */
+ output_stream_t(const output_stream_t&)
+ : pipe_to_pager_fd(-1), os(&std::cout) {
+ TRACE_CTOR(output_stream_t, "copy");
+ }
+
+ /**
+ * Destroys an output_stream_t. This deletes the dynamically
+ * allocated ostream, if necessary. It also closes output file
+ * descriptor, if necessary.
+ */
+ ~output_stream_t() {
+ TRACE_DTOR(output_stream_t);
+ close();
+ }
+
+ /**
+ * Initialize the output stream object.
+ *
+ * @param output_file File to which to send output. If both this
+ * and pager are set, output_file takes priority.
+ *
+ * @param pager_path Path to a pager. To not use a pager, leave this
+ * empty.
+ */
+ void initialize(const optional<path>& output_file = none,
+ const optional<path>& pager_path = none);
+
+ /**
+ * Convertor to a standard ostream. This is used so that we can
+ * stream directly to an object of type output_stream_t.
+ */
+ operator std::ostream&() {
+ return *os;
+ }
+
+ /**
+ * Flushing function. A simple proxy for ostream's flush.
+ */
+ void flush() {
+ os->flush();
+ }
+
+ /**
+ * Close the output stream, waiting on the pager process if necessary.
+ */
+ void close();
+};
+
+} // namespace ledger
+
+#endif // _STREAM_H
diff --git a/src/system.hh.in b/src/system.hh.in
new file mode 100644
index 00000000..341ce129
--- /dev/null
+++ b/src/system.hh.in
@@ -0,0 +1,256 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup util
+ *
+ * @file system.hh
+ * @author John Wiegley
+ *
+ * @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.
+ */
+#ifndef _SYSTEM_HH
+#define _SYSTEM_HH
+
+#include "config.h"
+
+#if defined(__GNUG__) && __GNUG__ < 3
+#define _XOPEN_SOURCE
+#endif
+
+#include <algorithm>
+#include <exception>
+#include <typeinfo>
+#include <locale>
+#include <stdexcept>
+#include <iostream>
+#include <streambuf>
+#include <iomanip>
+#include <fstream>
+#include <sstream>
+#include <iterator>
+#include <list>
+#include <map>
+#include <memory>
+#include <new>
+#include <set>
+#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 std::streamoff istream_pos_type;
+typedef std::streamoff ostream_pos_type;
+
+#else // ! (defined(__GNUG__) && __GNUG__ < 3)
+
+typedef std::istream::pos_type istream_pos_type;
+typedef std::ostream::pos_type ostream_pos_type;
+
+#endif
+
+#include <cassert>
+#include <cctype>
+#include <cstdarg>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <csignal>
+
+#if defined __FreeBSD__ && __FreeBSD__ <= 4
+// FreeBSD has a broken isspace macro, so don't use it
+#undef isspace(c)
+#endif
+
+#include <sys/stat.h>
+#if defined(WIN32)
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
+#if defined(HAVE_GETPWUID) || defined(HAVE_GETPWNAM)
+#include <pwd.h>
+#endif
+
+#if defined(HAVE_UNIX_PIPES)
+#include <sys/types.h>
+#include <sys/wait.h>
+#endif
+#if defined(HAVE_GETTEXT)
+#include "gettext.h"
+#define _(str) gettext(str)
+#else
+#define _(str) str
+#endif
+
+#include <gmp.h>
+#include <mpfr.h>
+#include "sha1.h"
+#include "utf8.h"
+
+#if defined(HAVE_LIBEDIT)
+#include <editline/readline.h>
+#endif
+
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/bind.hpp>
+#include <boost/cast.hpp>
+#include <boost/current_function.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/date_time/posix_time/posix_time_io.hpp>
+#include <boost/date_time/gregorian/gregorian_io.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/iostreams/stream.hpp>
+#include <boost/iostreams/write.hpp>
+#include <boost/iostreams/device/file_descriptor.hpp>
+#include <boost/iterator/transform_iterator.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/operators.hpp>
+#include <boost/optional.hpp>
+#include <boost/ptr_container/ptr_list.hpp>
+#include <boost/random/mersenne_twister.hpp>
+#include <boost/random/uniform_int.hpp>
+#include <boost/random/uniform_real.hpp>
+#include <boost/random/variate_generator.hpp>
+#if defined(HAVE_BOOST_REGEX_UNICODE)
+#include <boost/regex/icu.hpp>
+#else
+#include <boost/regex.hpp>
+#endif // HAVE_BOOST_REGEX_UNICODE
+#include <boost/variant.hpp>
+#include <boost/version.hpp>
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+
+#include <boost/archive/binary_iarchive.hpp>
+#include <boost/archive/binary_oarchive.hpp>
+
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/binary_object.hpp>
+#include <boost/serialization/optional.hpp>
+#include <boost/serialization/shared_ptr.hpp>
+#include <boost/serialization/variant.hpp>
+#include <boost/serialization/utility.hpp>
+#include <boost/serialization/export.hpp>
+#include <boost/serialization/level.hpp>
+#include <boost/serialization/string.hpp>
+#include <boost/serialization/vector.hpp>
+#include <boost/serialization/deque.hpp>
+#include <boost/serialization/list.hpp>
+#include <boost/serialization/map.hpp>
+
+#include <boost/date_time/posix_time/time_serialize.hpp>
+#include <boost/date_time/gregorian/greg_serialize.hpp>
+
+namespace boost {
+namespace serialization {
+
+template <class Archive>
+void serialize(Archive& ar, boost::filesystem::path& p, const unsigned int)
+{
+ std::string s;
+ if (Archive::is_saving::value)
+ s = p.string();
+
+ ar & s;
+
+ if (Archive::is_loading::value)
+ p = s;
+}
+
+template <class Archive, class T>
+void serialize(Archive& ar, boost::intrusive_ptr<T>& ptr, const unsigned int)
+{
+ if (Archive::is_saving::value) {
+ T * p = ptr.get();
+ ar & p;
+ }
+ else if (Archive::is_loading::value) {
+ T * p;
+ ar & p;
+ ptr.reset(p);
+ }
+}
+
+template <class Archive, class T>
+void serialize(Archive&, boost::function<T>&, const unsigned int)
+{
+}
+
+template <class Archive>
+void serialize(Archive& ar, istream_pos_type& pos, const unsigned int)
+{
+ ar & make_binary_object(&pos, sizeof(istream_pos_type));
+}
+
+} // namespace serialization
+} // namespace boost
+
+#endif // HAVE_BOOST_SERIALIZATION
+
+#if defined(HAVE_BOOST_PYTHON)
+
+#include <boost/python.hpp>
+
+#include <boost/python/detail/wrap_python.hpp>
+#include <datetime.h>
+#include <unicodeobject.h>
+
+#include <boost/python/module_init.hpp>
+#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
+
+#endif // HAVE_BOOST_PYTHON
+
+#endif // _SYSTEM_HH
diff --git a/src/temps.cc b/src/temps.cc
new file mode 100644
index 00000000..fd099e9a
--- /dev/null
+++ b/src/temps.cc
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "xact.h"
+#include "post.h"
+#include "account.h"
+#include "temps.h"
+
+namespace ledger {
+
+temporaries_t::~temporaries_t()
+{
+ if (post_temps) {
+ foreach (post_t& post, *post_temps) {
+ if (! post.xact->has_flags(ITEM_TEMP))
+ post.xact->remove_post(&post);
+
+ if (post.account && ! post.account->has_flags(ACCOUNT_TEMP))
+ post.account->remove_post(&post);
+ }
+ }
+
+ if (acct_temps) {
+ foreach (account_t& acct, *acct_temps) {
+ if (acct.parent && ! acct.parent->has_flags(ACCOUNT_TEMP))
+ acct.parent->remove_account(&acct);
+ }
+ }
+}
+
+xact_t& temporaries_t::copy_xact(xact_t& origin)
+{
+ if (! xact_temps)
+ xact_temps = std::list<xact_t>();
+
+ xact_temps->push_back(origin);
+ xact_t& temp(xact_temps->back());
+
+ temp.add_flags(ITEM_TEMP);
+ return temp;
+}
+
+xact_t& temporaries_t::create_xact()
+{
+ if (! xact_temps)
+ xact_temps = std::list<xact_t>();
+
+ xact_temps->push_back(xact_t());
+ xact_t& temp(xact_temps->back());
+
+ temp.add_flags(ITEM_TEMP);
+ return temp;
+}
+
+post_t& temporaries_t::copy_post(post_t& origin, xact_t& xact,
+ account_t * account)
+{
+ if (! post_temps)
+ post_temps = std::list<post_t>();
+
+ post_temps->push_back(origin);
+ post_t& temp(post_temps->back());
+
+ if (account)
+ temp.account = account;
+ temp.add_flags(ITEM_TEMP);
+
+ temp.account->add_post(&temp);
+ xact.add_post(&temp);
+
+ return temp;
+}
+
+post_t& temporaries_t::create_post(xact_t& xact, account_t * account)
+{
+ if (! post_temps)
+ post_temps = std::list<post_t>();
+
+ post_temps->push_back(post_t(account));
+ post_t& temp(post_temps->back());
+
+ temp.account = account;
+ temp.add_flags(ITEM_TEMP);
+
+ temp.account->add_post(&temp);
+ xact.add_post(&temp);
+
+ return temp;
+}
+
+account_t& temporaries_t::create_account(const string& name,
+ account_t * parent)
+{
+ if (! acct_temps)
+ acct_temps = std::list<account_t>();
+
+ acct_temps->push_back(account_t(parent, name));
+ account_t& temp(acct_temps->back());
+
+ if (parent)
+ parent->add_account(&temp);
+
+ temp.add_flags(ACCOUNT_TEMP);
+ return temp;
+}
+
+} // namespace ledger
diff --git a/src/temps.h b/src/temps.h
new file mode 100644
index 00000000..4243079c
--- /dev/null
+++ b/src/temps.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup report
+ */
+
+/**
+ * @file temps.h
+ * @author John Wiegley
+ *
+ * @ingroup report
+ */
+#ifndef _TEMPS_H
+#define _TEMPS_H
+
+namespace ledger {
+
+class temporaries_t
+{
+ optional<std::list<xact_t> > xact_temps;
+ optional<std::list<post_t> > post_temps;
+ optional<std::list<account_t> > acct_temps;
+
+public:
+ ~temporaries_t();
+
+ xact_t& copy_xact(xact_t& origin);
+ xact_t& create_xact();
+ xact_t& last_xact() {
+ return xact_temps->back();
+ }
+ post_t& copy_post(post_t& origin, xact_t& xact,
+ account_t * account = NULL);
+ post_t& create_post(xact_t& xact, account_t * account);
+ post_t& last_post() {
+ return post_temps->back();
+ }
+ account_t& create_account(const string& name = "",
+ account_t * parent = NULL);
+ account_t& last_account() {
+ return acct_temps->back();
+ }
+};
+
+} // namespace ledger
+
+#endif // _TEMPS_H
diff --git a/src/textual.cc b/src/textual.cc
new file mode 100644
index 00000000..cf670cae
--- /dev/null
+++ b/src/textual.cc
@@ -0,0 +1,1396 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "journal.h"
+#include "xact.h"
+#include "post.h"
+#include "account.h"
+#include "option.h"
+#include "query.h"
+#include "pstream.h"
+#include "pool.h"
+#include "session.h"
+
+#define TIMELOG_SUPPORT 1
+#if defined(TIMELOG_SUPPORT)
+#include "timelog.h"
+#endif
+
+namespace ledger {
+
+namespace {
+ class instance_t : public noncopyable, public scope_t
+ {
+ static const std::size_t MAX_LINE = 1024;
+
+ public:
+ typedef std::pair<commodity_t *, amount_t> fixed_rate_t;
+ typedef variant<account_t *, string, fixed_rate_t> state_t;
+
+ std::list<state_t>& state_stack;
+
+#if defined(TIMELOG_SUPPORT)
+ time_log_t& timelog;
+#endif
+ instance_t * parent;
+ std::istream& in;
+ scope_t& scope;
+ journal_t& journal;
+ account_t * master;
+ const path * original_file;
+ accounts_map account_aliases;
+ bool strict;
+ path pathname;
+ char linebuf[MAX_LINE + 1];
+ std::size_t linenum;
+ istream_pos_type line_beg_pos;
+ istream_pos_type curr_pos;
+ std::size_t count;
+ std::size_t errors;
+
+ optional<date_t::year_type> current_year;
+
+ instance_t(std::list<state_t>& _state_stack,
+#if defined(TIMELOG_SUPPORT)
+ time_log_t& _timelog,
+#endif
+ std::istream& _in,
+ scope_t& _scope,
+ journal_t& _journal,
+ account_t * _master = NULL,
+ const path * _original_file = NULL,
+ bool _strict = false,
+ instance_t * _parent = NULL);
+
+ ~instance_t();
+
+ bool front_is_account() {
+ return state_stack.front().type() == typeid(account_t *);
+ }
+ bool front_is_string() {
+ return state_stack.front().type() == typeid(string);
+ }
+ bool front_is_fixed_rate() {
+ return state_stack.front().type() == typeid(fixed_rate_t);
+ }
+
+ account_t * top_account() {
+ foreach (state_t& state, state_stack)
+ if (state.type() == typeid(account_t *))
+ return boost::get<account_t *>(state);
+ return NULL;
+ }
+
+ void parse();
+ std::streamsize read_line(char *& line);
+ bool peek_whitespace_line() {
+ return (in.good() && ! in.eof() &&
+ (in.peek() == ' ' || in.peek() == '\t'));
+ }
+
+ void read_next_directive();
+
+#if defined(TIMELOG_SUPPORT)
+ void clock_in_directive(char * line, bool capitalized);
+ void clock_out_directive(char * line, bool capitalized);
+#endif
+
+ void default_commodity_directive(char * line);
+ void default_account_directive(char * line);
+ void price_conversion_directive(char * line);
+ void price_xact_directive(char * line);
+ void nomarket_directive(char * line);
+ void year_directive(char * line);
+ void option_directive(char * line);
+ void automated_xact_directive(char * line);
+ void period_xact_directive(char * line);
+ void xact_directive(char * line, std::streamsize len);
+ void include_directive(char * line);
+ void master_account_directive(char * line);
+ void end_directive(char * line);
+ void alias_directive(char * line);
+ void fixed_directive(char * line);
+ void tag_directive(char * line);
+ void define_directive(char * line);
+ bool general_directive(char * line);
+
+ post_t * parse_post(char * line,
+ std::streamsize len,
+ account_t * account,
+ xact_t * xact,
+ bool honor_strict = true,
+ bool defer_expr = false);
+
+ bool parse_posts(account_t * account,
+ xact_base_t& xact,
+ const bool defer_expr = false);
+
+ xact_t * parse_xact(char * line,
+ std::streamsize len,
+ account_t * account);
+
+ virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind,
+ const string& name);
+ };
+
+ void parse_amount_expr(scope_t& scope,
+ std::istream& in,
+ amount_t& amount,
+ optional<expr_t> * amount_expr,
+ post_t * post,
+ const parse_flags_t& flags = PARSE_DEFAULT,
+ const bool defer_expr = false)
+ {
+ expr_t expr(in, flags.plus_flags(PARSE_PARTIAL));
+
+ DEBUG("textual.parse", "Parsed an amount expression");
+
+#if defined(DEBUG_ENABLED)
+ DEBUG_IF("textual.parse") {
+ if (_debug_stream) {
+ ledger::dump_value_expr(*_debug_stream, expr);
+ *_debug_stream << std::endl;
+ }
+ }
+#endif
+
+ if (expr) {
+ bind_scope_t bound_scope(scope, *post);
+ if (defer_expr) {
+ assert(amount_expr);
+ *amount_expr = expr;
+ (*amount_expr)->compile(bound_scope);
+ } else {
+ value_t result(expr.calc(bound_scope));
+ if (result.is_long()) {
+ amount = result.to_amount();
+ } else {
+ if (! result.is_amount())
+ throw_(amount_error,
+ _("Amount expressions must result in a simple amount"));
+ amount = result.as_amount();
+ }
+ DEBUG("textual.parse", "The posting amount is " << amount);
+ }
+ }
+ }
+}
+
+instance_t::instance_t(std::list<state_t>& _state_stack,
+#if defined(TIMELOG_SUPPORT)
+ time_log_t& _timelog,
+#endif
+ std::istream& _in,
+ scope_t& _scope,
+ journal_t& _journal,
+ account_t * _master,
+ const path * _original_file,
+ bool _strict,
+ instance_t * _parent)
+ : state_stack(_state_stack),
+#if defined(TIMELOG_SUPPORT)
+ timelog(_timelog),
+#endif
+ parent(_parent), in(_in), scope(_scope),
+ journal(_journal), master(_master),
+ original_file(_original_file), strict(_strict)
+{
+ TRACE_CTOR(instance_t, "...");
+
+ if (! master)
+ master = journal.master;
+ state_stack.push_front(master);
+
+ if (_original_file)
+ pathname = *_original_file;
+ else
+ pathname = "/dev/stdin";
+}
+
+instance_t::~instance_t()
+{
+ TRACE_DTOR(instance_t);
+
+ assert(! state_stack.empty());
+ state_stack.pop_front();
+}
+
+void instance_t::parse()
+{
+ INFO("Parsing file '" << pathname.string() << "'");
+
+ TRACE_START(instance_parse, 1,
+ "Done parsing file '" << pathname.string() << "'");
+
+ if (! in.good() || in.eof())
+ return;
+
+ linenum = 0;
+ errors = 0;
+ count = 0;
+ curr_pos = in.tellg();
+
+ while (in.good() && ! in.eof()) {
+ try {
+ read_next_directive();
+ }
+ catch (const std::exception& err) {
+ string current_context = error_context();
+
+ if (parent) {
+ std::list<instance_t *> instances;
+
+ for (instance_t * instance = parent;
+ instance;
+ instance = instance->parent)
+ instances.push_front(instance);
+
+ foreach (instance_t * instance, instances)
+ add_error_context(_("In file included from %1")
+ << file_context(instance->pathname,
+ instance->linenum));
+ }
+ add_error_context(_("While parsing file %1")
+ << file_context(pathname, linenum));
+
+ if (caught_signal != NONE_CAUGHT)
+ throw;
+
+ string context = error_context();
+ if (! context.empty())
+ std::cerr << context << std::endl;
+
+ if (! current_context.empty())
+ std::cerr << current_context << std::endl;
+
+ std::cerr << _("Error: ") << err.what() << std::endl;
+ errors++;
+ }
+ }
+
+ TRACE_STOP(instance_parse, 1);
+}
+
+std::streamsize instance_t::read_line(char *& line)
+{
+ assert(in.good());
+ assert(! in.eof()); // no one should call us in that case
+
+ line_beg_pos = curr_pos;
+
+ check_for_signal();
+
+ in.getline(linebuf, MAX_LINE);
+ std::streamsize len = in.gcount();
+
+ if (len > 0) {
+ if (linenum == 0 && utf8::is_bom(linebuf))
+ line = &linebuf[3];
+ else
+ line = linebuf;
+
+ if (line[len - 1] == '\r') // strip Windows CRLF down to LF
+ line[--len] = '\0';
+
+ linenum++;
+
+ curr_pos = line_beg_pos;
+ curr_pos += len;
+
+ return len - 1; // LF is being silently dropped
+ }
+ return 0;
+}
+
+void instance_t::read_next_directive()
+{
+ char * line;
+ std::streamsize len = read_line(line);
+
+ if (len == 0 || line == NULL)
+ return;
+
+ switch (line[0]) {
+ case '\0':
+ assert(false); // shouldn't ever reach here
+ break;
+
+ case ' ':
+ case '\t': {
+ break;
+ }
+
+ case ';': // comments
+ case '#':
+ case '*':
+ case '|':
+ break;
+
+ case '-': // option setting
+ option_directive(line);
+ break;
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ xact_directive(line, len);
+ break;
+ case '=': // automated xact
+ automated_xact_directive(line);
+ break;
+ case '~': // period xact
+ period_xact_directive(line);
+ break;
+
+ case '@':
+ case '!':
+ line++;
+ // fall through...
+ default: // some other directive
+ if (! general_directive(line)) {
+ switch (line[0]) {
+#if defined(TIMELOG_SUPPORT)
+ case 'i':
+ clock_in_directive(line, false);
+ break;
+ case 'I':
+ clock_in_directive(line, true);
+ break;
+
+ case 'o':
+ clock_out_directive(line, false);
+ break;
+ case 'O':
+ clock_out_directive(line, true);
+ break;
+
+ case 'h':
+ case 'b':
+ break;
+#endif // TIMELOG_SUPPORT
+
+ case 'A': // a default account for unbalanced posts
+ default_account_directive(line);
+ break;
+ case 'C': // a set of conversions
+ price_conversion_directive(line);
+ break;
+ case 'D': // a default commodity for "xact"
+ default_commodity_directive(line);
+ break;
+ case 'N': // don't download prices
+ nomarket_directive(line);
+ break;
+ case 'P': // a pricing xact
+ price_xact_directive(line);
+ break;
+ case 'Y': // set the current year
+ year_directive(line);
+ break;
+ }
+ }
+ break;
+ }
+}
+
+#if defined(TIMELOG_SUPPORT)
+
+void instance_t::clock_in_directive(char * line,
+ bool /*capitalized*/)
+{
+ string datetime(line, 2, 19);
+
+ char * p = skip_ws(line + 22);
+ char * n = next_element(p, true);
+ char * end = n ? next_element(n, true) : NULL;
+
+ if (end && *end == ';')
+ end = skip_ws(end + 1);
+ else
+ end = NULL;
+
+ position_t position;
+ position.pathname = pathname;
+ position.beg_pos = line_beg_pos;
+ position.beg_line = linenum;
+ position.end_pos = curr_pos;
+ position.end_line = linenum;
+
+ time_xact_t event(position, parse_datetime(datetime, current_year),
+ p ? top_account()->find_account(p) : NULL,
+ n ? n : "",
+ end ? end : "");
+
+ timelog.clock_in(event);
+}
+
+void instance_t::clock_out_directive(char * line,
+ bool /*capitalized*/)
+{
+ string datetime(line, 2, 19);
+
+ char * p = skip_ws(line + 22);
+ char * n = next_element(p, true);
+ char * end = n ? next_element(n, true) : NULL;
+
+ if (end && *end == ';')
+ end = skip_ws(end + 1);
+ else
+ end = NULL;
+
+ position_t position;
+ position.pathname = pathname;
+ position.beg_pos = line_beg_pos;
+ position.beg_line = linenum;
+ position.end_pos = curr_pos;
+ position.end_line = linenum;
+
+ time_xact_t event(position, parse_datetime(datetime, current_year),
+ p ? top_account()->find_account(p) : NULL,
+ n ? n : "",
+ end ? end : "");
+
+ timelog.clock_out(event);
+ count++;
+}
+
+#endif // TIMELOG_SUPPORT
+
+void instance_t::default_commodity_directive(char * line)
+{
+ amount_t amt(skip_ws(line + 1));
+ VERIFY(amt.valid());
+ commodity_pool_t::current_pool->default_commodity = &amt.commodity();
+ amt.commodity().add_flags(COMMODITY_KNOWN);
+}
+
+void instance_t::default_account_directive(char * line)
+{
+ journal.bucket = top_account()->find_account(skip_ws(line + 1));
+ journal.bucket->add_flags(ACCOUNT_KNOWN);
+}
+
+void instance_t::price_conversion_directive(char * line)
+{
+ if (char * p = std::strchr(line + 1, '=')) {
+ *p++ = '\0';
+ amount_t::parse_conversion(line + 1, p);
+ }
+}
+
+void instance_t::price_xact_directive(char * line)
+{
+ optional<std::pair<commodity_t *, price_point_t> > point =
+ commodity_pool_t::current_pool->parse_price_directive(skip_ws(line + 1));
+ if (! point)
+ throw parse_error(_("Pricing entry failed to parse"));
+}
+
+void instance_t::nomarket_directive(char * line)
+{
+ char * p = skip_ws(line + 1);
+ string symbol;
+ commodity_t::parse_symbol(p, symbol);
+
+ if (commodity_t * commodity =
+ commodity_pool_t::current_pool->find_or_create(symbol))
+ commodity->add_flags(COMMODITY_NOMARKET | COMMODITY_KNOWN);
+}
+
+void instance_t::year_directive(char * line)
+{
+ current_year = lexical_cast<unsigned short>(skip_ws(line + 1));
+}
+
+void instance_t::option_directive(char * line)
+{
+ char * p = next_element(line);
+ if (! p) {
+ p = std::strchr(line, '=');
+ if (p)
+ *p++ = '\0';
+ }
+
+ if (! process_option(pathname.string(), line + 2, scope, p, line))
+ throw_(option_error, _("Illegal option --%1") << line + 2);
+}
+
+void instance_t::automated_xact_directive(char * line)
+{
+ istream_pos_type pos = line_beg_pos;
+ std::size_t lnum = linenum;
+
+ bool reveal_context = true;
+
+ try {
+
+ std::auto_ptr<auto_xact_t> ae
+ (new auto_xact_t(query_t(string(skip_ws(line + 1)),
+ keep_details_t(true, true, true))));
+
+ reveal_context = false;
+
+ if (parse_posts(top_account(), *ae.get(), true)) {
+ reveal_context = true;
+
+ journal.auto_xacts.push_back(ae.get());
+
+ ae->journal = &journal;
+ ae->pos = position_t();
+ ae->pos->pathname = pathname;
+ ae->pos->beg_pos = pos;
+ ae->pos->beg_line = lnum;
+ ae->pos->end_pos = curr_pos;
+ ae->pos->end_line = linenum;
+
+ ae.release();
+ }
+
+ }
+ catch (const std::exception& err) {
+ if (reveal_context) {
+ add_error_context(_("While parsing periodic transaction:"));
+ add_error_context(source_context(pathname, pos, curr_pos, "> "));
+ }
+ throw;
+ }
+}
+
+void instance_t::period_xact_directive(char * line)
+{
+ istream_pos_type pos = line_beg_pos;
+ std::size_t lnum = linenum;
+
+ bool reveal_context = true;
+
+ try {
+
+ std::auto_ptr<period_xact_t> pe(new period_xact_t(skip_ws(line + 1)));
+
+ reveal_context = false;
+
+ if (parse_posts(top_account(), *pe.get())) {
+ reveal_context = true;
+ pe->journal = &journal;
+
+ if (pe->finalize()) {
+ journal.extend_xact(pe.get());
+ journal.period_xacts.push_back(pe.get());
+
+ pe->pos = position_t();
+ pe->pos->pathname = pathname;
+ pe->pos->beg_pos = pos;
+ pe->pos->beg_line = lnum;
+ pe->pos->end_pos = curr_pos;
+ pe->pos->end_line = linenum;
+
+ pe.release();
+ } else {
+ pe->journal = NULL;
+ throw parse_error(_("Period transaction failed to balance"));
+ }
+ }
+
+ }
+ catch (const std::exception& err) {
+ if (reveal_context) {
+ add_error_context(_("While parsing periodic transaction:"));
+ add_error_context(source_context(pathname, pos, curr_pos, "> "));
+ }
+ throw;
+ }
+}
+
+void instance_t::xact_directive(char * line, std::streamsize len)
+{
+ TRACE_START(xacts, 1, "Time spent handling transactions:");
+
+ if (xact_t * xact = parse_xact(line, len, top_account())) {
+ std::auto_ptr<xact_t> manager(xact);
+
+ if (journal.add_xact(xact)) {
+ manager.release(); // it's owned by the journal now
+ count++;
+ }
+ // It's perfectly valid for the journal to reject the xact, which it will
+ // do if the xact has no substantive effect (for example, a checking
+ // xact, all of whose postings have null amounts).
+ } else {
+ throw parse_error(_("Failed to parse transaction"));
+ }
+
+ TRACE_STOP(xacts, 1);
+}
+
+void instance_t::include_directive(char * line)
+{
+ path filename;
+
+ if (line[0] != '/' && line[0] != '\\' && line[0] != '~') {
+ string::size_type pos = pathname.string().rfind('/');
+ if (pos == string::npos)
+ pos = pathname.string().rfind('\\');
+ if (pos != string::npos)
+ filename = path(string(pathname.string(), 0, pos + 1)) / line;
+ } else {
+ filename = line;
+ }
+
+ filename = resolve_path(filename);
+
+ DEBUG("textual.include", "Line " << linenum << ": " <<
+ "Including path '" << filename << "'");
+
+ if (! exists(filename))
+ throw_(std::runtime_error,
+ _("File to include was not found: '%1'") << filename);
+
+ ifstream stream(filename);
+
+ instance_t instance(state_stack,
+#if defined(TIMELOG_SUPPORT)
+ timelog,
+#endif
+ stream, scope, journal, master,
+ &filename, strict, this);
+ instance.parse();
+
+ errors += instance.errors;
+ count += instance.count;
+}
+
+void instance_t::master_account_directive(char * line)
+{
+ if (account_t * acct = top_account()->find_account(line))
+ state_stack.push_front(acct);
+ else
+ assert(! "Failed to create account");
+}
+
+void instance_t::end_directive(char * kind)
+{
+ string name(kind);
+
+ if ((name.empty() || name == "account") && ! front_is_account())
+ throw_(std::runtime_error,
+ _("'end account' directive does not match open directive"));
+ else if (name == "tag" && ! front_is_string())
+ throw_(std::runtime_error,
+ _("'end tag' directive does not match open directive"));
+ else if (name == "fixed" && ! front_is_fixed_rate())
+ throw_(std::runtime_error,
+ _("'end fixed' directive does not match open directive"));
+
+ if (state_stack.size() <= 1)
+ throw_(std::runtime_error,
+ _("'end' found, but no enclosing tag or account directive"));
+ else
+ state_stack.pop_front();
+}
+
+void instance_t::alias_directive(char * line)
+{
+ char * b = skip_ws(line);
+ 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 post
+ // parser to resolve alias references.
+ account_t * acct = top_account()->find_account(e);
+ std::pair<accounts_map::iterator, bool> result
+ = account_aliases.insert(accounts_map::value_type(b, acct));
+ assert(result.second);
+ }
+}
+
+void instance_t::fixed_directive(char * line)
+{
+ if (optional<std::pair<commodity_t *, price_point_t> > price_point =
+ commodity_pool_t::current_pool->parse_price_directive(trim_ws(line),
+ true)) {
+ state_stack.push_front(fixed_rate_t(price_point->first,
+ price_point->second.price));
+ } else {
+ throw_(std::runtime_error, _("Error in fixed directive"));
+ }
+}
+
+void instance_t::tag_directive(char * line)
+{
+ string tag(trim_ws(line));
+
+ if (tag.find(':') == string::npos)
+ tag = string(":") + tag + ":";
+
+ state_stack.push_front(tag);
+}
+
+void instance_t::define_directive(char * line)
+{
+ expr_t def(skip_ws(line));
+ def.compile(scope); // causes definitions to be established
+}
+
+bool instance_t::general_directive(char * line)
+{
+ char buf[8192];
+
+ std::strcpy(buf, line);
+
+ char * p = buf;
+ char * arg = next_element(buf);
+
+ if (*p == '@' || *p == '!')
+ p++;
+
+ switch (*p) {
+ case 'a':
+ if (std::strcmp(p, "account") == 0) {
+ master_account_directive(arg);
+ return true;
+ }
+ else if (std::strcmp(p, "alias") == 0) {
+ alias_directive(arg);
+ return true;
+ }
+ break;
+
+ case 'b':
+ if (std::strcmp(p, "bucket") == 0) {
+ default_account_directive(arg);
+ return true;
+ }
+ break;
+
+ case 'd':
+ if (std::strcmp(p, "def") == 0 || std::strcmp(p, "define") == 0) {
+ define_directive(arg);
+ return true;
+ }
+ break;
+
+ case 'e':
+ if (std::strcmp(p, "end") == 0) {
+ end_directive(arg);
+ return true;
+ }
+ break;
+
+ case 'f':
+ if (std::strcmp(p, "fixed") == 0) {
+ fixed_directive(arg);
+ return true;
+ }
+ break;
+
+ case 'i':
+ if (std::strcmp(p, "include") == 0) {
+ include_directive(arg);
+ return true;
+ }
+ break;
+
+ case 't':
+ if (std::strcmp(p, "tag") == 0) {
+ tag_directive(arg);
+ return true;
+ }
+ break;
+
+ case 'y':
+ if (std::strcmp(p, "year") == 0) {
+ year_directive(arg);
+ return true;
+ }
+ break;
+ }
+
+ if (expr_t::ptr_op_t op = lookup(symbol_t::DIRECTIVE, p)) {
+ call_scope_t args(*this);
+ args.push_back(string_value(p));
+ op->as_function()(args);
+ return true;
+ }
+
+ return false;
+}
+
+post_t * instance_t::parse_post(char * line,
+ std::streamsize len,
+ account_t * account,
+ xact_t * xact,
+ bool honor_strict,
+ bool defer_expr)
+{
+ TRACE_START(post_details, 1, "Time spent parsing postings:");
+
+ std::auto_ptr<post_t> post(new post_t);
+
+ post->xact = xact; // this could be NULL
+ post->pos = position_t();
+ post->pos->pathname = pathname;
+ post->pos->beg_pos = line_beg_pos;
+ post->pos->beg_line = linenum;
+
+ char buf[MAX_LINE + 1];
+ std::strcpy(buf, line);
+ std::size_t beg = 0;
+
+ try {
+
+ // Parse the state flag
+
+ assert(line);
+ assert(*line);
+
+ char * p = skip_ws(line);
+
+ switch (*p) {
+ case '*':
+ post->set_state(item_t::CLEARED);
+ p = skip_ws(p + 1);
+ DEBUG("textual.parse", "line " << linenum << ": "
+ << "Parsed the CLEARED flag");
+ break;
+
+ case '!':
+ post->set_state(item_t::PENDING);
+ p = skip_ws(p + 1);
+ DEBUG("textual.parse", "line " << linenum << ": "
+ << "Parsed the PENDING flag");
+ break;
+ }
+
+ if (xact &&
+ ((xact->_state == item_t::CLEARED && post->_state != item_t::CLEARED) ||
+ (xact->_state == item_t::PENDING && post->_state == item_t::UNCLEARED)))
+ post->set_state(xact->_state);
+
+ // Parse the account name
+
+ if (! *p)
+ throw parse_error(_("Posting has no account"));
+
+ char * next = next_element(p, true);
+ char * e = p + std::strlen(p);
+
+ while (e > p && std::isspace(*(e - 1)))
+ e--;
+
+ if ((*p == '[' && *(e - 1) == ']') || (*p == '(' && *(e - 1) == ')')) {
+ post->add_flags(POST_VIRTUAL);
+ DEBUG("textual.parse", "line " << linenum << ": "
+ << "Parsed a virtual account name");
+
+ if (*p == '[') {
+ post->add_flags(POST_MUST_BALANCE);
+ DEBUG("textual.parse", "line " << linenum << ": "
+ << "Posting must balance");
+ }
+ p++; e--;
+ }
+
+ string name(p, e - p);
+ DEBUG("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())
+ post->account = (*i).second;
+ }
+ if (! post->account)
+ post->account = account->find_account(name);
+
+ if (honor_strict && strict && ! post->account->has_flags(ACCOUNT_KNOWN)) {
+ if (post->_state == item_t::UNCLEARED)
+ warning_(_("\"%1\", line %2: Unknown account '%3'")
+ << pathname << linenum << post->account->fullname());
+ post->account->add_flags(ACCOUNT_KNOWN);
+ }
+
+ // Parse the optional amount
+
+ bool saw_amount = false;
+
+ if (next && *next && (*next != ';' && *next != '=')) {
+ saw_amount = true;
+
+ beg = next - line;
+ ptristream stream(next, len - beg);
+
+ if (*next != '(') // indicates a value expression
+ post->amount.parse(stream, PARSE_NO_REDUCE);
+ else
+ parse_amount_expr(scope, stream, post->amount, &post->amount_expr,
+ post.get(), PARSE_NO_REDUCE | PARSE_SINGLE |
+ PARSE_NO_ASSIGN, defer_expr);
+
+ if (! post->amount.is_null() && post->amount.has_commodity()) {
+ if (honor_strict && strict &&
+ ! post->amount.commodity().has_flags(COMMODITY_KNOWN)) {
+ if (post->_state == item_t::UNCLEARED)
+ warning_(_("\"%1\", line %2: Unknown commodity '%3'")
+ << pathname << linenum << post->amount.commodity());
+ post->amount.commodity().add_flags(COMMODITY_KNOWN);
+ }
+
+ if (! post->amount.has_annotation()) {
+ foreach (state_t& state, state_stack) {
+ if (state.type() == typeid(fixed_rate_t)) {
+ fixed_rate_t& rate(boost::get<fixed_rate_t>(state));
+ if (*rate.first == post->amount.commodity()) {
+ annotation_t details(rate.second);
+ details.add_flags(ANNOTATION_PRICE_FIXATED);
+ post->amount.annotate(details);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ DEBUG("textual.parse", "line " << linenum << ": "
+ << "post amount = " << post->amount);
+
+ if (stream.eof()) {
+ next = NULL;
+ } else {
+ next = skip_ws(next + static_cast<std::ptrdiff_t>(stream.tellg()));
+
+ // Parse the optional cost (@ PER-UNIT-COST, @@ TOTAL-COST)
+
+ if (*next == '@') {
+ DEBUG("textual.parse", "line " << linenum << ": "
+ << "Found a price indicator");
+
+ bool per_unit = true;
+
+ if (*++next == '@') {
+ per_unit = false;
+ DEBUG("textual.parse", "line " << linenum << ": "
+ << "And it's for a total price");
+ }
+
+ beg = ++next - line;
+
+ p = skip_ws(next);
+ if (*p) {
+ post->cost = amount_t();
+
+ beg = p - line;
+ ptristream cstream(p, len - beg);
+
+ if (*p != '(') // indicates a value expression
+ post->cost->parse(cstream, PARSE_NO_MIGRATE);
+ else
+ parse_amount_expr(scope, cstream, *post->cost, NULL, post.get(),
+ PARSE_NO_MIGRATE | PARSE_SINGLE | PARSE_NO_ASSIGN,
+ defer_expr);
+
+ if (post->cost->sign() < 0)
+ throw parse_error(_("A posting's cost may not be negative"));
+
+ post->cost->in_place_unround();
+
+ if (per_unit) {
+ // For the sole case where the cost might be uncommoditized,
+ // guarantee that the commodity of the cost after multiplication
+ // is the same as it was before.
+ commodity_t& cost_commodity(post->cost->commodity());
+ *post->cost *= post->amount;
+ post->cost->set_commodity(cost_commodity);
+ }
+
+ DEBUG("textual.parse", "line " << linenum << ": "
+ << "Total cost is " << *post->cost);
+ DEBUG("textual.parse", "line " << linenum << ": "
+ << "Annotated amount is " << post->amount);
+
+ if (cstream.eof())
+ next = NULL;
+ else
+ next = skip_ws(p + static_cast<std::ptrdiff_t>(cstream.tellg()));
+ } else {
+ throw parse_error(_("Expected a cost amount"));
+ }
+ }
+ }
+ }
+
+ // Parse the optional balance assignment
+
+ if (xact && next && *next == '=') {
+ DEBUG("textual.parse", "line " << linenum << ": "
+ << "Found a balance assignment indicator");
+
+ beg = ++next - line;
+
+ p = skip_ws(next);
+ if (*p) {
+ post->assigned_amount = amount_t();
+
+ beg = p - line;
+ ptristream stream(p, len - beg);
+
+ if (*p != '(') // indicates a value expression
+ post->assigned_amount->parse(stream, PARSE_NO_MIGRATE);
+ else
+ parse_amount_expr(scope, stream, *post->assigned_amount, NULL,
+ post.get(), PARSE_SINGLE | PARSE_NO_MIGRATE,
+ defer_expr);
+
+ if (post->assigned_amount->is_null()) {
+ if (post->amount.is_null())
+ throw parse_error(_("Balance assignment must evaluate to a constant"));
+ else
+ throw parse_error(_("Balance assertion must evaluate to a constant"));
+ }
+
+ DEBUG("textual.parse", "line " << linenum << ": "
+ << "POST assign: parsed amt = " << *post->assigned_amount);
+
+ amount_t& amt(*post->assigned_amount);
+ value_t account_total
+ (post->account->amount(false).strip_annotations(keep_details_t()));
+
+ DEBUG("post.assign",
+ "line " << linenum << ": " "account balance = " << account_total);
+ DEBUG("post.assign",
+ "line " << linenum << ": " "post amount = " << amt);
+
+ amount_t diff = amt;
+
+ switch (account_total.type()) {
+ case value_t::AMOUNT:
+ diff -= account_total.as_amount();
+ break;
+
+ case value_t::BALANCE:
+ if (optional<amount_t> comm_bal =
+ account_total.as_balance().commodity_amount(amt.commodity()))
+ diff -= *comm_bal;
+ break;
+
+ default:
+ break;
+ }
+
+ DEBUG("post.assign",
+ "line " << linenum << ": " << "diff = " << diff);
+ DEBUG("textual.parse",
+ "line " << linenum << ": " << "POST assign: diff = " << diff);
+
+ if (! diff.is_zero()) {
+ if (! post->amount.is_null()) {
+ diff -= post->amount;
+ if (! diff.is_zero())
+ throw_(parse_error, _("Balance assertion off by %1") << diff);
+ } else {
+ post->amount = diff;
+ DEBUG("textual.parse", "line " << linenum << ": "
+ << "Overwrite null posting");
+ }
+ }
+
+ if (stream.eof())
+ next = NULL;
+ else
+ next = skip_ws(p + static_cast<std::ptrdiff_t>(stream.tellg()));
+ } else {
+ throw parse_error(_("Expected an balance assignment/assertion amount"));
+ }
+ }
+
+ // Parse the optional note
+
+ if (next && *next == ';') {
+ post->append_note(++next, current_year);
+ next = line + len;
+ DEBUG("textual.parse", "line " << linenum << ": "
+ << "Parsed a posting note");
+ }
+
+ // There should be nothing more to read
+
+ if (next && *next)
+ throw_(parse_error,
+ _("Unexpected char '%1' (Note: inline math requires parentheses)")
+ << *next);
+
+ post->pos->end_pos = curr_pos;
+ post->pos->end_line = linenum;
+
+ if (! state_stack.empty()) {
+ foreach (const state_t& state, state_stack)
+ if (state.type() == typeid(string))
+ post->parse_tags(boost::get<string>(state).c_str());
+ }
+
+ TRACE_STOP(post_details, 1);
+
+ return post.release();
+
+ }
+ catch (const std::exception& err) {
+ add_error_context(_("While parsing posting:"));
+ add_error_context(line_context(buf, beg, len));
+ throw;
+ }
+}
+
+bool instance_t::parse_posts(account_t * account,
+ xact_base_t& xact,
+ const bool defer_expr)
+{
+ TRACE_START(xact_posts, 1, "Time spent parsing postings:");
+
+ bool added = false;
+
+ while (peek_whitespace_line()) {
+ char * line;
+ std::streamsize len = read_line(line);
+ assert(len > 0);
+
+ if (post_t * post =
+ parse_post(line, len, account, NULL, /* honor_strict= */ false,
+ defer_expr)) {
+ xact.add_post(post);
+ added = true;
+ }
+ }
+
+ TRACE_STOP(xact_posts, 1);
+
+ return added;
+}
+
+xact_t * instance_t::parse_xact(char * line,
+ std::streamsize len,
+ account_t * account)
+{
+ TRACE_START(xact_text, 1, "Time spent parsing transaction text:");
+
+ std::auto_ptr<xact_t> xact(new xact_t);
+
+ xact->pos = position_t();
+ xact->pos->pathname = pathname;
+ xact->pos->beg_pos = line_beg_pos;
+ xact->pos->beg_line = linenum;
+
+ bool reveal_context = true;
+
+ try {
+
+ // Parse the date
+
+ char * next = next_element(line);
+
+ if (char * p = std::strchr(line, '=')) {
+ *p++ = '\0';
+ xact->_date_eff = parse_date(p, current_year);
+ }
+ xact->_date = parse_date(line, current_year);
+
+ // Parse the optional cleared flag: *
+
+ if (next) {
+ switch (*next) {
+ case '*':
+ xact->_state = item_t::CLEARED;
+ next = skip_ws(++next);
+ break;
+ case '!':
+ xact->_state = item_t::PENDING;
+ next = skip_ws(++next);
+ break;
+ }
+ }
+
+ // Parse the optional code: (TEXT)
+
+ if (next && *next == '(') {
+ if (char * p = std::strchr(next++, ')')) {
+ *p++ = '\0';
+ xact->code = next;
+ next = skip_ws(p);
+ }
+ }
+
+ // Parse the description text
+
+ if (next && *next) {
+ char * p = next_element(next, true);
+ xact->payee = next;
+ next = p;
+ } else {
+ xact->payee = _("<Unspecified payee>");
+ }
+
+ // Parse the xact note
+
+ if (next && *next == ';')
+ xact->append_note(++next, current_year);
+
+ TRACE_STOP(xact_text, 1);
+
+ // Parse all of the posts associated with this xact
+
+ TRACE_START(xact_details, 1, "Time spent parsing transaction details:");
+
+ post_t * last_post = NULL;
+
+ while (peek_whitespace_line()) {
+ len = read_line(line);
+
+ char * p = skip_ws(line);
+ if (! *p)
+ break;
+
+ if (*p == ';') {
+ item_t * item;
+ if (last_post)
+ item = last_post;
+ else
+ item = xact.get();
+
+ // This is a trailing note, and possibly a metadata info tag
+ item->append_note(p + 1, current_year);
+ item->pos->end_pos = curr_pos;
+ item->pos->end_line++;
+ } else {
+ reveal_context = false;
+
+ if (post_t * post =
+ parse_post(p, len - (p - line), account, xact.get())) {
+ xact->add_post(post);
+ last_post = post;
+ }
+ }
+ }
+
+ if (xact->_state == item_t::UNCLEARED) {
+ item_t::state_t result = item_t::CLEARED;
+
+ foreach (post_t * post, xact->posts) {
+ if (post->_state == item_t::UNCLEARED) {
+ result = item_t::UNCLEARED;
+ break;
+ }
+ else if (post->_state == item_t::PENDING) {
+ result = item_t::PENDING;
+ }
+ }
+ }
+
+ xact->pos->end_pos = curr_pos;
+ xact->pos->end_line = linenum;
+
+ if (! state_stack.empty()) {
+ foreach (const state_t& state, state_stack)
+ if (state.type() == typeid(string))
+ xact->parse_tags(boost::get<string>(state).c_str());
+ }
+
+ TRACE_STOP(xact_details, 1);
+
+ return xact.release();
+
+ }
+ catch (const std::exception& err) {
+ if (reveal_context) {
+ add_error_context(_("While parsing transaction:"));
+ add_error_context(source_context(xact->pos->pathname,
+ xact->pos->beg_pos, curr_pos, "> "));
+ }
+ throw;
+ }
+}
+
+expr_t::ptr_op_t instance_t::lookup(const symbol_t::kind_t kind,
+ const string& name)
+{
+ return scope.lookup(kind, name);
+}
+
+std::size_t journal_t::parse(std::istream& in,
+ scope_t& scope,
+ account_t * master,
+ const path * original_file,
+ bool strict)
+{
+ TRACE_START(parsing_total, 1, "Total time spent parsing text:");
+
+ std::list<instance_t::state_t> state_stack;
+#if defined(TIMELOG_SUPPORT)
+ time_log_t timelog(*this);
+#endif
+
+ instance_t parsing_instance(state_stack,
+#if defined(TIMELOG_SUPPORT)
+ timelog,
+#endif
+ in, scope, *this, master,
+ original_file, strict);
+ parsing_instance.parse();
+
+ TRACE_STOP(parsing_total, 1);
+
+ // These tracers were started in textual.cc
+ TRACE_FINISH(xact_text, 1);
+ TRACE_FINISH(xact_details, 1);
+ TRACE_FINISH(xact_posts, 1);
+ TRACE_FINISH(xacts, 1);
+ TRACE_FINISH(instance_parse, 1); // report per-instance timers
+ TRACE_FINISH(parsing_total, 1);
+
+ if (parsing_instance.errors > 0)
+ throw static_cast<int>(parsing_instance.errors);
+
+ return parsing_instance.count;
+}
+
+} // namespace ledger
diff --git a/src/timelog.cc b/src/timelog.cc
new file mode 100644
index 00000000..25bf8e94
--- /dev/null
+++ b/src/timelog.cc
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "timelog.h"
+#include "xact.h"
+#include "post.h"
+#include "account.h"
+#include "journal.h"
+
+namespace ledger {
+
+namespace {
+ void clock_out_from_timelog(std::list<time_xact_t>& time_xacts,
+ time_xact_t out_event,
+ journal_t& journal)
+ {
+ time_xact_t event;
+
+ if (time_xacts.size() == 1) {
+ event = time_xacts.back();
+ time_xacts.clear();
+ }
+ else if (time_xacts.empty()) {
+ throw parse_error(_("Timelog check-out event without a check-in"));
+ }
+ else if (! out_event.account) {
+ throw parse_error
+ (_("When multiple check-ins are active, checking out requires an account"));
+ }
+ else {
+ bool found = false;
+
+ for (std::list<time_xact_t>::iterator i = time_xacts.begin();
+ i != time_xacts.end();
+ i++)
+ if (out_event.account == (*i).account) {
+ event = *i;
+ found = true;
+ time_xacts.erase(i);
+ break;
+ }
+
+ if (! found)
+ throw parse_error
+ (_("Timelog check-out event does not match any current check-ins"));
+ }
+
+ if (out_event.checkin < event.checkin)
+ throw parse_error
+ (_("Timelog check-out date less than corresponding check-in"));
+
+ if (! out_event.desc.empty() && event.desc.empty()) {
+ event.desc = out_event.desc;
+ out_event.desc = empty_string;
+ }
+
+ if (! out_event.note.empty() && event.note.empty())
+ event.note = out_event.note;
+
+ std::auto_ptr<xact_t> curr(new xact_t);
+ curr->_date = out_event.checkin.date();
+ curr->code = out_event.desc; // if it wasn't used above
+ curr->payee = event.desc;
+ curr->pos = event.position;
+
+ if (! event.note.empty())
+ curr->append_note(event.note.c_str());
+
+ char buf[32];
+ std::sprintf(buf, "%lds", long((out_event.checkin - event.checkin)
+ .total_seconds()));
+ amount_t amt;
+ amt.parse(buf);
+ VERIFY(amt.valid());
+
+ post_t * post = new post_t(event.account, amt, POST_VIRTUAL);
+ post->set_state(item_t::CLEARED);
+ post->pos = event.position;
+ curr->add_post(post);
+ event.account->add_post(post);
+
+ if (! journal.add_xact(curr.get()))
+ throw parse_error(_("Failed to record 'out' timelog transaction"));
+ else
+ curr.release();
+ }
+} // unnamed namespace
+
+time_log_t::~time_log_t()
+{
+ TRACE_DTOR(time_log_t);
+
+ if (! time_xacts.empty()) {
+ std::list<account_t *> accounts;
+
+ foreach (time_xact_t& time_xact, time_xacts)
+ accounts.push_back(time_xact.account);
+
+ foreach (account_t * account, accounts)
+ clock_out_from_timelog(time_xacts,
+ time_xact_t(none, CURRENT_TIME(), account),
+ journal);
+
+ assert(time_xacts.empty());
+ }
+}
+
+void time_log_t::clock_in(time_xact_t event)
+{
+ if (! time_xacts.empty()) {
+ foreach (time_xact_t& time_xact, time_xacts) {
+ if (event.account == time_xact.account)
+ throw parse_error(_("Cannot double check-in to the same account"));
+ }
+ }
+
+ time_xacts.push_back(event);
+}
+
+void time_log_t::clock_out(time_xact_t event)
+{
+ if (time_xacts.empty())
+ throw std::logic_error(_("Timelog check-out event without a check-in"));
+
+ clock_out_from_timelog(time_xacts, event, journal);
+}
+
+} // namespace ledger
diff --git a/src/timelog.h b/src/timelog.h
new file mode 100644
index 00000000..83256dfa
--- /dev/null
+++ b/src/timelog.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup data
+ */
+
+/**
+ * @file timelog.h
+ * @author John Wiegley
+ *
+ * @ingroup data
+ */
+#ifndef _TIMELOG_H
+#define _TIMELOG_H
+
+#include "utils.h"
+#include "times.h"
+#include "item.h"
+
+namespace ledger {
+
+class account_t;
+class journal_t;
+
+class time_xact_t
+{
+public:
+ datetime_t checkin;
+ account_t * account;
+ string desc;
+ string note;
+ position_t position;
+
+ time_xact_t() : account(NULL) {
+ TRACE_CTOR(time_xact_t, "");
+ }
+ time_xact_t(const optional<position_t>& _position,
+ const datetime_t& _checkin,
+ account_t * _account = NULL,
+ const string& _desc = "",
+ const string& _note = "")
+ : checkin(_checkin), account(_account), desc(_desc), note(_note),
+ position(_position ? *_position : position_t()) {
+ TRACE_CTOR(time_xact_t,
+ "position_t, datetime_t, account_t *, string, string");
+ }
+ time_xact_t(const time_xact_t& xact)
+ : checkin(xact.checkin), account(xact.account),
+ desc(xact.desc), note(xact.note), position(xact.position) {
+ TRACE_CTOR(time_xact_t, "copy");
+ }
+ ~time_xact_t() throw() {
+ TRACE_DTOR(time_xact_t);
+ }
+};
+
+class time_log_t
+{
+ std::list<time_xact_t> time_xacts;
+ journal_t& journal;
+
+public:
+ time_log_t(journal_t& _journal) : journal(_journal) {
+ TRACE_CTOR(time_log_t, "journal_t&");
+ }
+ ~time_log_t();
+
+ void clock_in(time_xact_t event);
+ void clock_out(time_xact_t event);
+};
+
+} // namespace ledger
+
+#endif // _TIMELOG_H
diff --git a/src/times.cc b/src/times.cc
new file mode 100644
index 00000000..d4317509
--- /dev/null
+++ b/src/times.cc
@@ -0,0 +1,1587 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "times.h"
+
+namespace ledger {
+
+optional<datetime_t> epoch;
+
+date_time::weekdays start_of_week = gregorian::Sunday;
+
+//#define USE_BOOST_FACETS 1
+
+namespace {
+ template <typename T, typename InputFacetType, typename OutputFacetType>
+ class temporal_io_t : public noncopyable
+ {
+ const char * fmt_str;
+#if defined(USE_BOOST_FACETS)
+ std::istringstream input_stream;
+ std::ostringstream output_stream;
+ InputFacetType * input_facet;
+ OutputFacetType * output_facet;
+ std::string temp_string;
+#endif // USE_BOOST_FACETS
+
+ public:
+ date_traits_t traits;
+ bool input;
+
+ temporal_io_t(const char * _fmt_str, bool _input)
+ : fmt_str(_fmt_str),
+ traits(icontains(fmt_str, "%y"),
+ icontains(fmt_str, "%m") || icontains(fmt_str, "%b"),
+ icontains(fmt_str, "%d")),
+ input(_input) {
+#if defined(USE_BOOST_FACETS)
+ if (input) {
+ input_facet = new InputFacetType(fmt_str);
+ input_stream.imbue(std::locale(std::locale::classic(), input_facet));
+ } else {
+ output_facet = new OutputFacetType(fmt_str);
+ output_stream.imbue(std::locale(std::locale::classic(), output_facet));
+ }
+#endif // USE_BOOST_FACETS
+ }
+
+ void set_format(const char * fmt) {
+ fmt_str = fmt;
+ traits = date_traits_t(icontains(fmt_str, "%y"),
+ icontains(fmt_str, "%m") ||
+ icontains(fmt_str, "%b"),
+ icontains(fmt_str, "%d"));
+#if defined(USE_BOOST_FACETS)
+ if (input)
+ input_facet->format(fmt_str);
+ else
+ output_facet->format(fmt_str);
+#endif // USE_BOOST_FACETS
+ }
+
+ T parse(const char * str) {
+ }
+
+ std::string format(const T& when) {
+#if defined(USE_BOOST_FACETS)
+ output_stream.str(temp_string);
+ output_stream.seekp(std::ios_base::beg);
+ output_stream.clear();
+ output_stream << when;
+ return output_stream.str();
+#else // USE_BOOST_FACETS
+ std::tm data(to_tm(when));
+ char buf[128];
+ std::strftime(buf, 127, fmt_str, &data);
+ return buf;
+#endif // USE_BOOST_FACETS
+ }
+ };
+
+ template <>
+ datetime_t temporal_io_t<datetime_t, posix_time::time_input_facet,
+ posix_time::time_facet>
+ ::parse(const char * str)
+ {
+#if defined(USE_BOOST_FACETS)
+ input_stream.seekg(std::ios_base::beg);
+ input_stream.clear();
+ input_stream.str(str);
+
+ datetime_t when;
+ input_stream >> when;
+#if defined(DEBUG_ON)
+ if (when.is_not_a_date_time())
+ DEBUG("times.parse", "Failed to parse date/time '" << str
+ << "' using pattern '" << fmt_str << "'");
+#endif
+
+ if (! when.is_not_a_date_time() &&
+ input_stream.good() && ! input_stream.eof() &&
+ input_stream.peek() != EOF) {
+ DEBUG("times.parse", "This string has leftovers: '" << str << "'");
+ return datetime_t();
+ }
+ return when;
+#else // USE_BOOST_FACETS
+ std::tm data;
+ std::memset(&data, 0, sizeof(std::tm));
+ if (strptime(str, fmt_str, &data))
+ return posix_time::ptime_from_tm(data);
+ else
+ return datetime_t();
+#endif // USE_BOOST_FACETS
+ }
+
+ template <>
+ date_t temporal_io_t<date_t, gregorian::date_input_facet,
+ gregorian::date_facet>
+ ::parse(const char * str)
+ {
+#if defined(USE_BOOST_FACETS)
+ input_stream.seekg(std::ios_base::beg);
+ input_stream.clear();
+ input_stream.str(str);
+
+ date_t when;
+ input_stream >> when;
+#if defined(DEBUG_ON)
+ if (when.is_not_a_date())
+ DEBUG("times.parse", "Failed to parse date '" << str
+ << "' using pattern '" << fmt_str << "'");
+#endif
+
+ if (! when.is_not_a_date() &&
+ input_stream.good() && ! input_stream.eof() &&
+ input_stream.peek() != EOF) {
+ DEBUG("times.parse", "This string has leftovers: '" << str << "'");
+ return date_t();
+ }
+ return when;
+#else // USE_BOOST_FACETS
+ std::tm data;
+ std::memset(&data, 0, sizeof(std::tm));
+ data.tm_mday = 1; // some formats have no day
+ if (strptime(str, fmt_str, &data))
+ return gregorian::date_from_tm(data);
+ else
+ return date_t();
+#endif // USE_BOOST_FACETS
+ }
+
+ typedef temporal_io_t<datetime_t, posix_time::time_input_facet,
+ posix_time::time_facet> datetime_io_t;
+ typedef temporal_io_t<date_t, gregorian::date_input_facet,
+ gregorian::date_facet> date_io_t;
+
+ shared_ptr<datetime_io_t> input_datetime_io;
+ shared_ptr<date_io_t> input_date_io;
+ shared_ptr<datetime_io_t> written_datetime_io;
+ shared_ptr<date_io_t> written_date_io;
+ shared_ptr<datetime_io_t> printed_datetime_io;
+ shared_ptr<date_io_t> printed_date_io;
+
+ std::vector<shared_ptr<date_io_t> > readers;
+
+ date_t parse_date_mask_routine(const char * date_str, date_io_t& io,
+ optional_year year,
+ date_traits_t * traits = NULL)
+ {
+ date_t when;
+
+ if (std::strchr(date_str, '/')) {
+ when = io.parse(date_str);
+ } else {
+ char buf[128];
+ VERIFY(std::strlen(date_str) < 127);
+ std::strcpy(buf, date_str);
+
+ for (char * p = buf; *p; p++)
+ if (*p == '.' || *p == '-')
+ *p = '/';
+
+ when = io.parse(buf);
+ }
+
+ if (! when.is_not_a_date()) {
+ DEBUG("times.parse", "Parsed date string: " << date_str);
+ DEBUG("times.parse", "Parsed result is: " << when);
+
+ if (traits)
+ *traits = io.traits;
+
+ if (! io.traits.has_year) {
+ when = date_t(year ? *year : CURRENT_DATE().year(),
+ when.month(), when.day());
+
+ if (when.month() > CURRENT_DATE().month())
+ when -= gregorian::years(1);
+ }
+ }
+ return when;
+ }
+
+ date_t parse_date_mask(const char * date_str, optional_year year,
+ date_traits_t * traits = NULL)
+ {
+ if (input_date_io.get()) {
+ date_t when = parse_date_mask_routine(date_str, *input_date_io.get(),
+ year, traits);
+ if (! when.is_not_a_date())
+ return when;
+ }
+
+ foreach (shared_ptr<date_io_t>& reader, readers) {
+ date_t when = parse_date_mask_routine(date_str, *reader.get(),
+ year, traits);
+ if (! when.is_not_a_date())
+ return when;
+ }
+
+ throw_(date_error, _("Invalid date: %1") << date_str);
+ return date_t();
+ }
+}
+
+optional<date_time::weekdays> string_to_day_of_week(const std::string& str)
+{
+ if (str == _("sun") || str == _("sunday") || str == "0")
+ return gregorian::Sunday;
+ else if (str == _("mon") || str == _("monday") || str == "1")
+ return gregorian::Monday;
+ else if (str == _("tue") || str == _("tuesday") || str == "2")
+ return gregorian::Tuesday;
+ else if (str == _("wed") || str == _("wednesday") || str == "3")
+ return gregorian::Wednesday;
+ else if (str == _("thu") || str == _("thursday") || str == "4")
+ return gregorian::Thursday;
+ else if (str == _("fri") || str == _("friday") || str == "5")
+ return gregorian::Friday;
+ else if (str == _("sat") || str == _("saturday") || str == "6")
+ return gregorian::Saturday;
+ else
+ return none;
+}
+
+optional<date_time::months_of_year>
+string_to_month_of_year(const std::string& str)
+{
+ if (str == _("jan") || str == _("january") || str == "0")
+ return gregorian::Jan;
+ else if (str == _("feb") || str == _("february") || str == "1")
+ return gregorian::Feb;
+ else if (str == _("mar") || str == _("march") || str == "2")
+ return gregorian::Mar;
+ else if (str == _("apr") || str == _("april") || str == "3")
+ return gregorian::Apr;
+ else if (str == _("may") || str == _("may") || str == "4")
+ return gregorian::May;
+ else if (str == _("jun") || str == _("june") || str == "5")
+ return gregorian::Jun;
+ else if (str == _("jul") || str == _("july") || str == "6")
+ return gregorian::Jul;
+ else if (str == _("aug") || str == _("august") || str == "7")
+ return gregorian::Aug;
+ else if (str == _("sep") || str == _("september") || str == "8")
+ return gregorian::Sep;
+ else if (str == _("oct") || str == _("october") || str == "9")
+ return gregorian::Oct;
+ else if (str == _("nov") || str == _("november") || str == "10")
+ return gregorian::Nov;
+ else if (str == _("dec") || str == _("december") || str == "11")
+ return gregorian::Dec;
+ else
+ return none;
+}
+
+datetime_t parse_datetime(const char * str, optional_year)
+{
+ datetime_t when = input_datetime_io->parse(str);
+ if (when.is_not_a_date_time())
+ throw_(date_error, _("Invalid date/time: %1") << str);
+ return when;
+}
+
+date_t parse_date(const char * str, optional_year current_year)
+{
+ return parse_date_mask(str, current_year);
+}
+
+date_t date_specifier_t::begin(const optional_year& current_year) const
+{
+ assert(year || current_year);
+
+ year_type the_year = year ? *year : static_cast<year_type>(*current_year);
+ month_type the_month = month ? *month : date_t::month_type(1);
+ day_type the_day = day ? *day : date_t::day_type(1);
+
+ if (day)
+ assert(! wday);
+ else if (wday)
+ assert(! day);
+
+ // jww (2009-11-16): Handle wday. If a month is set, find the most recent
+ // wday in that month; if the year is set, then in that year.
+
+ return gregorian::date(static_cast<date_t::year_type>(the_year),
+ static_cast<date_t::month_type>(the_month),
+ static_cast<date_t::day_type>(the_day));
+}
+
+date_t date_specifier_t::end(const optional_year& current_year) const
+{
+ if (day || wday)
+ return begin(current_year) + gregorian::days(1);
+ else if (month)
+ return begin(current_year) + gregorian::months(1);
+ else if (year)
+ return begin(current_year) + gregorian::years(1);
+ else {
+ assert(false);
+ return date_t();
+ }
+}
+
+std::ostream& operator<<(std::ostream& out,
+ const date_duration_t& duration)
+{
+ if (duration.quantum == date_duration_t::DAYS)
+ out << duration.length << " day(s)";
+ else if (duration.quantum == date_duration_t::WEEKS)
+ out << duration.length << " week(s)";
+ else if (duration.quantum == date_duration_t::MONTHS)
+ out << duration.length << " month(s)";
+ else if (duration.quantum == date_duration_t::QUARTERS)
+ out << duration.length << " quarter(s)";
+ else {
+ assert(duration.quantum == date_duration_t::YEARS);
+ out << duration.length << " year(s)";
+ }
+ return out;
+}
+
+class date_parser_t
+{
+ friend void show_period_tokens(std::ostream& out, const string& arg);
+
+ class lexer_t
+ {
+ friend class date_parser_t;
+
+ string::const_iterator begin;
+ string::const_iterator end;
+
+ public:
+ struct token_t
+ {
+ enum kind_t {
+ UNKNOWN,
+
+ TOK_DATE,
+ TOK_INT,
+ TOK_SLASH,
+ TOK_DASH,
+ TOK_DOT,
+
+ TOK_A_YEAR,
+ TOK_A_MONTH,
+ TOK_A_WDAY,
+
+ TOK_SINCE,
+ TOK_UNTIL,
+ TOK_IN,
+ TOK_THIS,
+ TOK_NEXT,
+ TOK_LAST,
+ TOK_EVERY,
+
+ TOK_TODAY,
+ TOK_TOMORROW,
+ TOK_YESTERDAY,
+
+ TOK_YEAR,
+ TOK_QUARTER,
+ TOK_MONTH,
+ TOK_WEEK,
+ TOK_DAY,
+
+ TOK_YEARLY,
+ TOK_QUARTERLY,
+ TOK_BIMONTHLY,
+ TOK_MONTHLY,
+ TOK_BIWEEKLY,
+ TOK_WEEKLY,
+ TOK_DAILY,
+
+ TOK_YEARS,
+ TOK_QUARTERS,
+ TOK_MONTHS,
+ TOK_WEEKS,
+ TOK_DAYS,
+
+ END_REACHED
+
+ } kind;
+
+ typedef variant<unsigned short,
+ string,
+ date_specifier_t::year_type,
+ date_time::months_of_year,
+ date_time::weekdays,
+ date_specifier_t> content_t;
+
+ optional<content_t> value;
+
+ explicit token_t(kind_t _kind = UNKNOWN,
+ const optional<content_t>& _value =
+ content_t(empty_string))
+ : kind(_kind), value(_value) {
+ TRACE_CTOR(date_parser_t::lexer_t::token_t, "");
+ }
+ token_t(const token_t& tok)
+ : kind(tok.kind), value(tok.value) {
+ TRACE_CTOR(date_parser_t::lexer_t::token_t, "copy");
+ }
+ ~token_t() throw() {
+ TRACE_DTOR(date_parser_t::lexer_t::token_t);
+ }
+
+ token_t& operator=(const token_t& tok) {
+ if (this != &tok) {
+ kind = tok.kind;
+ value = tok.value;
+ }
+ return *this;
+ }
+
+ operator bool() const {
+ return kind != END_REACHED;
+ }
+
+ string to_string() const {
+ std::ostringstream out;
+
+ switch (kind) {
+ case UNKNOWN:
+ out << boost::get<string>(*value);
+ break;
+ case TOK_DATE:
+ return boost::get<date_specifier_t>(*value).to_string();
+ case TOK_INT:
+ out << boost::get<unsigned short>(*value);
+ break;
+ case TOK_SLASH: return "/";
+ case TOK_DASH: return "-";
+ case TOK_DOT: return ".";
+ case TOK_A_YEAR:
+ out << boost::get<date_specifier_t::year_type>(*value);
+ break;
+ case TOK_A_MONTH:
+ out << date_specifier_t::month_type
+ (boost::get<date_time::months_of_year>(*value));
+ break;
+ case TOK_A_WDAY:
+ out << date_specifier_t::day_of_week_type
+ (boost::get<date_time::weekdays>(*value));
+ break;
+ case TOK_SINCE: return "since";
+ case TOK_UNTIL: return "until";
+ case TOK_IN: return "in";
+ case TOK_THIS: return "this";
+ case TOK_NEXT: return "next";
+ case TOK_LAST: return "last";
+ case TOK_EVERY: return "every";
+ case TOK_TODAY: return "today";
+ case TOK_TOMORROW: return "tomorrow";
+ case TOK_YESTERDAY: return "yesterday";
+ case TOK_YEAR: return "year";
+ case TOK_QUARTER: return "quarter";
+ case TOK_MONTH: return "month";
+ case TOK_WEEK: return "week";
+ case TOK_DAY: return "day";
+ case TOK_YEARLY: return "yearly";
+ case TOK_QUARTERLY: return "quarterly";
+ case TOK_BIMONTHLY: return "bimonthly";
+ case TOK_MONTHLY: return "monthly";
+ case TOK_BIWEEKLY: return "biweekly";
+ case TOK_WEEKLY: return "weekly";
+ case TOK_DAILY: return "daily";
+ case TOK_YEARS: return "years";
+ case TOK_QUARTERS: return "quarters";
+ case TOK_MONTHS: return "months";
+ case TOK_WEEKS: return "weeks";
+ case TOK_DAYS: return "days";
+ case END_REACHED: return "<EOF>";
+ default:
+ assert(false);
+ return empty_string;
+ }
+
+ return out.str();
+ }
+
+ void dump(std::ostream& out) const {
+ switch (kind) {
+ case UNKNOWN: out << "UNKNOWN"; break;
+ case TOK_DATE: out << "TOK_DATE"; break;
+ case TOK_INT: out << "TOK_INT"; break;
+ case TOK_SLASH: out << "TOK_SLASH"; break;
+ case TOK_DASH: out << "TOK_DASH"; break;
+ case TOK_DOT: out << "TOK_DOT"; break;
+ case TOK_A_YEAR: out << "TOK_A_YEAR"; break;
+ case TOK_A_MONTH: out << "TOK_A_MONTH"; break;
+ case TOK_A_WDAY: out << "TOK_A_WDAY"; break;
+ case TOK_SINCE: out << "TOK_SINCE"; break;
+ case TOK_UNTIL: out << "TOK_UNTIL"; break;
+ case TOK_IN: out << "TOK_IN"; break;
+ case TOK_THIS: out << "TOK_THIS"; break;
+ case TOK_NEXT: out << "TOK_NEXT"; break;
+ case TOK_LAST: out << "TOK_LAST"; break;
+ case TOK_EVERY: out << "TOK_EVERY"; break;
+ case TOK_TODAY: out << "TOK_TODAY"; break;
+ case TOK_TOMORROW: out << "TOK_TOMORROW"; break;
+ case TOK_YESTERDAY: out << "TOK_YESTERDAY"; break;
+ case TOK_YEAR: out << "TOK_YEAR"; break;
+ case TOK_QUARTER: out << "TOK_QUARTER"; break;
+ case TOK_MONTH: out << "TOK_MONTH"; break;
+ case TOK_WEEK: out << "TOK_WEEK"; break;
+ case TOK_DAY: out << "TOK_DAY"; break;
+ case TOK_YEARLY: out << "TOK_YEARLY"; break;
+ case TOK_QUARTERLY: out << "TOK_QUARTERLY"; break;
+ case TOK_BIMONTHLY: out << "TOK_BIMONTHLY"; break;
+ case TOK_MONTHLY: out << "TOK_MONTHLY"; break;
+ case TOK_BIWEEKLY: out << "TOK_BIWEEKLY"; break;
+ case TOK_WEEKLY: out << "TOK_WEEKLY"; break;
+ case TOK_DAILY: out << "TOK_DAILY"; break;
+ case TOK_YEARS: out << "TOK_YEARS"; break;
+ case TOK_QUARTERS: out << "TOK_QUARTERS"; break;
+ case TOK_MONTHS: out << "TOK_MONTHS"; break;
+ case TOK_WEEKS: out << "TOK_WEEKS"; break;
+ case TOK_DAYS: out << "TOK_DAYS"; break;
+ case END_REACHED: out << "END_REACHED"; break;
+ default:
+ assert(false);
+ break;
+ }
+ }
+
+ void unexpected();
+ static void expected(char wanted, char c = '\0');
+ };
+
+ token_t token_cache;
+
+ lexer_t(string::const_iterator _begin,
+ string::const_iterator _end)
+ : begin(_begin), end(_end)
+ {
+ TRACE_CTOR(date_parser_t::lexer_t, "");
+ }
+ lexer_t(const lexer_t& lexer)
+ : begin(lexer.begin), end(lexer.end),
+ token_cache(lexer.token_cache)
+ {
+ TRACE_CTOR(date_parser_t::lexer_t, "copy");
+ }
+ ~lexer_t() throw() {
+ TRACE_DTOR(date_parser_t::lexer_t);
+ }
+
+ token_t next_token();
+ void push_token(token_t tok) {
+ assert(token_cache.kind == token_t::UNKNOWN);
+ token_cache = tok;
+ }
+ token_t peek_token() {
+ if (token_cache.kind == token_t::UNKNOWN)
+ token_cache = next_token();
+ return token_cache;
+ }
+ };
+
+ string arg;
+ lexer_t lexer;
+
+public:
+ date_parser_t(const string& _arg)
+ : arg(_arg), lexer(arg.begin(), arg.end()) {
+ TRACE_CTOR(date_parser_t, "");
+ }
+ date_parser_t(const date_parser_t& parser)
+ : arg(parser.arg), lexer(parser.lexer) {
+ TRACE_CTOR(date_parser_t, "copy");
+ }
+ ~date_parser_t() throw() {
+ TRACE_DTOR(date_parser_t);
+ }
+
+ date_interval_t parse();
+
+private:
+ void determine_when(lexer_t::token_t& tok, date_specifier_t& specifier);
+};
+
+void date_parser_t::determine_when(date_parser_t::lexer_t::token_t& tok,
+ date_specifier_t& specifier)
+{
+ switch (tok.kind) {
+ case lexer_t::token_t::TOK_DATE:
+ specifier = boost::get<date_specifier_t>(*tok.value);
+ break;
+
+ case lexer_t::token_t::TOK_INT:
+ specifier.day =
+ date_specifier_t::day_type(boost::get<unsigned short>(*tok.value));
+ break;
+ case lexer_t::token_t::TOK_A_YEAR:
+ specifier.year = boost::get<date_specifier_t::year_type>(*tok.value);
+ break;
+ case lexer_t::token_t::TOK_A_MONTH:
+ specifier.month =
+ date_specifier_t::month_type
+ (boost::get<date_time::months_of_year>(*tok.value));
+ break;
+ case lexer_t::token_t::TOK_A_WDAY:
+ specifier.wday =
+ date_specifier_t::day_of_week_type
+ (boost::get<date_time::weekdays>(*tok.value));
+ break;
+
+ default:
+ tok.unexpected();
+ break;
+ }
+}
+
+date_interval_t date_parser_t::parse()
+{
+ optional<date_specifier_t> since_specifier;
+ optional<date_specifier_t> until_specifier;
+ optional<date_specifier_t> inclusion_specifier;
+
+ date_interval_t period;
+ date_t today = CURRENT_DATE();
+ bool end_inclusive = false;
+
+ for (lexer_t::token_t tok = lexer.next_token();
+ tok.kind != lexer_t::token_t::END_REACHED;
+ tok = lexer.next_token()) {
+ switch (tok.kind) {
+#if 0
+ case lexer_t::token_t::TOK_INT:
+ // jww (2009-11-18): NYI
+ assert(! "Need to allow for expressions like \"4 months ago\"");
+ tok.unexpected();
+ break;
+#endif
+
+ case lexer_t::token_t::TOK_DATE:
+ if (! inclusion_specifier)
+ inclusion_specifier = date_specifier_t();
+ determine_when(tok, *inclusion_specifier);
+ break;
+
+ case lexer_t::token_t::TOK_INT:
+ if (! inclusion_specifier)
+ inclusion_specifier = date_specifier_t();
+ determine_when(tok, *inclusion_specifier);
+ break;
+
+ case lexer_t::token_t::TOK_A_YEAR:
+ if (! inclusion_specifier)
+ inclusion_specifier = date_specifier_t();
+ determine_when(tok, *inclusion_specifier);
+ break;
+
+ case lexer_t::token_t::TOK_A_MONTH:
+ if (! inclusion_specifier)
+ inclusion_specifier = date_specifier_t();
+ determine_when(tok, *inclusion_specifier);
+ break;
+
+ case lexer_t::token_t::TOK_A_WDAY:
+ if (! inclusion_specifier)
+ inclusion_specifier = date_specifier_t();
+ determine_when(tok, *inclusion_specifier);
+ break;
+
+ case lexer_t::token_t::TOK_DASH:
+ if (inclusion_specifier) {
+ since_specifier = inclusion_specifier;
+ until_specifier = date_specifier_t();
+ inclusion_specifier = none;
+
+ tok = lexer.next_token();
+ determine_when(tok, *until_specifier);
+
+ // The dash operator is special: it has an _inclusive_ end.
+ end_inclusive = true;
+ } else {
+ tok.unexpected();
+ }
+ break;
+
+ case lexer_t::token_t::TOK_SINCE:
+ if (since_specifier) {
+ tok.unexpected();
+ } else {
+ since_specifier = date_specifier_t();
+ tok = lexer.next_token();
+ determine_when(tok, *since_specifier);
+ }
+ break;
+
+ case lexer_t::token_t::TOK_UNTIL:
+ if (until_specifier) {
+ tok.unexpected();
+ } else {
+ until_specifier = date_specifier_t();
+ tok = lexer.next_token();
+ determine_when(tok, *until_specifier);
+ }
+ break;
+
+ case lexer_t::token_t::TOK_IN:
+ if (inclusion_specifier) {
+ tok.unexpected();
+ } else {
+ inclusion_specifier = date_specifier_t();
+ tok = lexer.next_token();
+ determine_when(tok, *inclusion_specifier);
+ }
+ break;
+
+ case lexer_t::token_t::TOK_THIS:
+ case lexer_t::token_t::TOK_NEXT:
+ case lexer_t::token_t::TOK_LAST: {
+ int8_t adjust = 0;
+ if (tok.kind == lexer_t::token_t::TOK_NEXT)
+ adjust = 1;
+ else if (tok.kind == lexer_t::token_t::TOK_LAST)
+ adjust = -1;
+
+ tok = lexer.next_token();
+ switch (tok.kind) {
+ case lexer_t::token_t::TOK_INT:
+ // jww (2009-11-18): Allow things like "last 5 weeks"
+ assert(! "Need to allow for expressions like \"last 5 weeks\"");
+ tok.unexpected();
+ break;
+
+ case lexer_t::token_t::TOK_A_MONTH: {
+ inclusion_specifier = date_specifier_t();
+ determine_when(tok, *inclusion_specifier);
+
+ date_t temp(today.year(), *inclusion_specifier->month, 1);
+ temp += gregorian::years(adjust);
+ inclusion_specifier =
+ date_specifier_t(static_cast<date_specifier_t::year_type>(temp.year()),
+ temp.month());
+ break;
+ }
+
+ case lexer_t::token_t::TOK_A_WDAY: {
+ inclusion_specifier = date_specifier_t();
+ determine_when(tok, *inclusion_specifier);
+
+ date_t temp =
+ date_duration_t::find_nearest(today, date_duration_t::WEEKS);
+ while (temp.day_of_week() != inclusion_specifier->wday)
+ temp += gregorian::days(1);
+ temp += gregorian::days(7 * adjust);
+ inclusion_specifier = date_specifier_t(temp);
+ break;
+ }
+
+ case lexer_t::token_t::TOK_YEAR: {
+ date_t temp(today);
+ temp += gregorian::years(adjust);
+ inclusion_specifier =
+ date_specifier_t(static_cast<date_specifier_t::year_type>(temp.year()));
+ break;
+ }
+
+ case lexer_t::token_t::TOK_QUARTER: {
+ date_t temp =
+ date_duration_t::find_nearest(today, date_duration_t::QUARTERS);
+ temp += gregorian::months(3 * adjust);
+ inclusion_specifier =
+ date_specifier_t(static_cast<date_specifier_t::year_type>(temp.year()),
+ temp.month());
+#if 0
+ period.duration = date_duration_t(date_duration_t::QUARTERS, 1);
+#endif
+ break;
+ }
+
+ case lexer_t::token_t::TOK_MONTH: {
+ date_t temp(today);
+ temp += gregorian::months(adjust);
+ inclusion_specifier =
+ date_specifier_t(static_cast<date_specifier_t::year_type>(temp.year()),
+ temp.month());
+ break;
+ }
+
+ case lexer_t::token_t::TOK_WEEK: {
+ date_t temp =
+ date_duration_t::find_nearest(today, date_duration_t::WEEKS);
+ temp += gregorian::days(7 * adjust);
+ inclusion_specifier = date_specifier_t(today);
+#if 0
+ period.duration = date_duration_t(date_duration_t::WEEKS, 1);
+#endif
+ break;
+ }
+
+ case lexer_t::token_t::TOK_DAY: {
+ date_t temp(today);
+ temp += gregorian::days(adjust);
+ inclusion_specifier = date_specifier_t(temp);
+ break;
+ }
+
+ default:
+ tok.unexpected();
+ break;
+ }
+ break;
+ }
+
+ case lexer_t::token_t::TOK_TODAY:
+ inclusion_specifier = date_specifier_t(today);
+ break;
+ case lexer_t::token_t::TOK_TOMORROW:
+ inclusion_specifier = date_specifier_t(today + gregorian::days(1));
+ break;
+ case lexer_t::token_t::TOK_YESTERDAY:
+ inclusion_specifier = date_specifier_t(today - gregorian::days(1));
+ break;
+
+ case lexer_t::token_t::TOK_EVERY:
+ tok = lexer.next_token();
+ if (tok == lexer_t::token_t::TOK_INT) {
+ int quantity = boost::get<unsigned short>(*tok.value);
+ tok = lexer.next_token();
+ switch (tok.kind) {
+ case lexer_t::token_t::TOK_YEARS:
+ period.duration = date_duration_t(date_duration_t::YEARS, quantity);
+ break;
+ case lexer_t::token_t::TOK_QUARTERS:
+ period.duration = date_duration_t(date_duration_t::QUARTERS, quantity);
+ break;
+ case lexer_t::token_t::TOK_MONTHS:
+ period.duration = date_duration_t(date_duration_t::MONTHS, quantity);
+ break;
+ case lexer_t::token_t::TOK_WEEKS:
+ period.duration = date_duration_t(date_duration_t::WEEKS, quantity);
+ break;
+ case lexer_t::token_t::TOK_DAYS:
+ period.duration = date_duration_t(date_duration_t::DAYS, quantity);
+ break;
+ default:
+ tok.unexpected();
+ break;
+ }
+ } else {
+ switch (tok.kind) {
+ case lexer_t::token_t::TOK_YEAR:
+ period.duration = date_duration_t(date_duration_t::YEARS, 1);
+ break;
+ case lexer_t::token_t::TOK_QUARTER:
+ period.duration = date_duration_t(date_duration_t::QUARTERS, 1);
+ break;
+ case lexer_t::token_t::TOK_MONTH:
+ period.duration = date_duration_t(date_duration_t::MONTHS, 1);
+ break;
+ case lexer_t::token_t::TOK_WEEK:
+ period.duration = date_duration_t(date_duration_t::WEEKS, 1);
+ break;
+ case lexer_t::token_t::TOK_DAY:
+ period.duration = date_duration_t(date_duration_t::DAYS, 1);
+ break;
+ default:
+ tok.unexpected();
+ break;
+ }
+ }
+ break;
+
+ case lexer_t::token_t::TOK_YEARLY:
+ period.duration = date_duration_t(date_duration_t::YEARS, 1);
+ break;
+ case lexer_t::token_t::TOK_QUARTERLY:
+ period.duration = date_duration_t(date_duration_t::QUARTERS, 1);
+ break;
+ case lexer_t::token_t::TOK_BIMONTHLY:
+ period.duration = date_duration_t(date_duration_t::MONTHS, 2);
+ break;
+ case lexer_t::token_t::TOK_MONTHLY:
+ period.duration = date_duration_t(date_duration_t::MONTHS, 1);
+ break;
+ case lexer_t::token_t::TOK_BIWEEKLY:
+ period.duration = date_duration_t(date_duration_t::WEEKS, 2);
+ break;
+ case lexer_t::token_t::TOK_WEEKLY:
+ period.duration = date_duration_t(date_duration_t::WEEKS, 1);
+ break;
+ case lexer_t::token_t::TOK_DAILY:
+ period.duration = date_duration_t(date_duration_t::DAYS, 1);
+ break;
+
+ default:
+ tok.unexpected();
+ break;
+ }
+ }
+
+#if 0
+ if (! period.duration && inclusion_specifier)
+ period.duration = inclusion_specifier->implied_duration();
+#endif
+
+ if (since_specifier || until_specifier) {
+ date_range_t range(since_specifier, until_specifier);
+ range.end_inclusive = end_inclusive;
+
+ period.range = date_specifier_or_range_t(range);
+ }
+ else if (inclusion_specifier) {
+ period.range = date_specifier_or_range_t(*inclusion_specifier);
+ }
+ else {
+ /* otherwise, it's something like "monthly", with no date reference */
+ }
+
+ return period;
+}
+
+void date_interval_t::parse(const string& str)
+{
+ date_parser_t parser(str);
+ *this = parser.parse();
+}
+
+void date_interval_t::resolve_end()
+{
+ if (start && ! end_of_duration) {
+ end_of_duration = duration->add(*start);
+ DEBUG("times.interval",
+ "stabilize: end_of_duration = " << *end_of_duration);
+ }
+
+ if (finish && *end_of_duration > *finish) {
+ end_of_duration = finish;
+ DEBUG("times.interval",
+ "stabilize: end_of_duration reset to end: " << *end_of_duration);
+ }
+
+ if (start && ! next) {
+ next = end_of_duration;
+ DEBUG("times.interval", "stabilize: next set to: " << *next);
+ }
+}
+
+date_t date_duration_t::find_nearest(const date_t& date, skip_quantum_t skip)
+{
+ date_t result;
+
+ switch (skip) {
+ case date_duration_t::YEARS:
+ result = date_t(date.year(), gregorian::Jan, 1);
+ break;
+ case date_duration_t::QUARTERS:
+ result = date_t(date.year(), date.month(), 1);
+ while (result.month() != gregorian::Jan &&
+ result.month() != gregorian::Apr &&
+ result.month() != gregorian::Jul &&
+ result.month() != gregorian::Oct)
+ result -= gregorian::months(1);
+ break;
+ case date_duration_t::MONTHS:
+ result = date_t(date.year(), date.month(), 1);
+ break;
+ case date_duration_t::WEEKS:
+ result = date;
+ while (result.day_of_week() != start_of_week)
+ result -= gregorian::days(1);
+ break;
+ case date_duration_t::DAYS:
+ result = date;
+ break;
+ default:
+ assert(false);
+ break;
+ }
+ return result;
+}
+
+void date_interval_t::stabilize(const optional<date_t>& date)
+{
+#if defined(DEBUG_ON)
+ if (date)
+ DEBUG("times.interval", "stabilize: with date = " << *date);
+#endif
+
+ if (date && ! aligned) {
+ DEBUG("times.interval", "stabilize: date passed, but not aligned");
+ if (duration) {
+ DEBUG("times.interval",
+ "stabilize: aligning with a duration: " << *duration);
+
+ // The interval object has not been seeded with a start date yet, so
+ // find the nearest period before on on date which fits, if possible.
+ //
+ // 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.
+ optional<date_t> initial_start = start ? start : begin(date->year());
+ optional<date_t> initial_finish = finish ? finish : end(date->year());
+
+#if defined(DEBUG_ON)
+ if (initial_start)
+ DEBUG("times.interval",
+ "stabilize: initial_start = " << *initial_start);
+ if (initial_finish)
+ DEBUG("times.interval",
+ "stabilize: initial_finish = " << *initial_finish);
+#endif
+
+ date_t when = start ? *start : *date;
+
+ if (duration->quantum == date_duration_t::MONTHS ||
+ duration->quantum == date_duration_t::QUARTERS ||
+ duration->quantum == date_duration_t::YEARS) {
+ DEBUG("times.interval",
+ "stabilize: monthly, quarterly or yearly duration");
+ start = date_duration_t::find_nearest(when, duration->quantum);
+ } else {
+ DEBUG("times.interval", "stabilize: daily or weekly duration");
+ start = date_duration_t::find_nearest(when - gregorian::days(400),
+ duration->quantum);
+ }
+
+ DEBUG("times.interval",
+ "stabilize: beginning start date = " << *start);
+
+ while (*start < *date) {
+ date_interval_t next_interval(*this);
+ ++next_interval;
+
+ if (next_interval.start && *next_interval.start < *date) {
+ *this = next_interval;
+ } else {
+ end_of_duration = none;
+ next = none;
+ break;
+ }
+ }
+
+ DEBUG("times.interval", "stabilize: proposed start date = " << *start);
+
+ if (initial_start && (! start || *start < *initial_start)) {
+ // Using the discovered start, find the end of the period
+ resolve_end();
+
+ start = initial_start;
+ DEBUG("times.interval", "stabilize: start reset to initial start");
+ }
+ if (initial_finish && (! finish || *finish > *initial_finish)) {
+ finish = initial_finish;
+ DEBUG("times.interval", "stabilize: finish reset to initial finish");
+ }
+
+#if defined(DEBUG_ON)
+ if (start)
+ DEBUG("times.interval", "stabilize: final start = " << *start);
+ if (finish)
+ DEBUG("times.interval", "stabilize: final finish = " << *finish);
+#endif
+ }
+ else if (range) {
+ if (date) {
+ start = range->begin(date->year());
+ finish = range->end(date->year());
+ } else {
+ start = range->begin();
+ finish = range->end();
+ }
+ }
+ aligned = true;
+ }
+
+ // If there is no duration, then if we've reached here the date falls
+ // between start and finish.
+ if (! duration) {
+ DEBUG("times.interval", "stabilize: there was no duration given");
+
+ if (! start && ! finish)
+ throw_(date_error,
+ _("Invalid date interval: neither start, nor finish, nor duration"));
+ } else {
+ resolve_end();
+ }
+}
+
+bool date_interval_t::find_period(const date_t& date)
+{
+ stabilize(date);
+
+ if (finish && date > *finish) {
+ DEBUG("times.interval",
+ "false: date [" << date << "] > finish [" << *finish << "]");
+ return false;
+ }
+
+ if (! start) {
+ throw_(std::runtime_error, _("Date interval is improperly initialized"));
+ }
+ else if (date < *start) {
+ DEBUG("times.interval",
+ "false: date [" << date << "] < start [" << *start << "]");
+ return false;
+ }
+
+ if (end_of_duration) {
+ if (date < *end_of_duration) {
+ DEBUG("times.interval",
+ "true: date [" << date << "] < end_of_duration ["
+ << *end_of_duration << "]");
+ return true;
+ }
+ } else {
+ DEBUG("times.interval", "false: there is no end_of_duration");
+ return false;
+ }
+
+ // If we've reached here, it means the date does not fall into the current
+ // interval, so we must seek another interval that does match -- unless we
+ // pass by date in so doing, which means we shouldn't alter the current
+ // period of the interval at all.
+
+ date_t scan = *start;
+ date_t end_of_scan = *end_of_duration;
+
+ DEBUG("times.interval", "date = " << date);
+ DEBUG("times.interval", "scan = " << scan);
+ DEBUG("times.interval", "end_of_scan = " << end_of_scan);
+
+ while (date >= scan && (! finish || scan < *finish)) {
+ if (date < end_of_scan) {
+ start = scan;
+ end_of_duration = end_of_scan;
+ next = none;
+
+ DEBUG("times.interval", "true: start = " << *start);
+ DEBUG("times.interval", "true: end_of_duration = " << *end_of_duration);
+
+ return true;
+ }
+
+ scan = duration->add(scan);
+ end_of_scan = duration->add(scan);
+ }
+
+ return false;
+}
+
+date_interval_t& date_interval_t::operator++()
+{
+ if (! start)
+ throw_(date_error, _("Cannot increment an unstarted date interval"));
+
+ stabilize();
+
+ if (! duration)
+ throw_(date_error,
+ _("Cannot increment a date interval without a duration"));
+
+ assert(next);
+
+ if (finish && *next >= *finish) {
+ start = none;
+ } else {
+ start = *next;
+ end_of_duration = duration->add(*start);
+ }
+ next = none;
+
+ resolve_end();
+
+ return *this;
+}
+
+void date_interval_t::dump(std::ostream& out, optional_year current_year)
+{
+ out << _("--- Before stabilization ---") << std::endl;
+
+ if (range)
+ out << _(" range: ") << range->to_string() << std::endl;
+ if (start)
+ out << _(" start: ") << format_date(*start) << std::endl;
+ if (finish)
+ out << _(" finish: ") << format_date(*finish) << std::endl;
+
+ if (duration)
+ out << _("duration: ") << duration->to_string() << std::endl;
+
+ stabilize(begin(current_year));
+
+ out << std::endl
+ << _("--- After stabilization ---") << std::endl;
+
+ if (range)
+ out << _(" range: ") << range->to_string() << std::endl;
+ if (start)
+ out << _(" start: ") << format_date(*start) << std::endl;
+ if (finish)
+ out << _(" finish: ") << format_date(*finish) << std::endl;
+
+ if (duration)
+ out << _("duration: ") << duration->to_string() << std::endl;
+
+ out << std::endl
+ << _("--- Sample dates in range (max. 20) ---") << std::endl;
+
+ date_t last_date;
+
+ for (int i = 0; i < 20 && *this; ++i, ++*this) {
+ out << std::right;
+ out.width(2);
+
+ if (! last_date.is_not_a_date() && last_date == *start)
+ break;
+
+ out << (i + 1) << ": " << format_date(*start);
+ if (duration)
+ out << " -- " << format_date(*inclusive_end());
+ out << std::endl;
+
+ if (! duration)
+ break;
+
+ last_date = *start;
+ }
+}
+
+date_parser_t::lexer_t::token_t date_parser_t::lexer_t::next_token()
+{
+ if (token_cache.kind != token_t::UNKNOWN) {
+ token_t tok = token_cache;
+ token_cache = token_t();
+ return tok;
+ }
+
+ while (begin != end && std::isspace(*begin))
+ begin++;
+
+ if (begin == end)
+ return token_t(token_t::END_REACHED);
+
+ switch (*begin) {
+ case '/': ++begin; return token_t(token_t::TOK_SLASH);
+ case '-': ++begin; return token_t(token_t::TOK_DASH);
+ case '.': ++begin; return token_t(token_t::TOK_DOT);
+ default: break;
+ }
+
+ string::const_iterator start = begin;
+
+ // If the first character is a digit, try parsing the whole argument as a
+ // date using the typical date formats. This allows not only dates like
+ // "2009/08/01", but also dates that fit the user's --input-date-format,
+ // assuming their format fits in one argument and begins with a digit.
+ if (std::isdigit(*begin)) {
+ try {
+ string::const_iterator i = begin;
+ for (i = begin; i != end && ! std::isspace(*i); i++) {}
+ assert(i != begin);
+
+ string possible_date(start, i);
+ date_traits_t traits;
+
+ date_t when = parse_date_mask(possible_date.c_str(), none, &traits);
+ if (! when.is_not_a_date()) {
+ begin = i;
+ return token_t(token_t::TOK_DATE,
+ token_t::content_t(date_specifier_t(when, traits)));
+ }
+ }
+ catch (...) {}
+ }
+
+ start = begin;
+
+ string term;
+ bool alnum = std::isalnum(*begin);
+ for (; (begin != end && ! std::isspace(*begin) &&
+ ((alnum && static_cast<bool>(std::isalnum(*begin))) ||
+ (! alnum && ! static_cast<bool>(std::isalnum(*begin))))); begin++)
+ term.push_back(*begin);
+
+ if (! term.empty()) {
+ if (std::isdigit(term[0])) {
+ if (term.length() == 4)
+ return token_t(token_t::TOK_A_YEAR,
+ token_t::content_t
+ (lexical_cast<date_specifier_t::year_type>(term)));
+ else
+ return token_t(token_t::TOK_INT,
+ token_t::content_t(lexical_cast<unsigned short>(term)));
+ }
+ else if (std::isalpha(term[0])) {
+ to_lower(term);
+
+ if (optional<date_time::months_of_year> month =
+ string_to_month_of_year(term)) {
+ return token_t(token_t::TOK_A_MONTH, token_t::content_t(*month));
+ }
+ else if (optional<date_time::weekdays> wday =
+ string_to_day_of_week(term)) {
+ return token_t(token_t::TOK_A_WDAY, token_t::content_t(*wday));
+ }
+ else if (term == _("from") || term == _("since"))
+ return token_t(token_t::TOK_SINCE);
+ else if (term == _("to") || term == _("until"))
+ return token_t(token_t::TOK_UNTIL);
+ else if (term == _("in"))
+ return token_t(token_t::TOK_IN);
+ else if (term == _("this"))
+ return token_t(token_t::TOK_THIS);
+ else if (term == _("next"))
+ return token_t(token_t::TOK_NEXT);
+ else if (term == _("last"))
+ return token_t(token_t::TOK_LAST);
+ else if (term == _("every"))
+ return token_t(token_t::TOK_EVERY);
+ else if (term == _("today"))
+ return token_t(token_t::TOK_TODAY);
+ else if (term == _("tomorrow"))
+ return token_t(token_t::TOK_TOMORROW);
+ else if (term == _("yesterday"))
+ return token_t(token_t::TOK_YESTERDAY);
+ else if (term == _("year"))
+ return token_t(token_t::TOK_YEAR);
+ else if (term == _("quarter"))
+ return token_t(token_t::TOK_QUARTER);
+ else if (term == _("month"))
+ return token_t(token_t::TOK_MONTH);
+ else if (term == _("week"))
+ return token_t(token_t::TOK_WEEK);
+ else if (term == _("day"))
+ return token_t(token_t::TOK_DAY);
+ else if (term == _("yearly"))
+ return token_t(token_t::TOK_YEARLY);
+ else if (term == _("quarterly"))
+ return token_t(token_t::TOK_QUARTERLY);
+ else if (term == _("bimonthly"))
+ return token_t(token_t::TOK_BIMONTHLY);
+ else if (term == _("monthly"))
+ return token_t(token_t::TOK_MONTHLY);
+ else if (term == _("biweekly"))
+ return token_t(token_t::TOK_BIWEEKLY);
+ else if (term == _("weekly"))
+ return token_t(token_t::TOK_WEEKLY);
+ else if (term == _("daily"))
+ return token_t(token_t::TOK_DAILY);
+ else if (term == _("years"))
+ return token_t(token_t::TOK_YEARS);
+ else if (term == _("quarters"))
+ return token_t(token_t::TOK_QUARTERS);
+ else if (term == _("months"))
+ return token_t(token_t::TOK_MONTHS);
+ else if (term == _("weeks"))
+ return token_t(token_t::TOK_WEEKS);
+ else if (term == _("days"))
+ return token_t(token_t::TOK_DAYS);
+ }
+ else {
+ token_t::expected('\0', term[0]);
+ begin = ++start;
+ }
+ } else {
+ token_t::expected('\0', *begin);
+ }
+
+ return token_t(token_t::UNKNOWN, token_t::content_t(term));
+}
+
+void date_parser_t::lexer_t::token_t::unexpected()
+{
+ switch (kind) {
+ case END_REACHED:
+ kind = UNKNOWN;
+ throw_(date_error, _("Unexpected end of expression"));
+ default: {
+ string desc = to_string();
+ kind = UNKNOWN;
+ throw_(date_error, _("Unexpected date period token '%1'") << desc);
+ }
+ }
+}
+
+void date_parser_t::lexer_t::token_t::expected(char wanted, char c)
+{
+ if (c == '\0' || c == -1) {
+ if (wanted == '\0' || wanted == -1)
+ throw_(date_error, _("Unexpected end"));
+ else
+ throw_(date_error, _("Missing '%1'") << wanted);
+ } else {
+ if (wanted == '\0' || wanted == -1)
+ throw_(date_error, _("Invalid char '%1'") << c);
+ else
+ throw_(date_error, _("Invalid char '%1' (wanted '%2')") << c << wanted);
+ }
+}
+
+namespace {
+ typedef std::map<std::string, datetime_io_t *> datetime_io_map;
+ typedef std::map<std::string, date_io_t *> date_io_map;
+
+ datetime_io_map temp_datetime_io;
+ date_io_map temp_date_io;
+}
+
+std::string format_datetime(const datetime_t& when,
+ const format_type_t format_type,
+ const optional<const char *>& format)
+{
+ if (format_type == FMT_WRITTEN) {
+ return written_datetime_io->format(when);
+ }
+ else if (format_type == FMT_CUSTOM || format) {
+ datetime_io_map::iterator i = temp_datetime_io.find(*format);
+ if (i != temp_datetime_io.end()) {
+ return (*i).second->format(when);
+ } else {
+ datetime_io_t * formatter = new datetime_io_t(*format, false);
+ temp_datetime_io.insert(datetime_io_map::value_type(*format, formatter));
+ return formatter->format(when);
+ }
+ }
+ else if (format_type == FMT_PRINTED) {
+ return printed_datetime_io->format(when);
+ }
+ else {
+ assert(false);
+ return empty_string;
+ }
+}
+
+std::string format_date(const date_t& when,
+ const format_type_t format_type,
+ const optional<const char *>& format)
+{
+ if (format_type == FMT_WRITTEN) {
+ return written_date_io->format(when);
+ }
+ else if (format_type == FMT_CUSTOM || format) {
+ date_io_map::iterator i = temp_date_io.find(*format);
+ if (i != temp_date_io.end()) {
+ return (*i).second->format(when);
+ } else {
+ date_io_t * formatter = new date_io_t(*format, false);
+ temp_date_io.insert(date_io_map::value_type(*format, formatter));
+ return formatter->format(when);
+ }
+ }
+ else if (format_type == FMT_PRINTED) {
+ return printed_date_io->format(when);
+ }
+ else {
+ assert(false);
+ return empty_string;
+ }
+}
+
+namespace {
+ bool is_initialized = false;
+}
+
+void set_datetime_format(const char * format)
+{
+ printed_datetime_io->set_format(format);
+}
+
+void set_date_format(const char * format)
+{
+ printed_date_io->set_format(format);
+}
+
+void set_input_date_format(const char * format)
+{
+ input_date_io.reset(new date_io_t(format, true));
+}
+
+void times_initialize()
+{
+ if (! is_initialized) {
+ input_datetime_io.reset(new datetime_io_t("%Y/%m/%d %H:%M:%S", true));
+
+ written_datetime_io.reset(new datetime_io_t("%Y/%m/%d %H:%M:%S", false));
+ written_date_io.reset(new date_io_t("%Y/%m/%d", false));
+
+ printed_datetime_io.reset(new datetime_io_t("%y-%b-%d %H:%M:%S", false));
+ printed_date_io.reset(new date_io_t("%y-%b-%d", false));
+
+ readers.push_back(shared_ptr<date_io_t>(new date_io_t("%m/%d", true)));
+ readers.push_back(shared_ptr<date_io_t>(new date_io_t("%Y/%m/%d", true)));
+ readers.push_back(shared_ptr<date_io_t>(new date_io_t("%Y/%m", true)));
+ readers.push_back(shared_ptr<date_io_t>(new date_io_t("%y/%m/%d", true)));
+
+ is_initialized = true;
+ }
+}
+
+void times_shutdown()
+{
+ if (is_initialized) {
+ input_datetime_io.reset();
+ input_date_io.reset();
+ written_datetime_io.reset();
+ written_date_io.reset();
+ printed_datetime_io.reset();
+ printed_date_io.reset();
+
+ readers.clear();
+
+ foreach (datetime_io_map::value_type& pair, temp_datetime_io)
+ checked_delete(pair.second);
+ temp_datetime_io.clear();
+
+ foreach (date_io_map::value_type& pair, temp_date_io)
+ checked_delete(pair.second);
+ temp_date_io.clear();
+
+ is_initialized = false;
+ }
+}
+
+void show_period_tokens(std::ostream& out, const string& arg)
+{
+ date_parser_t::lexer_t lexer(arg.begin(), arg.end());
+
+ out << _("--- Period expression tokens ---") << std::endl;
+
+ date_parser_t::lexer_t::token_t token;
+ do {
+ token = lexer.next_token();
+ token.dump(out);
+ out << ": " << token.to_string() << std::endl;
+ }
+ while (token.kind != date_parser_t::lexer_t::token_t::END_REACHED);
+}
+
+} // namespace ledger
diff --git a/src/times.h b/src/times.h
new file mode 100644
index 00000000..826937bb
--- /dev/null
+++ b/src/times.h
@@ -0,0 +1,632 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup util
+ */
+
+/**
+ * @file times.h
+ * @author John Wiegley
+ *
+ * @ingroup util
+ *
+ * @brief datetime_t and date_t objects
+ */
+#ifndef _TIMES_H
+#define _TIMES_H
+
+#include "utils.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_iterator date_iterator_t;
+
+inline bool is_valid(const date_t& moment) {
+ return ! moment.is_not_a_date();
+}
+
+extern optional<datetime_t> epoch;
+
+#ifdef BOOST_DATE_TIME_HAS_HIGH_PRECISION_CLOCK
+#define TRUE_CURRENT_TIME() (boost::posix_time::microsec_clock::universal_time())
+#define CURRENT_TIME() (epoch ? *epoch : TRUE_CURRENT_TIME())
+#else
+#define TRUE_CURRENT_TIME() (boost::posix_time::second_clock::universal_time())
+#define CURRENT_TIME() (epoch ? *epoch : TRUE_CURRENT_TIME())
+#endif
+#define CURRENT_DATE() \
+ (epoch ? epoch->date() : boost::gregorian::day_clock::universal_day())
+
+extern date_time::weekdays start_of_week;
+
+optional<date_time::weekdays>
+string_to_day_of_week(const std::string& str);
+optional<date_time::months_of_year>
+string_to_month_of_year(const std::string& str);
+
+typedef optional<date_t::year_type> optional_year;
+
+datetime_t parse_datetime(const char * str, optional_year current_year = none);
+
+inline datetime_t parse_datetime(const std::string& str,
+ optional_year current_year = none) {
+ return parse_datetime(str.c_str(), current_year);
+}
+
+date_t parse_date(const char * str, optional_year current_year = none);
+
+inline date_t parse_date(const std::string& str,
+ optional_year current_year = none) {
+ return parse_date(str.c_str(), current_year);
+}
+
+enum format_type_t {
+ FMT_WRITTEN, FMT_PRINTED, FMT_CUSTOM
+};
+
+std::string format_datetime(const datetime_t& when,
+ const format_type_t format_type = FMT_PRINTED,
+ const optional<const char *>& format = none);
+void set_datetime_format(const char * format);
+
+std::string format_date(const date_t& when,
+ const format_type_t format_type = FMT_PRINTED,
+ const optional<const char *>& format = none);
+void set_date_format(const char * format);
+void set_input_date_format(const char * format);
+
+inline void to_xml(std::ostream& out, const datetime_t& when,
+ bool wrap = true)
+{
+ if (wrap) {
+ push_xml x(out, "datetime");
+ out << format_datetime(when, FMT_WRITTEN);
+ } else {
+ out << format_datetime(when, FMT_WRITTEN);
+ }
+}
+
+inline void to_xml(std::ostream& out, const date_t& when,
+ bool wrap = true)
+{
+ if (wrap) {
+ push_xml x(out, "date");
+ out << format_date(when, FMT_WRITTEN);
+ } else {
+ out << format_date(when, FMT_WRITTEN);
+ }
+}
+
+struct date_traits_t
+{
+ bool has_year;
+ bool has_month;
+ bool has_day;
+
+ date_traits_t(bool _has_year = false,
+ bool _has_month = false,
+ bool _has_day = false)
+ : has_year(_has_year), has_month(_has_month), has_day(_has_day) {
+ TRACE_CTOR(date_traits_t, "bool, bool, bool");
+ }
+ date_traits_t(const date_traits_t& traits)
+ : has_year(traits.has_year),
+ has_month(traits.has_month),
+ has_day(traits.has_day) {
+ TRACE_CTOR(date_traits_t, "copy");
+ }
+ ~date_traits_t() throw() {
+ TRACE_DTOR(date_traits_t);
+ }
+
+ date_traits_t& operator=(const date_traits_t& traits) {
+ has_year = traits.has_year;
+ has_month = traits.has_month;
+ has_day = traits.has_day;
+ return *this;
+ }
+
+ bool operator==(const date_traits_t& traits) const {
+ return (has_year == traits.has_year &&
+ has_month == traits.has_month &&
+ has_day == traits.has_day);
+ }
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & has_year;
+ ar & has_month;
+ ar & has_day;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+struct date_duration_t
+{
+ enum skip_quantum_t {
+ DAYS, WEEKS, MONTHS, QUARTERS, YEARS
+ } quantum;
+ int length;
+
+ date_duration_t() : quantum(DAYS), length(0) {
+ TRACE_CTOR(date_duration_t, "");
+ }
+ date_duration_t(skip_quantum_t _quantum, int _length)
+ : quantum(_quantum), length(_length) {
+ TRACE_CTOR(date_duration_t, "skip_quantum_t, int");
+ }
+ date_duration_t(const date_duration_t& dur)
+ : quantum(dur.quantum), length(dur.length) {
+ TRACE_CTOR(date_duration_t, "copy");
+ }
+ ~date_duration_t() throw() {
+ TRACE_DTOR(date_duration_t);
+ }
+
+ date_t add(const date_t& date) const {
+ switch (quantum) {
+ case DAYS:
+ return date + gregorian::days(length);
+ case WEEKS:
+ return date + gregorian::weeks(length);
+ case MONTHS:
+ return date + gregorian::months(length);
+ case QUARTERS:
+ return date + gregorian::months(length * 3);
+ case YEARS:
+ return date + gregorian::years(length);
+ default:
+ assert(false); return date_t();
+ }
+ }
+
+ date_t subtract(const date_t& date) const {
+ switch (quantum) {
+ case DAYS:
+ return date - gregorian::days(length);
+ case WEEKS:
+ return date - gregorian::weeks(length);
+ case MONTHS:
+ return date - gregorian::months(length);
+ case QUARTERS:
+ return date - gregorian::months(length * 3);
+ case YEARS:
+ return date - gregorian::years(length);
+ default:
+ assert(false); return date_t();
+ }
+ }
+
+ string to_string() const {
+ std::ostringstream out;
+
+ out << length << ' ';
+
+ switch (quantum) {
+ case DAYS: out << "day"; break;
+ case WEEKS: out << "week"; break;
+ case MONTHS: out << "month"; break;
+ case QUARTERS: out << "quarter"; break;
+ case YEARS: out << "year"; break;
+ default:
+ assert(false);
+ break;
+ }
+
+ if (length > 1)
+ out << 's';
+
+ return out.str();
+ }
+
+ static date_t find_nearest(const date_t& date, skip_quantum_t skip);
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & quantum;
+ ar & length;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+class date_specifier_t
+{
+ friend class date_parser_t;
+
+#if 0
+ typedef date_t::year_type year_type;
+#else
+ typedef unsigned short year_type;
+#endif
+ typedef date_t::month_type month_type;
+ typedef date_t::day_type day_type;
+ typedef date_t::day_of_week_type day_of_week_type;
+
+ optional<year_type> year;
+ optional<month_type> month;
+ optional<day_type> day;
+ optional<day_of_week_type> wday;
+
+public:
+ date_specifier_t(const optional<year_type>& _year = none,
+ const optional<month_type>& _month = none,
+ const optional<day_type>& _day = none,
+ const optional<day_of_week_type>& _wday = none)
+ : year(_year), month(_month), day(_day), wday(_wday) {
+ TRACE_CTOR(date_specifier_t,
+ "year_type, month_type, day_type, day_of_week_type");
+ }
+ date_specifier_t(const date_t& date,
+ const optional<date_traits_t>& traits = none) {
+ TRACE_CTOR(date_specifier_t, "date_t, date_traits_t");
+ if (! traits || traits->has_year)
+ year = date.year();
+ if (! traits || traits->has_month)
+ month = date.month();
+ if (! traits || traits->has_day)
+ day = date.day();
+ }
+ date_specifier_t(const date_specifier_t& other)
+ : year(other.year), month(other.month),
+ day(other.day), wday(other.wday) {
+ TRACE_CTOR(date_specifier_t, "copy");
+ }
+ ~date_specifier_t() throw() {
+ TRACE_DTOR(date_specifier_t);
+ }
+
+ date_t begin(const optional_year& current_year = none) const;
+ date_t end(const optional_year& current_year = none) const;
+
+ bool is_within(const date_t& date,
+ const optional_year& current_year = none) const {
+ return date >= begin(current_year) && date < end(current_year);
+ }
+
+ optional<date_duration_t> implied_duration() const {
+ if (day || wday)
+ return date_duration_t(date_duration_t::DAYS, 1);
+ else if (month)
+ return date_duration_t(date_duration_t::MONTHS, 1);
+ else if (year)
+ return date_duration_t(date_duration_t::YEARS, 1);
+ else
+ return none;
+ }
+
+ string to_string() const {
+ std::ostringstream out;
+
+ if (year)
+ out << " year " << *year;
+ if (month)
+ out << " month " << *month;
+ if (day)
+ out << " day " << *day;
+ if (wday)
+ out << " wday " << *wday;
+
+ return out.str();
+ }
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & year;
+ ar & month;
+ ar & day;
+ ar & wday;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+class date_range_t
+{
+ friend class date_parser_t;
+
+ optional<date_specifier_t> range_begin;
+ optional<date_specifier_t> range_end;
+
+ bool end_inclusive;
+
+public:
+ date_range_t(const optional<date_specifier_t>& _range_begin = none,
+ const optional<date_specifier_t>& _range_end = none)
+ : range_begin(_range_begin), range_end(_range_end),
+ end_inclusive(false) {
+ TRACE_CTOR(date_range_t, "date_specifier_t, date_specifier_t");
+ }
+ date_range_t(const date_range_t& other)
+ : range_begin(other.range_begin), range_end(other.range_end),
+ end_inclusive(other.end_inclusive) {
+ TRACE_CTOR(date_range_t, "date_range_t");
+ }
+ ~date_range_t() throw() {
+ TRACE_DTOR(date_range_t);
+ }
+
+ optional<date_t> begin(const optional_year& current_year = none) const {
+ if (range_begin)
+ return range_begin->begin(current_year);
+ else
+ return none;
+ }
+ optional<date_t> end(const optional_year& current_year = none) const {
+ if (range_end) {
+ if (end_inclusive)
+ return range_end->end(current_year);
+ else
+ return range_end->begin(current_year);
+ } else {
+ return none;
+ }
+ }
+
+ bool is_within(const date_t& date,
+ const optional_year& current_year = none) const {
+ optional<date_t> b = begin(current_year);
+ optional<date_t> e = end(current_year);
+ bool after_begin = b ? date >= *b : true;
+ bool before_end = e ? date < *e : true;
+ return after_begin && before_end;
+ }
+
+ string to_string() const {
+ std::ostringstream out;
+
+ if (range_begin)
+ out << "from" << range_begin->to_string();
+ if (range_end)
+ out << " to" << range_end->to_string();
+
+ return out.str();
+ }
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & range_begin;
+ ar & range_end;
+ ar & end_inclusive;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+class date_specifier_or_range_t
+{
+ typedef variant<int, date_specifier_t, date_range_t> value_type;
+
+ value_type specifier_or_range;
+
+public:
+ date_specifier_or_range_t() {
+ TRACE_CTOR(date_specifier_or_range_t, "");
+ }
+ date_specifier_or_range_t(const date_specifier_or_range_t& other)
+ : specifier_or_range(other.specifier_or_range) {
+ TRACE_CTOR(date_specifier_or_range_t, "copy");
+ }
+ date_specifier_or_range_t(const date_specifier_t& specifier)
+ : specifier_or_range(specifier) {
+ TRACE_CTOR(date_specifier_or_range_t, "date_specifier_t");
+ }
+ date_specifier_or_range_t(const date_range_t& range)
+ : specifier_or_range(range) {
+ TRACE_CTOR(date_specifier_or_range_t, "date_range_t");
+ }
+ ~date_specifier_or_range_t() throw() {
+ TRACE_DTOR(date_specifier_or_range_t);
+ }
+
+ optional<date_t> begin(const optional_year& current_year = none) const {
+ if (specifier_or_range.type() == typeid(date_specifier_t))
+ return boost::get<date_specifier_t>(specifier_or_range).begin(current_year);
+ else if (specifier_or_range.type() == typeid(date_range_t))
+ return boost::get<date_range_t>(specifier_or_range).begin(current_year);
+ else
+ return none;
+ }
+ optional<date_t> end(const optional_year& current_year = none) const {
+ if (specifier_or_range.type() == typeid(date_specifier_t))
+ return boost::get<date_specifier_t>(specifier_or_range).end(current_year);
+ else if (specifier_or_range.type() == typeid(date_range_t))
+ return boost::get<date_range_t>(specifier_or_range).end(current_year);
+ else
+ return none;
+ }
+
+
+ string to_string() const {
+ std::ostringstream out;
+
+ if (specifier_or_range.type() == typeid(date_specifier_t))
+ out << "in" << boost::get<date_specifier_t>(specifier_or_range).to_string();
+ else if (specifier_or_range.type() == typeid(date_range_t))
+ out << boost::get<date_range_t>(specifier_or_range).to_string();
+
+ return out.str();
+ }
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & specifier_or_range;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+class date_interval_t : public equality_comparable<date_interval_t>
+{
+public:
+ static date_t add_duration(const date_t& date,
+ const date_duration_t& duration);
+ static date_t subtract_duration(const date_t& date,
+ const date_duration_t& duration);
+
+ optional<date_specifier_or_range_t> range;
+
+ optional<date_t> start; // the real start, after adjustment
+ optional<date_t> finish; // the real end, likewise
+ bool aligned;
+ optional<date_t> next;
+ optional<date_duration_t> duration;
+ optional<date_t> end_of_duration;
+
+ explicit date_interval_t() : aligned(false) {
+ TRACE_CTOR(date_interval_t, "");
+ }
+ date_interval_t(const string& str) : aligned(false) {
+ TRACE_CTOR(date_interval_t, "const string&");
+ parse(str);
+ }
+ date_interval_t(const date_interval_t& other)
+ : range(other.range),
+ start(other.start),
+ finish(other.finish),
+ aligned(other.aligned),
+ next(other.next),
+ duration(other.duration),
+ end_of_duration(other.end_of_duration) {
+ TRACE_CTOR(date_interval_t, "copy");
+ }
+ ~date_interval_t() throw() {
+ TRACE_DTOR(date_interval_t);
+ }
+
+ bool operator==(const date_interval_t& other) const {
+ return (start == other.start &&
+ (! start || *start == *other.start));
+ }
+
+ operator bool() const {
+ return is_valid();
+ }
+
+ optional<date_t> begin(const optional_year& current_year = none) const {
+ return start ? start : (range ? range->begin(current_year) : none);
+ }
+ optional<date_t> end(const optional_year& current_year = none) const {
+ return finish ? finish : (range ? range->end(current_year) : none);
+ }
+
+ void parse(const string& str);
+
+ void resolve_end();
+ void stabilize(const optional<date_t>& date = none);
+
+ bool is_valid() const {
+ return start;
+ }
+
+ /** Find the current or next period containing date. Returns true if the
+ date_interval_t object has been altered to reflect the interval
+ containing date, or false if no such period can be found. */
+ bool find_period(const date_t& date);
+
+ optional<date_t> inclusive_end() const {
+ if (end_of_duration)
+ return *end_of_duration - gregorian::days(1);
+ else
+ return none;
+ }
+
+ date_interval_t& operator++();
+
+ void dump(std::ostream& out, optional_year current_year = none);
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & range;
+ ar & start;
+ ar & finish;
+ ar & aligned;
+ ar & next;
+ ar & duration;
+ ar & end_of_duration;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+void times_initialize();
+void times_shutdown();
+
+void show_period_tokens(std::ostream& out, const string& arg);
+
+std::ostream& operator<<(std::ostream& out, const date_duration_t& duration);
+
+} // namespace ledger
+
+#endif // _TIMES_H
diff --git a/src/token.cc b/src/token.cc
new file mode 100644
index 00000000..81c54a82
--- /dev/null
+++ b/src/token.cc
@@ -0,0 +1,485 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "token.h"
+#include "parser.h"
+
+namespace ledger {
+
+int expr_t::token_t::parse_reserved_word(std::istream& in)
+{
+ char c = static_cast<char>(in.peek());
+
+ if (c == 'a' || c == 'd' || c == 'e' || c == 'f' ||
+ c == 'i' || c == 'o' || c == 'n' || c == 't') {
+ length = 0;
+
+ char buf[6];
+ READ_INTO_(in, buf, 5, c, length, std::isalpha(c));
+
+ switch (buf[0]) {
+ case 'a':
+ if (std::strcmp(buf, "and") == 0) {
+ symbol[0] = '&';
+ symbol[1] = '\0';
+ kind = KW_AND;
+ return 1;
+ }
+ break;
+
+ case 'd':
+ if (std::strcmp(buf, "div") == 0) {
+ symbol[0] = '/';
+ symbol[1] = '/';
+ symbol[2] = '\0';
+ kind = KW_DIV;
+ return 1;
+ }
+ break;
+
+ case 'e':
+ if (std::strcmp(buf, "else") == 0) {
+ symbol[0] = 'L';
+ symbol[1] = 'S';
+ symbol[2] = '\0';
+ kind = KW_ELSE;
+ return 1;
+ }
+ break;
+
+ case 'f':
+ if (std::strcmp(buf, "false") == 0) {
+ kind = VALUE;
+ value = false;
+ return 1;
+ }
+ break;
+
+ case 'i':
+ if (std::strcmp(buf, "if") == 0) {
+ symbol[0] = 'i';
+ symbol[1] = 'f';
+ symbol[2] = '\0';
+ kind = KW_IF;
+ return 1;
+ }
+ break;
+
+ case 'o':
+ if (std::strcmp(buf, "or") == 0) {
+ symbol[0] = '|';
+ symbol[1] = '\0';
+ kind = KW_OR;
+ return 1;
+ }
+ break;
+
+ case 'n':
+ if (std::strcmp(buf, "not") == 0) {
+ symbol[0] = '!';
+ symbol[1] = '\0';
+ kind = EXCLAM;
+ return 1;
+ }
+ break;
+
+ case 't':
+ if (std::strcmp(buf, "true") == 0) {
+ kind = VALUE;
+ value = true;
+ return 1;
+ }
+ break;
+ }
+
+ return 0;
+ }
+ return -1;
+}
+
+void expr_t::token_t::parse_ident(std::istream& in)
+{
+ kind = IDENT;
+ length = 0;
+
+ char c, buf[256];
+ READ_INTO_(in, buf, 255, c, length, std::isalnum(c) || c == '_');
+
+ value.set_string(buf);
+}
+
+void expr_t::token_t::next(std::istream& in, const parse_flags_t& pflags)
+{
+ if (in.eof()) {
+ kind = TOK_EOF;
+ return;
+ }
+ if (! in.good())
+ throw_(parse_error, _("Input stream no longer valid"));
+
+ char c = peek_next_nonws(in);
+
+ if (in.eof()) {
+ kind = TOK_EOF;
+ return;
+ }
+ if (! in.good())
+ throw_(parse_error, _("Input stream no longer valid"));
+
+ symbol[0] = c;
+ symbol[1] = '\0';
+
+ length = 1;
+
+ switch (c) {
+ case '&':
+ in.get(c);
+ c = static_cast<char>(in.peek());
+ if (c == '&') {
+ in.get(c);
+ kind = KW_AND;
+ length = 2;
+ break;
+ }
+ kind = KW_AND;
+ break;
+ case '|':
+ in.get(c);
+ c = static_cast<char>(in.peek());
+ if (c == '|') {
+ in.get(c);
+ kind = KW_OR;
+ length = 2;
+ break;
+ }
+ 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++;
+
+ date_interval_t timespan(buf);
+ optional<date_t> begin = timespan.begin();
+ if (! begin)
+ throw_(parse_error,
+ _("Date specifier does not refer to a starting date"));
+ kind = VALUE;
+ value = *begin;
+ 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, PARSE_NO_MIGRATE);
+ in.get(c);
+ if (c != '}')
+ expected('}', c);
+ length++;
+ kind = VALUE;
+ value = temp;
+ break;
+ }
+
+ case '!':
+ in.get(c);
+ c = static_cast<char>(in.peek());
+ if (c == '=') {
+ in.get(c);
+ symbol[1] = c;
+ symbol[2] = '\0';
+ kind = NEQUAL;
+ length = 2;
+ break;
+ }
+ else if (c == '~') {
+ in.get(c);
+ symbol[1] = c;
+ symbol[2] = '\0';
+ kind = NMATCH;
+ 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);
+ c = static_cast<char>(in.peek());
+ if (c == '=') {
+ in.get(c);
+ symbol[1] = c;
+ symbol[2] = '\0';
+ kind = DEFINE;
+ length = 2;
+ break;
+ }
+ kind = COLON;
+ break;
+
+ case '/': {
+ in.get(c);
+ if (pflags.has_flags(PARSE_OP_CONTEXT)) { // operator context
+ kind = SLASH;
+ } else { // terminal context
+ // Read in the regexp
+ char buf[256];
+ READ_INTO_(in, buf, 255, c, length, c != '/');
+ if (c != '/')
+ expected('/', c);
+ in.get(c);
+ length++;
+
+ kind = VALUE;
+ value.set_mask(buf);
+ }
+ break;
+ }
+
+ case '=':
+ in.get(c);
+ c = static_cast<char>(in.peek());
+ if (c == '~') {
+ in.get(c);
+ symbol[1] = c;
+ symbol[2] = '\0';
+ kind = MATCH;
+ length = 2;
+ break;
+ }
+ else if (c == '=') {
+ in.get(c);
+ symbol[1] = c;
+ symbol[2] = '\0';
+ kind = EQUAL;
+ length = 2;
+ break;
+ }
+ kind = EQUAL;
+ break;
+
+ case '<':
+ in.get(c);
+ if (static_cast<char>(in.peek()) == '=') {
+ in.get(c);
+ symbol[1] = c;
+ symbol[2] = '\0';
+ kind = LESSEQ;
+ length = 2;
+ break;
+ }
+ kind = LESS;
+ break;
+
+ case '>':
+ in.get(c);
+ if (static_cast<char>(in.peek()) == '=') {
+ in.get(c);
+ symbol[1] = c;
+ symbol[2] = '\0';
+ kind = GREATEREQ;
+ length = 2;
+ break;
+ }
+ kind = GREATER;
+ break;
+
+ case '.':
+ in.get(c);
+ kind = DOT;
+ break;
+
+ case ',':
+ in.get(c);
+ kind = COMMA;
+ break;
+
+ case ';':
+ in.get(c);
+ kind = SEMI;
+ break;
+
+ default: {
+ istream_pos_type pos = in.tellg();
+
+ // First, check to see if it's a reserved word, such as: and or not
+ int result = parse_reserved_word(in);
+ if (std::isalpha(c) && result == 1)
+ break;
+
+ // If not, rewind back to the beginning of the word to scan it
+ // again. If the result was -1, it means no identifier was scanned
+ // so we don't have to rewind.
+ if (result == 0) {
+ in.clear();
+ in.seekg(pos, std::ios::beg);
+ if (in.fail())
+ throw_(parse_error, _("Failed to reset input stream"));
+ }
+
+ // 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.
+ parse_flags_t parse_flags;
+
+ if (pflags.has_flags(PARSE_NO_MIGRATE))
+ parse_flags.add_flags(PARSE_NO_MIGRATE);
+ if (pflags.has_flags(PARSE_NO_REDUCE))
+ parse_flags.add_flags(PARSE_NO_REDUCE);
+
+ try {
+ amount_t temp;
+ if (! temp.parse(in, parse_flags.plus_flags(PARSE_SOFT_FAIL))) {
+ // If the amount had no commodity, it must be an unambiguous
+ // variable reference
+
+ in.clear();
+ in.seekg(pos, std::ios::beg);
+ if (in.fail())
+ throw_(parse_error, _("Failed to reset input stream"));
+
+ c = static_cast<char>(in.peek());
+ if (std::isdigit(c) || c == '.')
+ expected('\0', c);
+
+ parse_ident(in);
+ } else {
+ kind = VALUE;
+ value = temp;
+ length = static_cast<std::size_t>(in.tellg() - pos);
+ }
+ }
+ catch (const std::exception& err) {
+ kind = ERROR;
+ length = static_cast<std::size_t>(in.tellg() - pos);
+ throw;
+ }
+ break;
+ }
+ }
+}
+
+void expr_t::token_t::rewind(std::istream& in)
+{
+ in.seekg(- length, std::ios::cur);
+ if (in.fail())
+ throw_(parse_error, _("Failed to rewind input stream"));
+}
+
+
+void expr_t::token_t::unexpected()
+{
+ kind_t prev_kind = kind;
+
+ kind = ERROR;
+
+ switch (prev_kind) {
+ case TOK_EOF:
+ throw_(parse_error, _("Unexpected end of expression"));
+ case IDENT:
+ throw_(parse_error, _("Unexpected symbol '%1'") << value);
+ case VALUE:
+ throw_(parse_error, _("Unexpected value '%1'") << value);
+ default:
+ throw_(parse_error, _("Unexpected token '%1'") << symbol);
+ }
+}
+
+void expr_t::token_t::expected(char wanted, char c)
+{
+ kind = ERROR;
+
+ if (c == '\0' || c == -1) {
+ if (wanted == '\0' || wanted == -1)
+ throw_(parse_error, _("Unexpected end"));
+ else
+ throw_(parse_error, _("Missing '%1'") << wanted);
+ } else {
+ if (wanted == '\0' || wanted == -1)
+ throw_(parse_error, _("Invalid char '%1'") << c);
+ else
+ throw_(parse_error, _("Invalid char '%1' (wanted '%2')") << c << wanted);
+ }
+}
+
+} // namespace ledger
diff --git a/src/token.h b/src/token.h
new file mode 100644
index 00000000..670f16e3
--- /dev/null
+++ b/src/token.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @defgroup expr Value expressions
+ */
+
+/**
+ * @file token.h
+ * @author John Wiegley
+ *
+ * @ingroup expr
+ */
+#ifndef _TOKEN_H
+#define _TOKEN_H
+
+#include "expr.h"
+
+namespace ledger {
+
+struct expr_t::token_t : public noncopyable
+{
+ enum kind_t {
+ ERROR, // an error occurred while tokenizing
+ VALUE, // any kind of literal value
+ IDENT, // [A-Za-z_][-A-Za-z0-9_:]*
+ MASK, // /regexp/
+
+ LPAREN, // (
+ RPAREN, // )
+
+ EQUAL, // ==
+ NEQUAL, // !=
+ LESS, // <
+ LESSEQ, // <=
+ GREATER, // >
+ GREATEREQ, // >=
+
+ DEFINE, // :=
+ ASSIGN, // =
+ MATCH, // =~
+ NMATCH, // !~
+ MINUS, // -
+ PLUS, // +
+ STAR, // *
+ SLASH, // /
+ KW_DIV, // div
+
+ EXCLAM, // !, not
+ KW_AND, // &, &&, and
+ KW_OR, // |, ||, or
+ KW_MOD, // %
+
+ KW_IF, // if
+ KW_ELSE, // else
+
+ QUERY, // ?
+ COLON, // :
+
+ DOT, // .
+ COMMA, // ,
+ SEMI, // ;
+
+ TOK_EOF,
+ UNKNOWN
+
+ } kind;
+
+ char symbol[3];
+ value_t value;
+ std::size_t length;
+
+ explicit token_t() : kind(UNKNOWN), length(0) {
+ TRACE_CTOR(expr_t::token_t, "");
+ }
+ ~token_t() throw() {
+ TRACE_DTOR(expr_t::token_t);
+ }
+
+ token_t& operator=(const token_t& other) {
+ if (&other == this)
+ return *this;
+ assert(false); // only one token object is used at a time
+ return *this;
+ }
+
+ void clear() {
+ kind = UNKNOWN;
+ length = 0;
+ value = NULL_VALUE;
+
+ symbol[0] = '\0';
+ symbol[1] = '\0';
+ symbol[2] = '\0';
+ }
+
+ int parse_reserved_word(std::istream& in);
+ void parse_ident(std::istream& in);
+ void next(std::istream& in, const parse_flags_t& flags);
+ void rewind(std::istream& in);
+ void unexpected();
+ void expected(char wanted, char c = '\0');
+};
+
+} // namespace ledger
+
+#endif // _TOKEN_H
diff --git a/src/unistring.h b/src/unistring.h
new file mode 100644
index 00000000..7c433d9d
--- /dev/null
+++ b/src/unistring.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup utils
+ */
+
+/**
+ * @file unistring.h
+ * @author John Wiegley
+ *
+ * @ingroup utils
+ */
+#ifndef _UNISTRING_H
+#define _UNISTRING_H
+
+namespace ledger {
+
+/**
+ * @class unistring
+ *
+ * @brief Abstract working with UTF-32 encoded Unicode strings
+ *
+ * The input to the string is a UTF8 encoded ledger::string, which can
+ * then have its true length be taken, or characters extracted.
+ */
+class unistring
+{
+public:
+ std::vector<boost::uint32_t> utf32chars;
+
+ unistring() {
+ TRACE_CTOR(unistring, "");
+ }
+ unistring(const std::string& input)
+ {
+ TRACE_CTOR(unistring, "std::string");
+
+ const char * p = input.c_str();
+ std::size_t len = input.length();
+
+ VERIFY(utf8::is_valid(p, p + len));
+ utf8::unchecked::utf8to32(p, p + len, std::back_inserter(utf32chars));
+ }
+ ~unistring() {
+ TRACE_DTOR(unistring);
+ }
+
+ std::size_t length() const {
+ return utf32chars.size();
+ }
+
+ std::string extract(const std::string::size_type begin = 0,
+ const std::string::size_type len = 0) const
+ {
+ std::string utf8result;
+ std::string::size_type this_len = length();
+
+ assert(begin <= this_len);
+ assert(begin + len <= this_len);
+
+ if (this_len)
+ utf8::unchecked::utf32to8
+ (utf32chars.begin() + begin,
+ utf32chars.begin() + begin +
+ (len ? (len > this_len ? this_len : len) : this_len),
+ std::back_inserter(utf8result));
+
+ return utf8result;
+ }
+};
+
+inline void justify(std::ostream& out,
+ const std::string& str,
+ int width,
+ bool right = false,
+ bool redden = false)
+{
+ if (! right) {
+ if (redden) out << "\033[31m";
+ out << str;
+ if (redden) out << "\033[0m";
+ }
+
+ unistring temp(str);
+
+ int spacing = width - int(temp.length());
+ while (spacing-- > 0)
+ out << ' ';
+
+ if (right) {
+ if (redden) out << "\033[31m";
+ out << str;
+ if (redden) out << "\033[0m";
+ }
+}
+
+} // namespace ledger
+
+#endif // _UNISTRING_H
diff --git a/src/utils.cc b/src/utils.cc
new file mode 100644
index 00000000..f2460ba1
--- /dev/null
+++ b/src/utils.cc
@@ -0,0 +1,821 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "times.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,
+ std::size_t 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> memory_map;
+typedef std::multimap<void *, allocation_pair> objects_map;
+
+typedef std::pair<std::size_t, std::size_t> count_size_pair;
+typedef std::map<std::string, count_size_pair> object_count_map;
+
+namespace {
+ bool memory_tracing_active = false;
+
+ memory_map * live_memory = NULL;
+ memory_map * freed_memory = NULL;
+ object_count_map * live_memory_count = NULL;
+ object_count_map * total_memory_count = NULL;
+ objects_map * live_objects = NULL;
+ object_count_map * live_object_count = NULL;
+ object_count_map * total_object_count = NULL;
+ object_count_map * total_ctor_count = NULL;
+}
+
+void initialize_memory_tracing()
+{
+ memory_tracing_active = false;
+
+ live_memory = new memory_map;
+ freed_memory = new memory_map;
+ live_memory_count = new object_count_map;
+ total_memory_count = new object_count_map;
+ live_objects = new 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(freed_memory); freed_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)
+{
+ if (! live_memory || ! memory_tracing_active) return;
+
+ memory_tracing_active = false;
+
+ memory_map::iterator i = freed_memory->find(ptr);
+ if (i != freed_memory->end())
+ freed_memory->erase(i);
+
+ live_memory->insert
+ (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)
+{
+ if (! live_memory || ! memory_tracing_active) return;
+
+ memory_tracing_active = false;
+
+ // 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.
+
+ memory_map::iterator i = live_memory->find(ptr);
+ if (i == live_memory->end()) {
+ i = freed_memory->find(ptr);
+ if (i != freed_memory->end())
+ VERIFY(! "Freeing a block of memory twice");
+#if 0
+ // There can be memory allocated by Boost or the standard library, which
+ // was allocated before memory tracing got turned on, that the system
+ // might free for some coincidental reason. As such, we can't rely on
+ // this check being valid. I've seen cases where processes ran to
+ // completion with it on, and then others where valid processes failed.
+ else
+ VERIFY(! "Freeing an unknown block of memory");
+#endif
+ memory_tracing_active = true;
+ return;
+ }
+
+ std::size_t size = (*i).second.second;
+ VERIFY((*i).second.first == which);
+
+ live_memory->erase(i);
+
+ freed_memory->insert
+ (memory_map::value_type(ptr, allocation_pair(which, size)));
+
+ 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)
+{
+ if (! live_objects || ! memory_tracing_active) return;
+
+ memory_tracing_active = false;
+
+ 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
+ (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)
+{
+ if (! live_objects || ! memory_tracing_active) return;
+
+ memory_tracing_active = false;
+
+ DEBUG("memory.debug", "TRACE_DTOR " << ptr << " " << cls_name);
+
+ objects_map::iterator i = live_objects->find(ptr);
+ if (i == live_objects->end()) {
+ warning_(_("Attempting to delete %1 a non-living %2") << ptr << cls_name);
+ memory_tracing_active = true;
+ return;
+ }
+
+ std::size_t ptr_count = live_objects->count(ptr);
+ for (std::size_t 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);
+ if (k == live_object_count->end()) {
+ warning_(_("Failed to find %1 in live object counts") << cls_name);
+ memory_tracing_active = true;
+ return;
+ }
+
+ (*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 || ! memory_tracing_active) 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 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 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);
+ }
+ }
+}
+
+} // namespace ledger
+
+#endif // VERIFY_ON
+
+/**********************************************************************
+ *
+ * String wrapper
+ */
+
+namespace ledger {
+
+#if defined(VERIFY_ON) || defined(HAVE_BOOST_PYTHON)
+
+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(size_type len, char x) : std::string(len, x) {
+ TRACE_CTOR(string, "size_type, 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, size_type x) : std::string(str, x) {
+ TRACE_CTOR(string, "const string&, size_type");
+}
+string::string(const string& str, size_type x, size_type y)
+ : std::string(str, x, y) {
+ TRACE_CTOR(string, "const string&, size_type, size_type");
+}
+string::string(const char * str, size_type x) : std::string(str, x) {
+ TRACE_CTOR(string, "const char *, size_type");
+}
+string::string(const char * str, size_type x, size_type y)
+ : std::string(str, x, y) {
+ TRACE_CTOR(string, "const char *, size_type, size_type");
+}
+string::~string() throw() {
+ TRACE_DTOR(string);
+}
+
+#endif // defined(VERIFY_ON) || defined(HAVE_BOOST_PYTHON)
+
+string empty_string("");
+
+strings_list split_arguments(const char * line)
+{
+ strings_list args;
+
+ char buf[4096];
+ char * q = buf;
+ char in_quoted_string = '\0';
+
+ for (const char * p = line; *p; p++) {
+ if (! in_quoted_string && std::isspace(*p)) {
+ if (q != buf) {
+ *q = '\0';
+ args.push_back(buf);
+ q = buf;
+ }
+ }
+ else if (in_quoted_string != '\'' && *p == '\\') {
+ p++;
+ if (! *p)
+ throw_(std::logic_error, _("Invalid use of backslash"));
+ *q++ = *p;
+ }
+ else if (in_quoted_string != '"' && *p == '\'') {
+ if (in_quoted_string == '\'')
+ in_quoted_string = '\0';
+ else
+ in_quoted_string = '\'';
+ }
+ else if (in_quoted_string != '\'' && *p == '"') {
+ if (in_quoted_string == '"')
+ in_quoted_string = '\0';
+ else
+ in_quoted_string = '"';
+ }
+ else {
+ *q++ = *p;
+ }
+ }
+
+ if (in_quoted_string)
+ throw_(std::logic_error,
+ _("Unterminated string, expected '%1'") << in_quoted_string);
+
+ if (q != buf) {
+ *q = '\0';
+ args.push_back(buf);
+ }
+
+ return args;
+}
+
+} // namespace ledger
+
+/**********************************************************************
+ *
+ * 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)
+uint8_t _trace_level;
+#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)
+{
+ if (! logger_has_run) {
+ logger_has_run = true;
+ logger_start = TRUE_CURRENT_TIME();
+
+#if defined(VERIFY_ON)
+ IF_VERIFY()
+ *_log_stream << " TIME OBJSZ MEMSZ" << std::endl;
+#else
+ IF_VERIFY()
+ *_log_stream << " TIME" << std::endl;
+#endif
+ }
+
+ *_log_stream << std::right << std::setw(5)
+ << (TRUE_CURRENT_TIME() -
+ logger_start).total_milliseconds() << "ms";
+
+#if defined(VERIFY_ON)
+ 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());
+ }
+#endif
+
+ *_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() << std::endl;
+ _log_buffer.str("");
+
+ return true;
+}
+
+} // namespace ledger
+
+#if defined(DEBUG_ON)
+
+namespace ledger {
+
+optional<std::string> _log_category;
+
+struct __maybe_enable_debugging {
+ __maybe_enable_debugging() {
+ const char * p = std::getenv("LEDGER_DEBUG");
+ if (p != NULL) {
+ _log_level = LOG_DEBUG;
+ _log_category = p;
+ }
+ }
+} __maybe_enable_debugging_obj;
+
+} // namespace ledger
+
+#endif // DEBUG_ON
+#endif // LOGGING_ON
+
+/**********************************************************************
+ *
+ * Timers (allows log xacts 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(TRUE_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)
+ bool tracing_active = memory_tracing_active;
+ 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 = TRUE_CURRENT_TIME();
+ (*i).second.active = true;
+ }
+ _log_buffer.str("");
+
+#if defined(VERIFY_ON)
+ memory_tracing_active = tracing_active;
+#endif
+}
+
+void stop_timer(const char * name)
+{
+#if defined(VERIFY_ON)
+ bool tracing_active = memory_tracing_active;
+ memory_tracing_active = false;
+#endif
+
+ timer_map::iterator i = timers.find(name);
+ assert(i != timers.end());
+
+ (*i).second.spent += TRUE_CURRENT_TIME() - (*i).second.begin;
+ (*i).second.active = false;
+
+#if defined(VERIFY_ON)
+ memory_tracing_active = tracing_active;
+#endif
+}
+
+void finish_timer(const char * name)
+{
+#if defined(VERIFY_ON)
+ bool tracing_active = memory_tracing_active;
+ memory_tracing_active = false;
+#endif
+
+ timer_map::iterator i = timers.find(name);
+ if (i == timers.end()) {
+#if defined(VERIFY_ON)
+ memory_tracing_active = tracing_active;
+#endif
+ return;
+ }
+
+ time_duration spent = (*i).second.spent;
+ if ((*i).second.active) {
+ spent = TRUE_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 = tracing_active;
+#endif
+}
+
+} // namespace ledger
+
+#endif // LOGGING_ON && TIMERS_ON
+
+/**********************************************************************
+ *
+ * Signal handlers
+ */
+
+caught_signal_t caught_signal = NONE_CAUGHT;
+
+void sigint_handler(int)
+{
+ caught_signal = INTERRUPTED;
+}
+
+void sigpipe_handler(int)
+{
+ caught_signal = PIPE_CLOSED;
+}
+
+/**********************************************************************
+ *
+ * General utility functions
+ */
+
+namespace ledger {
+
+const string version = PACKAGE_VERSION;
+
+path expand_path(const path& pathname)
+{
+ if (pathname.empty())
+ return pathname;
+
+ std::string path_string = pathname.string();
+ const char * pfx = NULL;
+ string::size_type pos = path_string.find_first_of('/');
+
+ if (path_string.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(path_string, 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 += path_string.substr(pos + 1);
+
+ return result;
+}
+
+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..ab8fb495
--- /dev/null
+++ b/src/utils.h
@@ -0,0 +1,713 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @defgroup util General utilities
+ */
+
+/**
+ * @file utils.h
+ * @author John Wiegley
+ *
+ * @ingroup util
+ *
+ * @brief General utility facilities used by Ledger
+ */
+#ifndef _UTILS_H
+#define _UTILS_H
+
+/**
+ * @name 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 TRACING_ON 1 // use --trace X to enable
+#define TIMERS_ON 1
+#endif
+
+/*@}*/
+
+/**
+ * @name Forward declarations
+ */
+/*@{*/
+
+namespace ledger {
+ using namespace boost;
+
+#if defined(VERIFY_ON) || defined(HAVE_BOOST_PYTHON)
+ 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;
+}
+
+/*@}*/
+
+/**
+ * @name 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, std::size_t line);
+}
+
+#define assert(x) \
+ ((x) ? ((void)0) : debug_assert(#x, BOOST_CURRENT_FUNCTION, \
+ __FILE__, __LINE__))
+
+#else // ! ASSERTS_ON
+
+#define assert(x)
+
+#endif // ASSERTS_ON
+
+/*@}*/
+
+/**
+ * @name Verification (i.e., heavy 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);
+
+} // namespace ledger
+
+#else // ! VERIFY_ON
+
+#define VERIFY(x)
+#define DO_VERIFY() true
+#define TRACE_CTOR(cls, args)
+#define TRACE_DTOR(cls)
+
+#endif // VERIFY_ON
+
+#define IF_VERIFY() if (DO_VERIFY())
+
+/*@}*/
+
+/**
+ * @name String wrapper
+ *
+ * This string type is a wrapper around std::string that allows us to trace
+ * constructor and destructor calls. It also makes ledger's use of strings a
+ * unique type, that the Boost.Python code can use as the basis for
+ * transparent Unicode conversions.
+ */
+/*@{*/
+
+namespace ledger {
+
+#if defined(VERIFY_ON) || defined(HAVE_BOOST_PYTHON)
+
+class string : public std::string
+{
+public:
+ string();
+ string(const string& str);
+ string(const std::string& str);
+ string(size_type len, char x);
+ template<class _InputIterator>
+ string(_InputIterator __beg, _InputIterator __end)
+ : std::string(__beg, __end) {
+ TRACE_CTOR(string, "InputIterator, InputIterator");
+ }
+ string(const char * str);
+ string(const char * str, const char * end);
+ string(const string& str, size_type x);
+ string(const string& str, size_type x, size_type y);
+ string(const char * str, size_type x);
+ string(const char * str, size_type x, size_type y);
+ ~string() throw();
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & boost::serialization::base_object<std::string>(*this);
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+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; }
+
+#endif // defined(VERIFY_ON) || defined(HAVE_BOOST_PYTHON)
+
+extern string empty_string;
+
+strings_list split_arguments(const char * line);
+
+} // namespace ledger
+
+/*@}*/
+
+/**
+ * @name Tracing and 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 uint8_t _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())
+
+/*@}*/
+
+/**
+ * @name Timers
+ * This allows log xacts 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
+
+/*@}*/
+
+/*
+ * These files define the other internal facilities.
+ */
+
+#include "error.h"
+
+enum caught_signal_t {
+ NONE_CAUGHT,
+ INTERRUPTED,
+ PIPE_CLOSED
+};
+
+extern caught_signal_t caught_signal;
+
+void sigint_handler(int sig);
+void sigpipe_handler(int sig);
+
+inline void check_for_signal() {
+ switch (caught_signal) {
+ case NONE_CAUGHT:
+ break;
+ case INTERRUPTED:
+ throw std::runtime_error(_("Interrupted by user (use Control-D to quit)"));
+ case PIPE_CLOSED:
+ throw std::runtime_error(_("Pipe terminated"));
+ }
+}
+
+/**
+ * @name 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 * trim_ws(char * ptr) {
+ std::size_t len = std::strlen(ptr);
+ int i = int(len) - 1;
+ while (i >= 0 && (ptr[i] == ' ' || ptr[i] == '\t' || ptr[i] == '\n'))
+ ptr[i--] = '\0';
+ return skip_ws(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 = static_cast<char>(in.peek());
+ while (in.good() && ! in.eof() && std::isspace(c)) {
+ in.get(c);
+ c = static_cast<char>(in.peek());
+ }
+ return c;
+}
+
+#define READ_INTO(str, targ, size, var, cond) { \
+ char * _p = targ; \
+ var = static_cast<char>(str.peek()); \
+ while (str.good() && ! str.eof() && var != '\n' && \
+ (cond) && _p - targ < size) { \
+ str.get(var); \
+ if (str.eof()) \
+ break; \
+ if (var == '\\') { \
+ str.get(var); \
+ if (in.eof()) \
+ break; \
+ switch (var) { \
+ case 'b': var = '\b'; break; \
+ case 'f': var = '\f'; break; \
+ case 'n': var = '\n'; break; \
+ case 'r': var = '\r'; break; \
+ case 't': var = '\t'; break; \
+ case 'v': var = '\v'; break; \
+ default: break; \
+ } \
+ } \
+ *_p++ = var; \
+ var = static_cast<char>(str.peek()); \
+ } \
+ *_p = '\0'; \
+ }
+
+#define READ_INTO_(str, targ, size, var, idx, cond) { \
+ char * _p = targ; \
+ var = static_cast<char>(str.peek()); \
+ while (str.good() && ! 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; \
+ switch (var) { \
+ case 'b': var = '\b'; break; \
+ case 'f': var = '\f'; break; \
+ case 'n': var = '\n'; break; \
+ case 'r': var = '\r'; break; \
+ case 't': var = '\t'; break; \
+ case 'v': var = '\v'; break; \
+ default: break; \
+ } \
+ idx++; \
+ } \
+ *_p++ = var; \
+ var = static_cast<char>(str.peek()); \
+ } \
+ *_p = '\0'; \
+ }
+
+inline string to_hex(uint_least32_t * message_digest, const int len = 1)
+{
+ std::ostringstream buf;
+
+ for(int i = 0; i < 5 ; i++) {
+ buf.width(8);
+ buf.fill('0');
+ buf << std::hex << message_digest[i];
+ if (i + 1 >= len)
+ break; // only output the first LEN dwords
+ }
+ return buf.str();
+}
+
+class push_xml
+{
+ std::ostream& out;
+ string tag;
+ bool leave_open;
+
+public:
+ push_xml(std::ostream& _out, const string& _tag, bool has_attrs = false,
+ bool _leave_open = false)
+ : out(_out), tag(_tag), leave_open(_leave_open) {
+ out << '<' << tag;
+ if (! has_attrs)
+ out << '>';
+ }
+ ~push_xml() {
+ if (! leave_open)
+ out << "</" << tag << '>';
+ }
+
+ void close_attrs() {
+ out << '>';
+ }
+
+ static string guard(const string& str) {
+ std::ostringstream buf;
+ foreach (const char& ch, str) {
+ switch (ch) {
+ case '<':
+ buf << "&lt;";
+ break;
+ case '>':
+ buf << "&gt;";
+ break;
+ case '&':
+ buf << "&amp;";
+ break;
+ default:
+ buf << ch;
+ break;
+ }
+ }
+ return buf.str();
+ }
+};
+
+extern const string version;
+
+} // namespace ledger
+
+/*@}*/
+
+#endif // _UTILS_H
diff --git a/src/value.cc b/src/value.cc
new file mode 100644
index 00000000..cce4c4e8
--- /dev/null
+++ b/src/value.cc
@@ -0,0 +1,1864 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "value.h"
+#include "commodity.h"
+#include "annotate.h"
+#include "pool.h"
+#include "unistring.h" // for justify()
+
+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 BALANCE:
+ data = new balance_t(*boost::get<balance_t *>(rhs.data));
+ break;
+ case SEQUENCE:
+ data = new sequence_t(*boost::get<sequence_t *>(rhs.data));
+ break;
+
+ default:
+ data = rhs.data;
+ break;
+ }
+
+ return *this;
+}
+
+void value_t::initialize()
+{
+ true_value = new storage_t;
+ true_value->type = BOOLEAN;
+ true_value->data = true;
+
+ false_value = new storage_t;
+ false_value->type = BOOLEAN;
+ false_value->data = false;
+}
+
+void value_t::shutdown()
+{
+ true_value = intrusive_ptr<storage_t>();
+ false_value = intrusive_ptr<storage_t>();
+}
+
+value_t::operator bool() const
+{
+ switch (type()) {
+ case VOID:
+ return false;
+ 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 STRING:
+ return ! as_string().empty();
+ case MASK: {
+ std::ostringstream out;
+ out << *this;
+ throw_(value_error,
+ _("Cannot determine truth of %1 (did you mean 'account =~ %2'?)")
+ << label() << out.str());
+ }
+ case SEQUENCE:
+ if (! as_sequence().empty()) {
+ foreach (const value_t& value, as_sequence()) {
+ if (value)
+ return true;
+ }
+ }
+ return false;
+ case SCOPE:
+ return as_scope() != NULL;
+ default:
+ break;
+ }
+
+ throw_(value_error, _("Cannot determine truth of %1") << label());
+ return false;
+}
+
+void value_t::set_type(type_t new_type)
+{
+ if (new_type == VOID) {
+#if BOOST_VERSION >= 103700
+ storage.reset();
+#else
+ storage = intrusive_ptr<storage_t>();
+#endif
+ } else {
+ if (! storage || storage->refc > 1)
+ storage = new storage_t;
+ else
+ storage->destroy();
+ storage->type = new_type;
+ }
+}
+
+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();
+ }
+}
+
+int value_t::to_int() const
+{
+ if (is_long()) {
+ return static_cast<int>(as_long());
+ } else {
+ value_t temp(*this);
+ temp.in_place_cast(INTEGER);
+ return static_cast<int>(temp.as_long());
+ }
+}
+
+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();
+ }
+}
+
+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();
+ }
+}
+
+mask_t value_t::to_mask() const
+{
+ if (is_mask()) {
+ return as_mask();
+ } else {
+ value_t temp(*this);
+ temp.in_place_cast(MASK);
+ return temp.as_mask();
+ }
+}
+
+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()
+{
+#if defined(DEBUG_ON)
+ LOGGER("value.simplify");
+#endif
+
+ if (is_realzero()) {
+ DEBUG_("Zeroing type " << static_cast<int>(type()));
+ set_long(0L);
+ return;
+ }
+
+ if (is_balance() && as_balance().single_amount()) {
+ DEBUG_("Reducing balance to amount");
+ DEBUG_("as a balance it looks like: " << *this);
+ in_place_cast(AMOUNT);
+ DEBUG_("as an amount it looks like: " << *this);
+ }
+
+#ifdef REDUCE_TO_INTEGER // this is off by default
+ 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::number() const
+{
+ switch (type()) {
+ case VOID:
+ return 0L;
+ case BOOLEAN:
+ return as_boolean() ? 1L : 0L;
+ case INTEGER:
+ return as_long();
+ case AMOUNT:
+ return as_amount().number();
+ case BALANCE:
+ return as_balance().number();
+ case SEQUENCE:
+ if (! as_sequence().empty()) {
+ value_t temp;
+ foreach (const value_t& value, as_sequence())
+ temp += value.number();
+ return temp;
+ }
+ break;
+ default:
+ break;
+ }
+
+ throw_(value_error, _("Cannot determine numeric value of %1") << label());
+ return false;
+}
+
+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()) {
+ if (size() == val.size()) {
+ sequence_t::iterator i = begin();
+ sequence_t::const_iterator j = val.begin();
+
+ for (; i != end(); i++, j++)
+ *i += *j;
+ } else {
+ throw_(value_error, _("Cannot add sequences of different lengths"));
+ }
+ } else {
+ as_sequence_lval().push_back(val);
+ }
+ return *this;
+ }
+
+ switch (type()) {
+ case DATETIME:
+ switch (val.type()) {
+ case INTEGER:
+ as_datetime_lval() +=
+ time_duration_t(0, 0, static_cast<time_duration_t::sec_type>(val.as_long()));
+ return *this;
+ case AMOUNT:
+ as_datetime_lval() +=
+ time_duration_t(0, 0, static_cast<time_duration_t::sec_type>
+ (val.as_amount().to_long()));
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ case DATE:
+ switch (val.type()) {
+ case INTEGER:
+ as_date_lval() += gregorian::date_duration(val.as_long());
+ return *this;
+ case AMOUNT:
+ as_date_lval() += gregorian::date_duration(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:
+ if (val.as_amount().has_commodity()) {
+ in_place_cast(BALANCE);
+ return *this += val;
+ }
+ 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;
+ 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;
+
+ 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;
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ throw_(value_error, _("Cannot add %1 to %2") << val.label() << 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()) {
+ if (size() == val.size()) {
+ sequence_t::iterator i = begin();
+ sequence_t::const_iterator j = val.begin();
+
+ for (; i != end(); i++, j++)
+ *i -= *j;
+ } else {
+ throw_(value_error, _("Cannot subtract sequences of different lengths"));
+ }
+ } 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() -=
+ time_duration_t(0, 0, static_cast<time_duration_t::sec_type>(val.as_long()));
+ return *this;
+ case AMOUNT:
+ as_datetime_lval() -=
+ time_duration_t(0, 0, static_cast<time_duration_t::sec_type>
+ (val.as_amount().to_long()));
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ case DATE:
+ switch (val.type()) {
+ case INTEGER:
+ as_date_lval() -= gregorian::date_duration(val.as_long());
+ return *this;
+ case AMOUNT:
+ as_date_lval() -= gregorian::date_duration(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;
+ 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;
+
+ 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;
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ throw_(value_error, _("Cannot subtract %1 from %2") << val.label() << 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:
+ as_amount_lval() *= val.as_amount();
+ return *this;
+ case BALANCE:
+ if (val.as_balance().single_amount()) {
+ as_amount_lval() *= val.simplified().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 (as_balance().single_amount()) {
+ in_place_simplify();
+ as_amount_lval() *= val.as_amount();
+ return *this;
+ }
+ else if (! val.as_amount().has_commodity()) {
+ as_balance_lval() *= val.as_amount();
+ return *this;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ DEBUG("value.multiply.error", "Left: " << *this);
+ DEBUG("value.multiply.error", "Right: " << val);
+
+ throw_(value_error, _("Cannot multiply %1 with %2") << label() << 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:
+ as_amount_lval() /= val.as_amount();
+ return *this;
+ case BALANCE:
+ if (val.as_balance().single_amount()) {
+ value_t simpler(val.simplified());
+ switch (simpler.type()) {
+ case INTEGER:
+ as_amount_lval() /= simpler.as_long();
+ break;
+ case AMOUNT:
+ as_amount_lval() /= simpler.as_amount();
+ break;
+ default:
+ assert(false);
+ break;
+ }
+ return *this;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case BALANCE:
+ switch (val.type()) {
+ case INTEGER:
+ as_balance_lval() /= val.as_long();
+ return *this;
+ case AMOUNT:
+ if (as_balance().single_amount()) {
+ in_place_simplify();
+ as_amount_lval() /= val.as_amount();
+ return *this;
+ }
+ else if (! val.as_amount().has_commodity()) {
+ as_balance_lval() /= val.as_amount();
+ return *this;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ throw_(value_error, _("Cannot divide %1 by %2") << label() << val.label());
+
+ return *this;
+}
+
+
+bool value_t::is_equal_to(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();
+ 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();
+ 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();
+ default:
+ break;
+ }
+ break;
+
+ case STRING:
+ if (val.is_string())
+ return as_string() == val.as_string();
+ break;
+
+ case MASK:
+ if (val.is_mask())
+ return as_mask() == val.as_mask();
+ break;
+
+ case SEQUENCE:
+ if (val.is_sequence())
+ return as_sequence() == val.as_sequence();
+ break;
+
+ default:
+ break;
+ }
+
+ throw_(value_error, _("Cannot compare %1 by %2") << label() << val.label());
+
+ return *this;
+}
+
+bool value_t::is_less_than(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:
+ try {
+ return as_amount() < val.as_amount();
+ }
+ catch (const amount_error&) {
+ return compare_amount_commodities()(&as_amount(), &val.as_amount());
+ }
+ default:
+ break;
+ }
+ break;
+
+ case BALANCE:
+ switch (val.type()) {
+ case INTEGER:
+ case AMOUNT: {
+ if (val.is_nonzero())
+ break;
+
+ bool no_amounts = true;
+ foreach (const balance_t::amounts_map::value_type& pair,
+ as_balance().amounts) {
+ if (pair.second >= 0L)
+ return false;
+ no_amounts = false;
+ }
+ return ! no_amounts;
+ }
+ default:
+ break;
+ }
+ break;
+
+ case STRING:
+ if (val.is_string())
+ return as_string() < val.as_string();
+ break;
+
+ case SEQUENCE:
+ switch (val.type()) {
+ case INTEGER:
+ case AMOUNT: {
+ if (val.is_nonzero())
+ break;
+
+ bool no_amounts = true;
+ foreach (const value_t& value, as_sequence()) {
+ if (value >= 0L)
+ return false;
+ no_amounts = false;
+ }
+ return ! no_amounts;
+ }
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ throw_(value_error, _("Cannot compare %1 to %2") << label() << val.label());
+
+ return *this;
+}
+
+bool value_t::is_greater_than(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 BALANCE:
+ switch (val.type()) {
+ case INTEGER:
+ case AMOUNT: {
+ if (val.is_nonzero())
+ break;
+
+ bool no_amounts = true;
+ foreach (const balance_t::amounts_map::value_type& pair,
+ as_balance().amounts) {
+ if (pair.second <= 0L)
+ return false;
+ no_amounts = false;
+ }
+ return ! no_amounts;
+ }
+ default:
+ break;
+ }
+ break;
+
+ case STRING:
+ if (val.is_string())
+ return as_string() > val.as_string();
+ break;
+
+ case SEQUENCE:
+ switch (val.type()) {
+ case INTEGER:
+ case AMOUNT: {
+ if (val.is_nonzero())
+ break;
+
+ bool no_amounts = true;
+ foreach (const value_t& value, as_sequence()) {
+ if (value <= 0L)
+ return false;
+ no_amounts = false;
+ }
+ return ! no_amounts;
+ }
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ throw_(value_error, _("Cannot compare %1 to %2") << label() << val.label());
+
+ return *this;
+}
+
+void value_t::in_place_cast(type_t cast_type)
+{
+ if (type() == cast_type)
+ return;
+
+ _dup();
+
+ 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 AMOUNT:
+ set_amount(as_boolean() ? 1L : 0L);
+ return;
+ case STRING:
+ set_string(as_boolean() ? "true" : "false");
+ return;
+ default:
+ break;
+ }
+ break;
+
+ case DATE:
+ switch (cast_type) {
+ case DATETIME:
+ set_datetime(datetime_t(as_date(), time_duration(0, 0, 0, 0)));
+ return;
+ case STRING:
+ set_string(format_date(as_date(), FMT_WRITTEN));
+ return;
+ default:
+ break;
+ }
+ break;
+ case DATETIME:
+ switch (cast_type) {
+ case DATE:
+ set_date(as_datetime().date());
+ return;
+ case STRING:
+ set_string(format_datetime(as_datetime(), FMT_WRITTEN));
+ return;
+ default:
+ break;
+ }
+ break;
+
+ case INTEGER:
+ switch (cast_type) {
+ case AMOUNT:
+ set_amount(as_long());
+ return;
+ case BALANCE:
+ set_balance(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());
+ return;
+ case STRING:
+ if (amt.is_null())
+ set_string("");
+ else
+ set_string(as_amount().to_string());
+ return;
+ default:
+ break;
+ }
+ break;
+ }
+
+ case BALANCE: {
+ const balance_t& bal(as_balance());
+ switch (cast_type) {
+ case AMOUNT: {
+ if (bal.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((*bal.amounts.begin()).second));
+ return;
+ }
+ else if (bal.amounts.size() == 0) {
+ set_amount(0L);
+ return;
+ }
+ else {
+ throw_(value_error, _("Cannot convert %1 with multiple commodities to %2")
+ << label() << label(cast_type));
+ }
+ break;
+ }
+ case STRING:
+ if (bal.is_empty())
+ set_string("");
+ else
+ set_string(as_balance().to_string());
+ 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;
+ }
+ break;
+ }
+ case AMOUNT:
+ set_amount(amount_t(as_string()));
+ return;
+ case DATE:
+ set_date(parse_date(as_string()));
+ return;
+ case DATETIME:
+ set_datetime(parse_datetime(as_string()));
+ return;
+ case MASK:
+ set_mask(as_string());
+ return;
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ throw_(value_error,
+ _("Cannot convert %1 to %2") << label() << 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 SEQUENCE: {
+ value_t temp;
+ foreach (const value_t& value, as_sequence())
+ temp.push_back(- value);
+ *this = temp;
+ return;
+ }
+ default:
+ break;
+ }
+
+ throw_(value_error, _("Cannot negate %1") << label());
+}
+
+void value_t::in_place_not()
+{
+ switch (type()) {
+ case BOOLEAN:
+ set_boolean(! as_boolean());
+ return;
+ case INTEGER:
+ case DATETIME:
+ set_boolean(! as_long());
+ return;
+ case DATE:
+ set_boolean(! as_long());
+ return;
+ case AMOUNT:
+ set_boolean(! as_amount());
+ return;
+ case BALANCE:
+ set_boolean(! as_balance());
+ return;
+ case STRING:
+ set_boolean(as_string().empty());
+ return;
+ case SEQUENCE: {
+ value_t temp;
+ foreach (const value_t& value, as_sequence())
+ temp.push_back(! value);
+ *this = temp;
+ return;
+ }
+ default:
+ break;
+ }
+
+ throw_(value_error, _("Cannot 'not' %1") << 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 STRING:
+ return as_string().empty();
+ case SEQUENCE:
+ return as_sequence().empty();
+
+ case SCOPE:
+ return as_scope() == NULL;
+
+ default:
+ throw_(value_error, _("Cannot determine if %1 is really zero") << label());
+ }
+ return false;
+}
+
+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 STRING:
+ return as_string().empty();
+ case SEQUENCE:
+ return as_sequence().empty();
+
+ case SCOPE:
+ return as_scope() == NULL;
+
+ default:
+ throw_(value_error, _("Cannot determine if %1 is zero") << label());
+ }
+ return false;
+}
+
+value_t value_t::value(const bool primary_only,
+ const optional<datetime_t>& moment,
+ const optional<commodity_t&>& in_terms_of) const
+{
+ switch (type()) {
+ case INTEGER:
+ return NULL_VALUE;
+
+ case AMOUNT:
+ if (optional<amount_t> val =
+ as_amount().value(primary_only, moment, in_terms_of))
+ return *val;
+ return NULL_VALUE;
+
+ case BALANCE:
+ if (optional<balance_t> bal =
+ as_balance().value(primary_only, moment, in_terms_of))
+ return *bal;
+ return NULL_VALUE;
+
+ default:
+ break;
+ }
+
+ throw_(value_error, _("Cannot find the value of %1") << label());
+ return NULL_VALUE;
+}
+
+value_t value_t::price() const
+{
+ switch (type()) {
+ case AMOUNT:
+ return as_amount().price();
+ case BALANCE:
+ return as_balance().price();
+ default:
+ return *this;
+ }
+}
+
+value_t value_t::exchange_commodities(const std::string& commodities,
+ const bool add_prices,
+ const optional<datetime_t>& moment)
+{
+ scoped_array<char> buf(new char[commodities.length() + 1]);
+
+ std::strcpy(buf.get(), commodities.c_str());
+
+ for (char * p = std::strtok(buf.get(), ",");
+ p;
+ p = std::strtok(NULL, ",")) {
+ if (commodity_t * commodity =
+ commodity_pool_t::current_pool->parse_price_expression(p, add_prices,
+ moment)) {
+ value_t result = value(false, moment, *commodity);
+ if (! result.is_null())
+ return result;
+ }
+ }
+ return *this;
+}
+
+void value_t::in_place_reduce()
+{
+ switch (type()) {
+ case AMOUNT:
+ as_amount_lval().in_place_reduce();
+ return;
+ case BALANCE:
+ as_balance_lval().in_place_reduce();
+ return;
+ default:
+ return;
+ }
+
+ //throw_(value_error, "Cannot reduce " << label());
+}
+
+void value_t::in_place_unreduce()
+{
+ switch (type()) {
+ case AMOUNT:
+ as_amount_lval().in_place_unreduce();
+ return;
+ case BALANCE:
+ as_balance_lval().in_place_unreduce();
+ return;
+ default:
+ return;
+ }
+
+ //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();
+ default:
+ break;
+ }
+
+ throw_(value_error, _("Cannot abs %1") << label());
+ return NULL_VALUE;
+}
+
+void value_t::in_place_round()
+{
+ switch (type()) {
+ case INTEGER:
+ return;
+ case AMOUNT:
+ as_amount_lval().in_place_round();
+ return;
+ case BALANCE:
+ as_balance_lval().in_place_round();
+ return;
+ case SEQUENCE: {
+ value_t temp;
+ foreach (const value_t& value, as_sequence())
+ temp.push_back(value.rounded());
+ *this = temp;
+ return;
+ }
+ default:
+ break;
+ }
+
+ throw_(value_error, _("Cannot set rounding for %1") << label());
+}
+
+void value_t::in_place_truncate()
+{
+ switch (type()) {
+ case INTEGER:
+ return;
+ case AMOUNT:
+ as_amount_lval().in_place_truncate();
+ return;
+ case BALANCE:
+ as_balance_lval().in_place_truncate();
+ return;
+ case SEQUENCE: {
+ value_t temp;
+ foreach (const value_t& value, as_sequence())
+ temp.push_back(value.truncated());
+ *this = temp;
+ return;
+ }
+ default:
+ break;
+ }
+
+ throw_(value_error, _("Cannot truncate %1") << label());
+}
+
+void value_t::in_place_floor()
+{
+ switch (type()) {
+ case INTEGER:
+ return;
+ case AMOUNT:
+ as_amount_lval().in_place_floor();
+ return;
+ case BALANCE:
+ as_balance_lval().in_place_floor();
+ return;
+ case SEQUENCE: {
+ value_t temp;
+ foreach (const value_t& value, as_sequence())
+ temp.push_back(value.floored());
+ *this = temp;
+ return;
+ }
+ default:
+ break;
+ }
+
+ throw_(value_error, _("Cannot floor %1") << label());
+}
+
+void value_t::in_place_unround()
+{
+ switch (type()) {
+ case INTEGER:
+ return;
+ case AMOUNT:
+ as_amount_lval().in_place_unround();
+ return;
+ case BALANCE:
+ as_balance_lval().in_place_unround();
+ return;
+ case SEQUENCE: {
+ value_t temp;
+ foreach (const value_t& value, as_sequence())
+ temp.push_back(value.unrounded());
+ *this = temp;
+ return;
+ }
+ default:
+ break;
+ }
+
+ throw_(value_error, _("Cannot unround %1") << label());
+}
+
+void value_t::annotate(const annotation_t& details)
+{
+ if (is_amount())
+ as_amount_lval().annotate(details);
+ else
+ throw_(value_error, _("Cannot annotate %1") << label());
+}
+
+bool value_t::has_annotation() const
+{
+ if (is_amount())
+ return as_amount().has_annotation();
+ else
+ throw_(value_error,
+ _("Cannot determine whether %1 is annotated") << label());
+ return false;
+}
+
+annotation_t& value_t::annotation()
+{
+ if (is_amount())
+ return as_amount_lval().annotation();
+ else {
+ throw_(value_error, _("Cannot request annotation of %1") << label());
+ return as_amount_lval().annotation(); // quiet g++ warning
+ }
+}
+
+value_t value_t::strip_annotations(const keep_details_t& what_to_keep) const
+{
+ if (what_to_keep.keep_all())
+ return *this;
+
+ switch (type()) {
+ case VOID:
+ case BOOLEAN:
+ case INTEGER:
+ case DATETIME:
+ case DATE:
+ case STRING:
+ case MASK:
+ case SCOPE:
+ return *this;
+
+ case SEQUENCE: {
+ sequence_t temp;
+ foreach (const value_t& value, as_sequence())
+ temp.push_back(value.strip_annotations(what_to_keep));
+ return temp;
+ }
+
+ case AMOUNT:
+ return as_amount().strip_annotations(what_to_keep);
+ case BALANCE:
+ return as_balance().strip_annotations(what_to_keep);
+
+ default:
+ assert(false);
+ break;
+ }
+ assert(false);
+ return NULL_VALUE;
+}
+
+void value_t::print(std::ostream& out,
+ const int first_width,
+ const int latter_width,
+ const bool right_justify,
+ const bool colorize) const
+{
+ if (first_width > 0 &&
+ (! is_amount() || as_amount().is_zero()) &&
+ ! is_balance() && ! is_string()) {
+ out.width(first_width);
+
+ if (right_justify)
+ out << std::right;
+ else
+ out << std::left;
+ }
+
+ switch (type()) {
+ case VOID:
+ out << "";
+ break;
+
+ case BOOLEAN:
+ out << (as_boolean() ? "1" : "0");
+ break;
+
+ case DATETIME:
+ out << format_datetime(as_datetime(), FMT_WRITTEN);
+ break;
+
+ case DATE:
+ out << format_date(as_date(), FMT_WRITTEN);
+ break;
+
+ case INTEGER:
+ if (colorize && as_long() < 0)
+ justify(out, to_string(), first_width, right_justify, true);
+ else
+ out << as_long();
+ break;
+
+ case AMOUNT: {
+ if (as_amount().is_zero()) {
+ out << 0;
+ } else {
+ std::ostringstream buf;
+ buf << as_amount();
+ justify(out, buf.str(), first_width, right_justify,
+ colorize && as_amount().sign() < 0);
+ }
+ break;
+ }
+
+ case BALANCE:
+ as_balance().print(out, first_width, latter_width, right_justify,
+ colorize);
+ break;
+
+ case STRING:
+ if (first_width > 0)
+ justify(out, as_string(), first_width, right_justify);
+ else
+ out << as_string();
+ break;
+
+ case MASK:
+ out << '/' << as_mask() << '/';
+ break;
+
+ case SEQUENCE: {
+ out << '(';
+ bool first = true;
+ foreach (const value_t& value, as_sequence()) {
+ if (first)
+ first = false;
+ else
+ out << ", ";
+
+ value.print(out, first_width, latter_width, right_justify,
+ colorize);
+ }
+ out << ')';
+ break;
+ }
+
+ case SCOPE:
+ out << "<SCOPE>";
+ break;
+
+ default:
+ throw_(value_error, _("Cannot print %1") << label());
+ }
+}
+
+void value_t::dump(std::ostream& out, const bool relaxed) const
+{
+ switch (type()) {
+ case VOID:
+ out << "null";
+ break;
+
+ case BOOLEAN:
+ if (as_boolean())
+ out << "true";
+ else
+ out << "false";
+ break;
+
+ case DATETIME:
+ out << '[' << format_datetime(as_datetime(), FMT_WRITTEN) << ']';
+ break;
+ case DATE:
+ out << '[' << format_date(as_date(), FMT_WRITTEN) << ']';
+ break;
+
+ case INTEGER:
+ out << as_long();
+ break;
+
+ case AMOUNT:
+ if (! relaxed)
+ out << '{';
+ out << as_amount();
+ if (! relaxed)
+ out << '}';
+ break;
+
+ case BALANCE:
+ out << as_balance();
+ break;
+
+ case STRING:
+ out << '"';
+ foreach (const char& ch, as_string()) {
+ switch (ch) {
+ case '"':
+ out << "\\\"";
+ break;
+ case '\\':
+ out << "\\\\";
+ break;
+ default:
+ out << ch;
+ break;
+ }
+ }
+ out << '"';
+ break;
+
+ case MASK:
+ out << '/' << as_mask() << '/';
+ break;
+
+ case SCOPE:
+ out << as_scope();
+ break;
+
+ case SEQUENCE: {
+ out << '(';
+ bool first = true;
+ foreach (const value_t& value, as_sequence()) {
+ if (first)
+ first = false;
+ else
+ out << ", ";
+
+ value.dump(out, relaxed);
+ }
+ out << ')';
+ break;
+ }
+
+ default:
+ assert(false);
+ break;
+ }
+}
+
+bool value_t::valid() const
+{
+ switch (type()) {
+ case AMOUNT:
+ return as_amount().valid();
+ case BALANCE:
+ return as_balance().valid();
+ default:
+ break;
+ }
+ return true;
+}
+
+bool sort_value_is_less_than(const std::list<sort_value_t>& left_values,
+ const std::list<sort_value_t>& right_values)
+{
+ std::list<sort_value_t>::const_iterator left_iter = left_values.begin();
+ std::list<sort_value_t>::const_iterator right_iter = right_values.begin();
+
+ while (left_iter != left_values.end() && right_iter != right_values.end()) {
+ // Don't even try to sort balance values
+ if (! (*left_iter).value.is_balance() &&
+ ! (*right_iter).value.is_balance()) {
+ DEBUG("value.sort",
+ " Comparing " << (*left_iter).value << " < " << (*right_iter).value);
+ if ((*left_iter).value < (*right_iter).value) {
+ DEBUG("value.sort", " is less");
+ return ! (*left_iter).inverted;
+ }
+ else if ((*left_iter).value > (*right_iter).value) {
+ DEBUG("value.sort", " is greater");
+ return (*left_iter).inverted;
+ }
+ }
+ left_iter++; right_iter++;
+ }
+
+ assert(left_iter == left_values.end());
+ assert(right_iter == right_values.end());
+
+ return false;
+}
+
+void to_xml(std::ostream& out, const value_t& value)
+{
+ switch (value.type()) {
+ case value_t::VOID:
+ out << "<void />";
+ break;
+ case value_t::BOOLEAN: {
+ push_xml y(out, "boolean");
+ out << (value.as_boolean() ? "true" : "false");
+ break;
+ }
+ case value_t::INTEGER: {
+ push_xml y(out, "integer");
+ out << value.as_long();
+ break;
+ }
+
+ case value_t::AMOUNT:
+ to_xml(out, value.as_amount());
+ break;
+ case value_t::BALANCE:
+ to_xml(out, value.as_balance());
+ break;
+
+ case value_t::DATETIME:
+ to_xml(out, value.as_datetime());
+ break;
+ case value_t::DATE:
+ to_xml(out, value.as_date());
+ break;
+ case value_t::STRING: {
+ push_xml y(out, "string");
+ out << y.guard(value.as_string());
+ break;
+ }
+ case value_t::MASK:
+ to_xml(out, value.as_mask());
+ break;
+
+ case value_t::SEQUENCE: {
+ push_xml y(out, "sequence");
+ foreach (const value_t& member, value.as_sequence())
+ to_xml(out, member);
+ break;
+ }
+
+ case value_t::SCOPE:
+ default:
+ assert(false);
+ break;
+ }
+}
+
+} // namespace ledger
diff --git a/src/value.h b/src/value.h
new file mode 100644
index 00000000..ffbb89e8
--- /dev/null
+++ b/src/value.h
@@ -0,0 +1,998 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup math
+ */
+
+/**
+ * @file value.h
+ * @author John Wiegley
+ *
+ * @ingroup math
+ *
+ * @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 "balance.h" // includes amount.h
+#include "mask.h"
+
+namespace ledger {
+
+DECLARE_EXCEPTION(value_error, std::runtime_error);
+
+class scope_t;
+
+/**
+ * @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_t,
+ additive<value_t, balance_t,
+ multiplicative<value_t, balance_t,
+ ordered_field_operators<value_t, amount_t,
+ ordered_field_operators<value_t, double,
+ ordered_field_operators<value_t, unsigned long,
+ ordered_field_operators<value_t, long> > > > > > > >
+{
+public:
+ /**
+ * The sequence_t member type abstracts the type used to represent a
+ * resizable "array" of value_t objects.
+ */
+ typedef std::deque<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
+ STRING, // a string object
+ MASK, // a regular expression mask
+ SEQUENCE, // a vector of value_t objects
+ SCOPE // a pointer to a scope
+ };
+
+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.
+ */
+ variant<bool, // BOOLEAN
+ datetime_t, // DATETIME
+ date_t, // DATE
+ long, // INTEGER
+ amount_t, // AMOUNT
+ balance_t *, // BALANCE
+ string, // STRING
+ mask_t, // MASK
+ sequence_t *, // SEQUENCE
+ scope_t * // SCOPE
+ > data;
+
+ 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);
+ VERIFY(refc == 0);
+ 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);
+ VERIFY(refc >= 0);
+ refc++;
+ }
+ void release() const {
+ DEBUG("value.storage.refcount",
+ "Releasing " << this << ", refc now " << refc - 1);
+ VERIFY(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();
+ }
+
+ void destroy() {
+ DEBUG("value.storage.refcount", "Destroying " << this);
+ switch (type) {
+ case VOID:
+ return;
+ case BALANCE:
+ checked_delete(boost::get<balance_t *>(data));
+ break;
+ case SEQUENCE:
+ checked_delete(boost::get<sequence_t *>(data));
+ break;
+ default:
+ break;
+ }
+ data = false;
+ type = VOID;
+ }
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+ private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & data;
+ ar & type;
+ ar & refc;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+ };
+
+ /**
+ * The actual data for each value_t is kept in reference counted storage.
+ * Data is modified using a copy-on-write policy.
+ */
+ intrusive_ptr<storage_t> storage;
+
+ /**
+ * Make a private copy of the current value (if necessary) so it can
+ * subsequently be modified.
+ */
+ void _dup() {
+ VERIFY(storage);
+ if (storage->refc > 1)
+ storage = new storage_t(*storage.get());
+ }
+
+ /**
+ * 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:
+ 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);
+ }
+ value_t(const double val) {
+ TRACE_CTOR(value_t, "const double");
+ set_amount(val);
+ }
+ 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 mask_t& val) {
+ TRACE_CTOR(value_t, "const mask_t&");
+ set_mask(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);
+ }
+
+ explicit value_t(scope_t * item) {
+ TRACE_CTOR(value_t, "scope_t *");
+ set_scope(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 is_equal_to(const value_t& val) const;
+ bool is_less_than(const value_t& val) const;
+ bool is_greater_than(const value_t& val) const;
+
+ template <typename T>
+ bool operator==(const T& amt) const {
+ return is_equal_to(amt);
+ }
+ template <typename T>
+ bool operator<(const T& amt) const {
+ return is_less_than(amt);
+ }
+ template <typename T>
+ bool operator>(const T& amt) const {
+ return is_greater_than(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);
+
+ /**
+ * Unary arithmetic operators.
+ */
+ value_t negated() const {
+ value_t temp = *this;
+ temp.in_place_negate();
+ return temp;
+ }
+ void in_place_negate(); // exists for efficiency's sake
+ void in_place_not(); // exists for efficiency's sake
+
+ value_t operator-() const {
+ return negated();
+ }
+
+ value_t abs() const;
+
+ value_t rounded() const {
+ value_t temp(*this);
+ temp.in_place_round();
+ return temp;
+ }
+ void in_place_round();
+
+ value_t truncated() const {
+ value_t temp(*this);
+ temp.in_place_truncate();
+ return temp;
+ }
+ void in_place_truncate();
+
+ value_t floored() const {
+ value_t temp(*this);
+ temp.in_place_floor();
+ return temp;
+ }
+ void in_place_floor();
+
+ value_t unrounded() const {
+ value_t temp(*this);
+ temp.in_place_unround();
+ return temp;
+ }
+ void in_place_unround();
+
+ value_t reduced() const {
+ value_t temp(*this);
+ temp.in_place_reduce();
+ return temp;
+ }
+ void in_place_reduce(); // exists for efficiency's sake
+
+ value_t unreduced() const {
+ value_t temp(*this);
+ temp.in_place_unreduce();
+ return temp;
+ }
+ void in_place_unreduce(); // exists for efficiency's sake
+
+ // Return the "market value" of a given value at a specific time.
+ value_t value(const bool primary_only = false,
+ const optional<datetime_t>& moment = none,
+ const optional<commodity_t&>& in_terms_of = none) const;
+
+ value_t price() const;
+
+ value_t exchange_commodities(const std::string& commodities,
+ const bool add_prices = false,
+ const optional<datetime_t>& moment = none);
+
+ /**
+ * Truth tests.
+ */
+ operator bool() const;
+
+ bool is_nonzero() const {
+ return ! is_zero();
+ }
+
+ bool is_realzero() const;
+ bool is_zero() const;
+ bool is_null() const {
+ if (! storage) {
+ VERIFY(is_type(VOID));
+ return true;
+ } else {
+ VERIFY(! is_type(VOID));
+ return false;
+ }
+ }
+
+ type_t type() const {
+ return storage ? storage->type : VOID;
+ }
+ bool is_type(type_t _type) const {
+ return type() == _type;
+ }
+
+private:
+ void set_type(type_t 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_string()
+ * is_mask()
+ * 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() {
+ VERIFY(is_boolean());
+ _dup();
+ return boost::get<bool>(storage->data);
+ }
+ const bool& as_boolean() const {
+ VERIFY(is_boolean());
+ return boost::get<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() {
+ VERIFY(is_datetime());
+ _dup();
+ return boost::get<datetime_t>(storage->data);
+ }
+ const datetime_t& as_datetime() const {
+ VERIFY(is_datetime());
+ return boost::get<datetime_t>(storage->data);
+ }
+ void set_datetime(const datetime_t& val) {
+ set_type(DATETIME);
+ storage->data = val;
+ }
+
+ bool is_date() const {
+ return is_type(DATE);
+ }
+ date_t& as_date_lval() {
+ VERIFY(is_date());
+ _dup();
+ return boost::get<date_t>(storage->data);
+ }
+ const date_t& as_date() const {
+ VERIFY(is_date());
+ return boost::get<date_t>(storage->data);
+ }
+ void set_date(const date_t& val) {
+ set_type(DATE);
+ storage->data = val;
+ }
+
+ bool is_long() const {
+ return is_type(INTEGER);
+ }
+ long& as_long_lval() {
+ VERIFY(is_long());
+ _dup();
+ return boost::get<long>(storage->data);
+ }
+ const long& as_long() const {
+ VERIFY(is_long());
+ return boost::get<long>(storage->data);
+ }
+ void set_long(const long val) {
+ set_type(INTEGER);
+ storage->data = val;
+ }
+
+ bool is_amount() const {
+ return is_type(AMOUNT);
+ }
+ amount_t& as_amount_lval() {
+ VERIFY(is_amount());
+ _dup();
+ return boost::get<amount_t>(storage->data);
+ }
+ const amount_t& as_amount() const {
+ VERIFY(is_amount());
+ return boost::get<amount_t>(storage->data);
+ }
+ void set_amount(const amount_t& val) {
+ VERIFY(val.valid());
+ set_type(AMOUNT);
+ storage->data = val;
+ }
+
+ bool is_balance() const {
+ return is_type(BALANCE);
+ }
+ balance_t& as_balance_lval() {
+ VERIFY(is_balance());
+ _dup();
+ return *boost::get<balance_t *>(storage->data);
+ }
+ const balance_t& as_balance() const {
+ VERIFY(is_balance());
+ return *boost::get<balance_t *>(storage->data);
+ }
+ void set_balance(const balance_t& val) {
+ VERIFY(val.valid());
+ set_type(BALANCE);
+ storage->data = new balance_t(val);
+ }
+
+ bool is_string() const {
+ return is_type(STRING);
+ }
+ string& as_string_lval() {
+ VERIFY(is_string());
+ _dup();
+ return boost::get<string>(storage->data);
+ }
+ const string& as_string() const {
+ VERIFY(is_string());
+ return boost::get<string>(storage->data);
+ }
+ void set_string(const string& val = "") {
+ set_type(STRING);
+ storage->data = val;
+ VERIFY(boost::get<string>(storage->data) == val);
+ }
+ void set_string(const char * val = "") {
+ set_type(STRING);
+ storage->data = string(val);
+ VERIFY(boost::get<string>(storage->data) == val);
+ }
+
+ bool is_mask() const {
+ return is_type(MASK);
+ }
+ mask_t& as_mask_lval() {
+ VERIFY(is_mask());
+ _dup();
+ VERIFY(boost::get<mask_t>(storage->data).valid());
+ return boost::get<mask_t>(storage->data);
+ }
+ const mask_t& as_mask() const {
+ VERIFY(is_mask());
+ VERIFY(boost::get<mask_t>(storage->data).valid());
+ return boost::get<mask_t>(storage->data);
+ }
+ void set_mask(const string& val) {
+ set_type(MASK);
+ storage->data = mask_t(val);
+ }
+ void set_mask(const mask_t& val) {
+ set_type(MASK);
+ storage->data = val;
+ }
+
+ bool is_sequence() const {
+ return is_type(SEQUENCE);
+ }
+ sequence_t& as_sequence_lval() {
+ VERIFY(is_sequence());
+ _dup();
+ return *boost::get<sequence_t *>(storage->data);
+ }
+ const sequence_t& as_sequence() const {
+ VERIFY(is_sequence());
+ return *boost::get<sequence_t *>(storage->data);
+ }
+ void set_sequence(const sequence_t& val) {
+ set_type(SEQUENCE);
+ storage->data = new sequence_t(val);
+ }
+
+ /**
+ * Dealing with scope pointers.
+ */
+ bool is_scope() const {
+ return is_type(SCOPE);
+ }
+ scope_t * as_scope() const {
+ VERIFY(is_scope());
+ return boost::get<scope_t *>(storage->data);
+ }
+ void set_scope(scope_t * val) {
+ set_type(SCOPE);
+ storage->data = 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;
+ int to_int() 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;
+ string to_string() const;
+ mask_t to_mask() 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 casted(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 simplified() const {
+ value_t temp = *this;
+ temp.in_place_simplify();
+ return temp;
+ }
+ void in_place_simplify();
+
+ value_t number() const;
+
+ /**
+ * Annotated commodity methods.
+ */
+ void annotate(const annotation_t& details);
+ bool has_annotation() const;
+
+ annotation_t& annotation();
+ const annotation_t& annotation() const {
+ return const_cast<value_t&>(*this).annotation();
+ }
+
+ value_t strip_annotations(const keep_details_t& what_to_keep) const;
+
+ /**
+ * Collection-style access methods for SEQUENCE values.
+ */
+ value_t& operator[](const std::size_t index) {
+ VERIFY(! 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 std::size_t index) const {
+ VERIFY(! 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_front(const value_t& val) {
+ if (is_null())
+ *this = sequence_t();
+ if (! is_sequence())
+ in_place_cast(SEQUENCE);
+ as_sequence_lval().push_front(val);
+ }
+
+ void push_back(const value_t& val) {
+ if (is_null())
+ *this = sequence_t();
+ if (! is_sequence())
+ in_place_cast(SEQUENCE);
+ as_sequence_lval().push_back(val);
+ }
+
+ void pop_back() {
+ VERIFY(! is_null());
+
+ if (! is_sequence()) {
+#if BOOST_VERSION >= 103700
+ storage.reset();
+#else
+ storage = intrusive_ptr<storage_t>();
+#endif
+ } else {
+ as_sequence_lval().pop_back();
+
+ const sequence_t& seq(as_sequence());
+ std::size_t new_size = seq.size();
+ if (new_size == 0) {
+#if BOOST_VERSION >= 103700
+ storage.reset();
+#else
+ storage = intrusive_ptr<storage_t>();
+#endif
+ }
+ else if (new_size == 1) {
+ *this = seq.front();
+ }
+ }
+ }
+
+ sequence_t::iterator begin() {
+ return as_sequence_lval().begin();
+ }
+
+ sequence_t::iterator end() {
+ return as_sequence_lval().end();
+ }
+
+ sequence_t::const_iterator begin() const {
+ return as_sequence().begin();
+ }
+
+ sequence_t::const_iterator end() const {
+ return as_sequence().end();
+ }
+
+ std::size_t size() const {
+ if (is_null())
+ return 0;
+ else if (is_sequence())
+ return as_sequence().size();
+ else
+ return 1;
+ }
+
+ bool empty() const {
+ return size() == 0;
+ }
+
+ /**
+ * 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 STRING:
+ return _("a string");
+ case MASK:
+ return _("a regexp");
+ case SEQUENCE:
+ return _("a sequence");
+ case SCOPE:
+ return _("a scope");
+ default:
+ assert(false);
+ break;
+ }
+ assert(false);
+ return _("<invalid>");
+ }
+
+ /**
+ * Printing methods.
+ */
+ void print(std::ostream& out,
+ const int first_width = -1,
+ const int latter_width = -1,
+ const bool right_justify = false,
+ const bool colorize = false) const;
+ void dump(std::ostream& out, const bool relaxed = true) const;
+
+ /**
+ * Debugging methods.
+ */
+ bool valid() const;
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & true_value;
+ ar & false_value;
+ ar & storage;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+#define NULL_VALUE (value_t())
+
+inline value_t string_value(const string& str = "") {
+ return value_t(str, true);
+}
+
+#define VALUE_OR_ZERO(val) ((val).is_null() ? value_t(0L) : (val))
+#define SIMPLIFIED_VALUE_OR_ZERO(val) \
+ ((val).is_null() ? value_t(0L) : (val).simplified())
+
+inline value_t mask_value(const string& str) {
+ return value_t(mask_t(str));
+}
+
+inline std::ostream& operator<<(std::ostream& out, const value_t& val) {
+ val.print(out);
+ return out;
+}
+
+inline string value_context(const value_t& val) {
+ std::ostringstream buf;
+ val.print(buf, 20, 20, true);
+ return buf.str();
+}
+
+template <typename T>
+inline value_t& add_or_set_value(value_t& lhs, const T& rhs) {
+ if (lhs.is_null())
+ lhs = rhs;
+ else
+ lhs += rhs;
+ return lhs;
+}
+
+struct sort_value_t
+{
+ bool inverted;
+ value_t value;
+
+ sort_value_t() : inverted(false) {}
+};
+
+bool sort_value_is_less_than(const std::list<sort_value_t>& left_values,
+ const std::list<sort_value_t>& right_values);
+
+void to_xml(std::ostream& out, const value_t& value);
+
+} // namespace ledger
+
+#endif // _VALUE_H
diff --git a/src/xact.cc b/src/xact.cc
new file mode 100644
index 00000000..623c5772
--- /dev/null
+++ b/src/xact.cc
@@ -0,0 +1,781 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "xact.h"
+#include "post.h"
+#include "account.h"
+#include "journal.h"
+#include "pool.h"
+
+namespace ledger {
+
+xact_base_t::xact_base_t(const xact_base_t& xact_base)
+ : item_t(xact_base), journal(xact_base.journal)
+{
+ TRACE_CTOR(xact_base_t, "copy");
+}
+
+xact_base_t::~xact_base_t()
+{
+ TRACE_DTOR(xact_base_t);
+
+ if (! has_flags(ITEM_TEMP)) {
+ foreach (post_t * post, posts) {
+ // If the posting is a temporary, it will be destructed when the
+ // temporary is.
+ assert(! post->has_flags(ITEM_TEMP));
+ checked_delete(post);
+ }
+ }
+}
+
+void xact_base_t::add_post(post_t * post)
+{
+ // You can add temporary postings to transactions, but not real postings to
+ // temporary transactions.
+ if (! post->has_flags(ITEM_TEMP))
+ assert(! has_flags(ITEM_TEMP));
+
+ posts.push_back(post);
+}
+
+bool xact_base_t::remove_post(post_t * post)
+{
+ posts.remove(post);
+ post->xact = NULL;
+ return true;
+}
+
+bool xact_base_t::has_xdata()
+{
+ foreach (post_t * post, posts)
+ if (post->has_xdata())
+ return true;
+
+ return false;
+}
+
+void xact_base_t::clear_xdata()
+{
+ foreach (post_t * post, posts)
+ if (! post->has_flags(ITEM_TEMP))
+ post->clear_xdata();
+}
+
+value_t xact_base_t::magnitude() const
+{
+ value_t halfbal = 0L;
+ foreach (const post_t * post, posts) {
+ if (post->amount.sign() > 0) {
+ if (post->cost)
+ halfbal += *post->cost;
+ else
+ halfbal += post->amount;
+ }
+ }
+ return halfbal;
+}
+
+bool xact_base_t::finalize()
+{
+ // Scan through and compute the total balance for the xact. This is used
+ // for auto-calculating the value of xacts with no cost, and the per-unit
+ // price of unpriced commodities.
+
+ value_t balance;
+ post_t * null_post = NULL;
+
+ foreach (post_t * post, posts) {
+ if (! post->must_balance())
+ continue;
+
+ amount_t& p(post->cost ? *post->cost : post->amount);
+ if (! p.is_null()) {
+ DEBUG("xact.finalize", "post must balance = " << p.reduced());
+ if (! post->cost && post->amount.has_annotation() &&
+ post->amount.annotation().price) {
+ // If the amount has no cost, but is annotated with a per-unit
+ // price, use the price times the amount as the cost
+ post->cost = (*post->amount.annotation().price *
+ post->amount).unrounded();
+ DEBUG("xact.finalize",
+ "annotation price = " << *post->amount.annotation().price);
+ DEBUG("xact.finalize", "amount = " << post->amount);
+ DEBUG("xact.finalize", "priced cost = " << *post->cost);
+ post->add_flags(POST_COST_CALCULATED);
+ add_or_set_value(balance, post->cost->rounded().reduced());
+ } else {
+ // If the amount was a cost, it very likely has the "keep_precision"
+ // flag set, meaning commodity display precision is ignored when
+ // displaying the amount. We never want this set for the balance,
+ // so we must clear the flag in a temporary to avoid it propagating
+ // into the balance.
+ add_or_set_value(balance, p.keep_precision() ?
+ p.rounded().reduced() : p.reduced());
+ }
+ }
+ else if (null_post) {
+ throw_(std::logic_error,
+ _("Only one posting with null amount allowed per transaction"));
+ }
+ else {
+ null_post = post;
+ }
+ }
+ VERIFY(balance.valid());
+
+#if defined(DEBUG_ON)
+ DEBUG("xact.finalize", "initial balance = " << balance);
+ DEBUG("xact.finalize", "balance is " << balance.label());
+ if (balance.is_balance())
+ DEBUG("xact.finalize", "balance commodity count = "
+ << balance.as_balance().amounts.size());
+#endif
+
+ // If there is only one post, balance against the default account if one has
+ // been set.
+
+ if (journal && journal->bucket && posts.size() == 1 && ! balance.is_null()) {
+ null_post = new post_t(journal->bucket, ITEM_GENERATED);
+ null_post->_state = (*posts.begin())->_state;
+ add_post(null_post);
+ }
+
+ if (null_post != NULL) {
+ // If one post has no value at all, its value will become the inverse of
+ // the rest. If multiple commodities are involved, multiple posts are
+ // generated to balance them all.
+
+ DEBUG("xact.finalize", "there was a null posting");
+
+ if (balance.is_balance()) {
+ bool first = true;
+ const balance_t& bal(balance.as_balance());
+ foreach (const balance_t::amounts_map::value_type& pair, bal.amounts) {
+ if (first) {
+ null_post->amount = pair.second.negated();
+ null_post->add_flags(POST_CALCULATED);
+ first = false;
+ } else {
+ post_t * p = new post_t(null_post->account, pair.second.negated(),
+ ITEM_GENERATED | POST_CALCULATED);
+ p->set_state(null_post->state());
+ add_post(p);
+ }
+ }
+ }
+ else if (balance.is_amount()) {
+ null_post->amount = balance.as_amount().negated();
+ null_post->add_flags(POST_CALCULATED);
+ }
+ else if (balance.is_long()) {
+ null_post->amount = amount_t(- balance.as_long());
+ null_post->add_flags(POST_CALCULATED);
+ }
+ else if (! balance.is_null() && ! balance.is_realzero()) {
+ throw_(balance_error, _("Transaction does not balance"));
+ }
+ balance = NULL_VALUE;
+ }
+ else if (balance.is_balance() &&
+ balance.as_balance().amounts.size() == 2) {
+ // When an xact involves two different commodities (regardless of how
+ // many posts there are) determine the conversion ratio by dividing the
+ // total value of one commodity by the total value of the other. This
+ // establishes the per-unit cost for this post for both commodities.
+
+ DEBUG("xact.finalize", "there were exactly two commodities");
+
+ bool saw_cost = false;
+ post_t * top_post = NULL;
+
+ foreach (post_t * post, posts) {
+ if (! post->amount.is_null()) {
+ if (post->amount.has_annotation())
+ top_post = post;
+ else if (! top_post)
+ top_post = post;
+ }
+
+ if (post->cost && ! post->has_flags(POST_COST_CALCULATED)) {
+ saw_cost = true;
+ break;
+ }
+ }
+
+ if (! saw_cost && top_post) {
+ const balance_t& bal(balance.as_balance());
+
+ DEBUG("xact.finalize", "there were no costs, and a valid top_post");
+
+ balance_t::amounts_map::const_iterator a = bal.amounts.begin();
+
+ const amount_t * x = &(*a++).second;
+ const amount_t * y = &(*a++).second;
+
+ if (x->commodity() != top_post->amount.commodity()) {
+ const amount_t * t = x;
+ x = y;
+ y = t;
+ }
+
+ if (*x && *y) {
+ DEBUG("xact.finalize", "primary amount = " << *x);
+ DEBUG("xact.finalize", "secondary amount = " << *y);
+
+ commodity_t& comm(x->commodity());
+ amount_t per_unit_cost;
+ amount_t total_cost;
+
+ foreach (post_t * post, posts) {
+ if (post != top_post && post->must_balance() &&
+ ! post->amount.is_null() &&
+ post->amount.has_annotation() &&
+ post->amount.annotation().price) {
+ amount_t temp = *post->amount.annotation().price * post->amount;
+ if (total_cost.is_null()) {
+ total_cost = temp;
+ y = &total_cost;
+ } else {
+ total_cost += temp;
+ }
+ DEBUG("xact.finalize", "total_cost = " << total_cost);
+ }
+ }
+ per_unit_cost = (*y / *x).abs().unrounded();
+
+ DEBUG("xact.finalize", "per_unit_cost = " << per_unit_cost);
+
+ foreach (post_t * post, posts) {
+ const amount_t& amt(post->amount);
+
+ if (post->must_balance() && amt.commodity() == comm) {
+ balance -= amt;
+ post->cost = per_unit_cost * amt;
+ post->add_flags(POST_COST_CALCULATED);
+ balance += *post->cost;
+
+ DEBUG("xact.finalize", "set post->cost to = " << *post->cost);
+ }
+ }
+ }
+ }
+ }
+
+ // Now that the post list has its final form, calculate the balance once
+ // more in terms of total cost, accounting for any possible gain/loss
+ // amounts.
+
+ DEBUG("xact.finalize", "resolved balance = " << balance);
+
+ posts_list copy(posts);
+
+ foreach (post_t * post, copy) {
+ if (! post->cost)
+ continue;
+
+ if (post->amount.commodity() == post->cost->commodity())
+ throw_(balance_error,
+ _("A posting's cost must be of a different commodity than its amount"));
+
+ cost_breakdown_t breakdown =
+ commodity_pool_t::current_pool->exchange
+ (post->amount, *post->cost, false,
+ datetime_t(date(), time_duration(0, 0, 0, 0)));
+
+ if (post->amount.has_annotation() &&
+ breakdown.basis_cost.commodity() ==
+ breakdown.final_cost.commodity()) {
+ if (amount_t gain_loss = (breakdown.basis_cost -
+ breakdown.final_cost).rounded()) {
+ DEBUG("xact.finalize", "gain_loss = " << gain_loss);
+
+ add_or_set_value(balance, gain_loss.reduced());
+
+ account_t * account;
+ if (gain_loss.sign() > 0)
+ account = journal->find_account(_("Equity:Capital Gains"));
+ else
+ account = journal->find_account(_("Equity:Capital Losses"));
+
+ post_t * p = new post_t(account, gain_loss, ITEM_GENERATED);
+ p->set_state(post->state());
+ add_post(p);
+ DEBUG("xact.finalize", "added gain_loss, balance = " << balance);
+ }
+ } else {
+ post->amount = breakdown.amount;
+ DEBUG("xact.finalize", "added breakdown, balance = " << balance);
+ }
+ }
+
+ DEBUG("xact.finalize", "final balance = " << balance);
+
+ if (! balance.is_null() && ! balance.is_zero()) {
+ add_error_context(item_context(*this, _("While balancing transaction")));
+ add_error_context(_("Unbalanced remainder is:"));
+ add_error_context(value_context(balance));
+ add_error_context(_("Amount to balance against:"));
+ add_error_context(value_context(magnitude()));
+ throw_(balance_error, _("Transaction does not balance"));
+ }
+
+ // Add a pointer to each posting to their related accounts
+
+ if (dynamic_cast<xact_t *>(this)) {
+ bool all_null = true;
+ bool some_null = false;
+
+ foreach (post_t * post, posts) {
+ if (! post->amount.is_null()) {
+ all_null = false;
+ post->amount.in_place_reduce();
+ } else {
+ some_null = true;
+ }
+
+ post->account->add_post(post);
+
+ post->xdata().add_flags(POST_EXT_VISITED);
+ post->account->xdata().add_flags(ACCOUNT_EXT_VISITED);
+ }
+
+ if (all_null)
+ return false; // ignore this xact completely
+ else if (some_null)
+ throw_(balance_error,
+ _("There cannot be null amounts after balancing a transaction"));
+ }
+
+ VERIFY(valid());
+
+ return true;
+}
+
+bool xact_base_t::verify()
+{
+ // Scan through and compute the total balance for the xact.
+
+ value_t balance;
+
+ foreach (post_t * post, posts) {
+ if (! post->must_balance())
+ continue;
+
+ amount_t& p(post->cost ? *post->cost : post->amount);
+ assert(! p.is_null());
+
+ // If the amount was a cost, it very likely has the "keep_precision" flag
+ // set, meaning commodity display precision is ignored when displaying the
+ // amount. We never want this set for the balance, so we must clear the
+ // flag in a temporary to avoid it propagating into the balance.
+ add_or_set_value(balance, p.keep_precision() ?
+ p.rounded().reduced() : p.reduced());
+ }
+ VERIFY(balance.valid());
+
+ // Now that the post list has its final form, calculate the balance once
+ // more in terms of total cost, accounting for any possible gain/loss
+ // amounts.
+
+ foreach (post_t * post, posts) {
+ if (! post->cost)
+ continue;
+
+ if (post->amount.commodity() == post->cost->commodity())
+ throw_(amount_error,
+ _("A posting's cost must be of a different commodity than its amount"));
+ }
+
+ if (! balance.is_null() && ! balance.is_zero()) {
+ add_error_context(item_context(*this, _("While balancing transaction")));
+ add_error_context(_("Unbalanced remainder is:"));
+ add_error_context(value_context(balance));
+ add_error_context(_("Amount to balance against:"));
+ add_error_context(value_context(magnitude()));
+ throw_(balance_error, _("Transaction does not balance"));
+ }
+
+ VERIFY(valid());
+
+ return true;
+}
+
+xact_t::xact_t(const xact_t& e)
+ : xact_base_t(e), code(e.code), payee(e.payee)
+{
+ TRACE_CTOR(xact_t, "copy");
+}
+
+void xact_t::add_post(post_t * post)
+{
+ post->xact = this;
+ xact_base_t::add_post(post);
+}
+
+string xact_t::idstring() const
+{
+ std::ostringstream buf;
+ buf << format_date(*_date, FMT_WRITTEN);
+ buf << payee;
+ magnitude().number().print(buf);
+ return buf.str();
+}
+
+string xact_t::id() const
+{
+ SHA1 sha;
+ sha.Reset();
+ sha << idstring().c_str();
+ uint_least32_t message_digest[5];
+ sha.Result(message_digest);
+ return to_hex(message_digest, 5);
+}
+
+namespace {
+ value_t get_magnitude(xact_t& xact) {
+ return xact.magnitude();
+ }
+ value_t get_idstring(xact_t& xact) {
+ return string_value(xact.idstring());
+ }
+ value_t get_id(xact_t& xact) {
+ return string_value(xact.id());
+ }
+
+ value_t get_code(xact_t& xact) {
+ if (xact.code)
+ return string_value(*xact.code);
+ else
+ return string_value(empty_string);
+ }
+
+ value_t get_payee(xact_t& xact) {
+ return string_value(xact.payee);
+ }
+
+ 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 symbol_t::kind_t kind,
+ const string& name)
+{
+ if (kind != symbol_t::FUNCTION)
+ return item_t::lookup(kind, name);
+
+ switch (name[0]) {
+ case 'c':
+ if (name == "code")
+ return WRAP_FUNCTOR(get_wrapper<&get_code>);
+ break;
+
+ case 'i':
+ if (name == "id")
+ return WRAP_FUNCTOR(get_wrapper<&get_id>);
+ else if (name == "idstring")
+ return WRAP_FUNCTOR(get_wrapper<&get_idstring>);
+ break;
+
+ case 'm':
+ if (name == "magnitude")
+ return WRAP_FUNCTOR(get_wrapper<&get_magnitude>);
+ break;
+
+ case 'p':
+ if (name[1] == '\0' || name == "payee")
+ return WRAP_FUNCTOR(get_wrapper<&get_payee>);
+ break;
+ }
+
+ return item_t::lookup(kind, name);
+}
+
+bool xact_t::valid() const
+{
+ if (! _date) {
+ DEBUG("ledger.validate", "xact_t: ! _date");
+ return false;
+ }
+
+ foreach (post_t * post, posts)
+ if (post->xact != this || ! post->valid()) {
+ DEBUG("ledger.validate", "xact_t: post not valid");
+ return false;
+ }
+
+ return true;
+}
+
+namespace {
+
+ bool post_pred(expr_t::ptr_op_t op, post_t& post)
+ {
+ switch (op->kind) {
+ case expr_t::op_t::VALUE:
+ return op->as_value().to_boolean();
+ break;
+
+ case expr_t::op_t::O_MATCH:
+ if (op->left()->kind == expr_t::op_t::IDENT &&
+ op->left()->as_ident() == "account" &&
+ op->right()->kind == expr_t::op_t::VALUE &&
+ op->right()->as_value().is_mask())
+ return op->right()->as_value().as_mask()
+ .match(post.reported_account()->fullname());
+ else
+ break;
+
+ case expr_t::op_t::O_NOT:
+ return ! post_pred(op->left(), post);
+
+ case expr_t::op_t::O_AND:
+ return post_pred(op->left(), post) && post_pred(op->right(), post);
+
+ case expr_t::op_t::O_OR:
+ return post_pred(op->left(), post) || post_pred(op->right(), post);
+
+ case expr_t::op_t::O_QUERY:
+ if (post_pred(op->left(), post))
+ return post_pred(op->right()->left(), post);
+ else
+ return post_pred(op->right()->right(), post);
+
+ default:
+ break;
+ }
+
+ throw_(calc_error, _("Unhandled operator"));
+ return false;
+ }
+
+} // unnamed namespace
+
+void auto_xact_t::extend_xact(xact_base_t& xact)
+{
+ posts_list initial_posts(xact.posts.begin(), xact.posts.end());
+
+ try {
+
+ bool needs_further_verification = false;
+
+ foreach (post_t * initial_post, initial_posts) {
+ if (initial_post->has_flags(ITEM_GENERATED))
+ continue;
+
+ bool matches_predicate = false;
+ if (try_quick_match) {
+ try {
+ bool found_memoized_result = false;
+ if (! memoized_results.empty()) {
+ std::map<string, bool>::iterator i =
+ memoized_results.find(initial_post->account->fullname());
+ if (i != memoized_results.end()) {
+ found_memoized_result = true;
+ matches_predicate = (*i).second;
+ }
+ }
+
+ // Since the majority of people who use automated transactions simply
+ // match against account names, try using a *much* faster version of
+ // the predicate evaluator.
+ if (! found_memoized_result) {
+ matches_predicate = post_pred(predicate.get_op(), *initial_post);
+ memoized_results.insert
+ (std::pair<string, bool>(initial_post->account->fullname(),
+ matches_predicate));
+ }
+ }
+ catch (...) {
+ DEBUG("xact.extend.fail",
+ "The quick matcher failed, going back to regular eval");
+ try_quick_match = false;
+ matches_predicate = predicate(*initial_post);
+ }
+ } else {
+ matches_predicate = predicate(*initial_post);
+ }
+ if (matches_predicate) {
+ foreach (post_t * post, posts) {
+ amount_t post_amount;
+ if (post->amount.is_null()) {
+ if (! post->amount_expr)
+ throw_(amount_error,
+ _("Automated transaction's posting has no amount"));
+
+ bind_scope_t bound_scope(*scope_t::default_scope, *initial_post);
+ value_t result(post->amount_expr->calc(bound_scope));
+ if (result.is_long()) {
+ post_amount = result.to_amount();
+ } else {
+ if (! result.is_amount())
+ throw_(amount_error,
+ _("Amount expressions must result in a simple amount"));
+ post_amount = result.as_amount();
+ }
+ } else {
+ post_amount = post->amount;
+ }
+
+ amount_t amt;
+ if (! post_amount.commodity())
+ amt = initial_post->amount * post_amount;
+ else
+ amt = post_amount;
+
+ IF_DEBUG("xact.extend") {
+ DEBUG("xact.extend",
+ "Initial post on line " << initial_post->pos->beg_line << ": "
+ << "amount " << initial_post->amount << " (precision "
+ << initial_post->amount.precision() << ")");
+
+#if defined(DEBUG_ON)
+ if (initial_post->amount.keep_precision())
+ DEBUG("xact.extend", " precision is kept");
+#endif
+
+ DEBUG("xact.extend",
+ "Posting on line " << post->pos->beg_line << ": "
+ << "amount " << post_amount << ", amt " << amt
+ << " (precision " << post_amount.precision()
+ << " != " << amt.precision() << ")");
+
+#if defined(DEBUG_ON)
+ if (post_amount.keep_precision())
+ DEBUG("xact.extend", " precision is kept");
+ if (amt.keep_precision())
+ DEBUG("xact.extend", " amt precision is kept");
+#endif
+ }
+
+ account_t * account = post->account;
+ string fullname = account->fullname();
+ assert(! fullname.empty());
+ if (fullname == "$account" || fullname == "@account")
+ account = initial_post->account;
+
+ // Copy over details so that the resulting post is a mirror of
+ // the automated xact's one.
+ post_t * new_post = new post_t(account, amt);
+ new_post->copy_details(*post);
+ new_post->add_flags(ITEM_GENERATED);
+
+ xact.add_post(new_post);
+ new_post->account->add_post(new_post);
+
+ if (new_post->must_balance())
+ needs_further_verification = true;
+ }
+ }
+ }
+
+ if (needs_further_verification)
+ xact.verify();
+
+ }
+ catch (const std::exception& err) {
+ add_error_context(item_context(*this, _("While applying automated transaction")));
+ add_error_context(item_context(xact, _("While extending transaction")));
+ throw;
+ }
+}
+
+void extend_xact_base(journal_t * journal,
+ xact_base_t& base)
+{
+ foreach (auto_xact_t * xact, journal->auto_xacts)
+ xact->extend_xact(base);
+}
+
+void to_xml(std::ostream& out, const xact_t& xact)
+{
+ push_xml x(out, "transaction", true, true);
+
+ if (xact.state() == item_t::CLEARED)
+ out << " state=\"cleared\"";
+ else if (xact.state() == item_t::PENDING)
+ out << " state=\"pending\"";
+
+ if (xact.has_flags(ITEM_GENERATED))
+ out << " generated=\"true\"";
+
+ x.close_attrs();
+
+ if (xact._date) {
+ push_xml y(out, "date");
+ to_xml(out, *xact._date, false);
+ }
+ if (xact._date_eff) {
+ push_xml y(out, "effective-date");
+ to_xml(out, *xact._date_eff, false);
+ }
+
+ if (xact.code) {
+ push_xml y(out, "code");
+ out << y.guard(*xact.code);
+ }
+
+ {
+ push_xml y(out, "payee");
+ out << y.guard(xact.payee);
+ }
+
+ if (xact.note) {
+ push_xml y(out, "note");
+ out << y.guard(*xact.note);
+ }
+
+ if (xact.metadata) {
+ push_xml y(out, "metadata");
+ foreach (const item_t::string_map::value_type& pair, *xact.metadata) {
+ if (pair.second) {
+ push_xml z(out, "variable");
+ {
+ push_xml w(out, "key");
+ out << y.guard(pair.first);
+ }
+ {
+ push_xml w(out, "value");
+ out << y.guard(*pair.second);
+ }
+ } else {
+ push_xml z(out, "tag");
+ out << y.guard(pair.first);
+ }
+ }
+ }
+}
+
+} // namespace ledger
diff --git a/src/xact.h b/src/xact.h
new file mode 100644
index 00000000..c819b2a0
--- /dev/null
+++ b/src/xact.h
@@ -0,0 +1,232 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup data
+ */
+
+/**
+ * @file xact.h
+ * @author John Wiegley
+ *
+ * @ingroup data
+ */
+#ifndef _XACT_H
+#define _XACT_H
+
+#include "item.h"
+#include "predicate.h"
+
+namespace ledger {
+
+class post_t;
+class journal_t;
+
+typedef std::list<post_t *> posts_list;
+
+class xact_base_t : public item_t
+{
+public:
+ journal_t * journal;
+ posts_list posts;
+
+ xact_base_t() : item_t(), journal(NULL) {
+ TRACE_CTOR(xact_base_t, "");
+ }
+ xact_base_t(const xact_base_t& e);
+
+ virtual ~xact_base_t();
+
+ virtual void add_post(post_t * post);
+ virtual bool remove_post(post_t * post);
+
+ posts_list::iterator posts_begin() {
+ return posts.begin();
+ }
+ posts_list::iterator posts_end() {
+ return posts.end();
+ }
+
+ value_t magnitude() const;
+
+ bool finalize();
+ bool verify();
+
+ bool has_xdata();
+ void clear_xdata();
+
+ virtual bool valid() const {
+ return true;
+ }
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & boost::serialization::base_object<item_t>(*this);
+ ar & journal;
+ ar & posts;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+class xact_t : public xact_base_t
+{
+public:
+ optional<string> code;
+ string payee;
+
+ xact_t() {
+ TRACE_CTOR(xact_t, "");
+ }
+ xact_t(const xact_t& e);
+
+ virtual ~xact_t() {
+ TRACE_DTOR(xact_t);
+ }
+
+ virtual void add_post(post_t * post);
+
+ string idstring() const;
+ string id() const;
+
+ virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind,
+ const string& name);
+
+ virtual bool valid() const;
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & boost::serialization::base_object<xact_base_t>(*this);
+ ar & code;
+ ar & payee;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+class auto_xact_t : public xact_base_t
+{
+public:
+ predicate_t predicate;
+ bool try_quick_match;
+
+ std::map<string, bool> memoized_results;
+
+ auto_xact_t() : try_quick_match(true) {
+ TRACE_CTOR(auto_xact_t, "");
+ }
+ auto_xact_t(const auto_xact_t& other)
+ : xact_base_t(), predicate(other.predicate),
+ try_quick_match(other.try_quick_match) {
+ TRACE_CTOR(auto_xact_t, "copy");
+ }
+ auto_xact_t(const predicate_t& _predicate)
+ : predicate(_predicate), try_quick_match(true)
+ {
+ TRACE_CTOR(auto_xact_t, "const predicate_t&");
+ }
+
+ virtual ~auto_xact_t() {
+ TRACE_DTOR(auto_xact_t);
+ }
+
+ virtual void extend_xact(xact_base_t& xact);
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & boost::serialization::base_object<xact_base_t>(*this);
+ ar & predicate;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+class period_xact_t : public xact_base_t
+{
+ public:
+ date_interval_t period;
+ string period_string;
+
+ period_xact_t() {
+ TRACE_CTOR(period_xact_t, "");
+ }
+ period_xact_t(const period_xact_t& e)
+ : xact_base_t(e), period(e.period), period_string(e.period_string) {
+ TRACE_CTOR(period_xact_t, "copy");
+ }
+ period_xact_t(const string& _period)
+ : period(_period), period_string(_period) {
+ TRACE_CTOR(period_xact_t, "const string&");
+ }
+
+ virtual ~period_xact_t() {
+ TRACE_DTOR(period_xact_t);
+ }
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & boost::serialization::base_object<xact_base_t>(*this);
+ ar & period;
+ ar & period_string;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+typedef std::list<xact_t *> xacts_list;
+typedef std::list<auto_xact_t *> auto_xacts_list;
+typedef std::list<period_xact_t *> period_xacts_list;
+
+void to_xml(std::ostream& out, const xact_t& xact);
+
+} // namespace ledger
+
+#endif // _XACT_H
diff --git a/src/xml.cc b/src/xml.cc
new file mode 100644
index 00000000..9cff980a
--- /dev/null
+++ b/src/xml.cc
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "xml.h"
+#include "xact.h"
+#include "post.h"
+#include "account.h"
+#include "session.h"
+#include "report.h"
+
+namespace ledger {
+
+namespace {
+ void xml_account(std::ostream& out, const account_t * acct) {
+ if ((acct->has_xdata() &&
+ acct->xdata().has_flags(ACCOUNT_EXT_VISITED)) ||
+ acct->children_with_flags(ACCOUNT_EXT_VISITED)) {
+ out << "<account id=\"";
+ out.width(sizeof(unsigned long) * 2);
+ out.fill('0');
+ out << std::hex << reinterpret_cast<unsigned long>(acct);
+ out << "\">\n";
+
+ out << "<name>" << acct->name << "</name>\n";
+ value_t total = acct->amount();
+ if (! total.is_null()) {
+ out << "<amount>\n";
+ to_xml(out, total);
+ out << "</amount>\n";
+ }
+ total = acct->total();
+ if (! total.is_null()) {
+ out << "<total>\n";
+ to_xml(out, total);
+ out << "</total>\n";
+ }
+ out << "</account>\n";
+ }
+
+ foreach (const accounts_map::value_type& pair, acct->accounts)
+ xml_account(out, pair.second);
+ }
+
+ void xml_transaction(std::ostream& out, const xact_t * xact) {
+ to_xml(out, *xact);
+
+ foreach (const post_t * post, xact->posts)
+ if (post->has_xdata() &&
+ post->xdata().has_flags(POST_EXT_VISITED))
+ to_xml(out, *post);
+
+ out << "</transaction>\n";
+ }
+}
+
+void format_xml::flush()
+{
+ std::ostream& out(report.output_stream);
+
+ out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
+ out << "<ledger version=\"" << VERSION << "\">\n";
+
+ out << "<commodities>\n";
+ foreach (const commodities_pair& pair, commodities) {
+ to_xml(out, *pair.second, true);
+ out << '\n';
+ }
+ out << "</commodities>\n";
+
+ out << "<accounts>\n";
+ xml_account(out, report.session.journal->master);
+ out << "</accounts>\n";
+
+ out << "<transactions>\n";
+ foreach (const xact_t * xact, transactions)
+ xml_transaction(out, xact);
+ out << "</transactions>\n";
+
+ out << "</ledger>\n";
+ out.flush();
+}
+
+void format_xml::operator()(post_t& post)
+{
+ assert(post.xdata().has_flags(POST_EXT_VISITED));
+
+ commodities.insert(commodities_pair(post.amount.commodity().symbol(),
+ &post.amount.commodity()));
+
+ if (transactions_set.find(post.xact) == transactions_set.end())
+ transactions.push_back(post.xact);
+}
+
+} // namespace ledger
diff --git a/src/xml.h b/src/xml.h
new file mode 100644
index 00000000..30a7b1b1
--- /dev/null
+++ b/src/xml.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup report
+ */
+
+/**
+ * @file xml.h
+ * @author John Wiegley
+ *
+ * @ingroup report
+ *
+ * @brief Brief
+ *
+ * Long.
+ */
+#ifndef _XML_H
+#define _XML_H
+
+#include "chain.h"
+
+namespace ledger {
+
+class xact_t;
+class account_t;
+class commodity_t;
+class post_t;
+class report_t;
+
+/**
+ * @brief Brief
+ *
+ * Long.
+ */
+class format_xml : public item_handler<post_t>
+{
+protected:
+ report_t& report;
+
+ typedef std::map<string, commodity_t *> commodities_map;
+ typedef std::pair<string, commodity_t *> commodities_pair;
+
+ commodities_map commodities;
+ std::set<xact_t *> transactions_set;
+ std::deque<xact_t *> transactions;
+
+public:
+ format_xml(report_t& _report) : report(_report) {
+ TRACE_CTOR(format_xml, "report&");
+ }
+ virtual ~format_xml() {
+ TRACE_DTOR(format_xml);
+ }
+
+ virtual void flush();
+ virtual void operator()(post_t& post);
+};
+
+} // namespace ledger
+
+#endif // _XML_H