summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/account.cc15
-rw-r--r--src/amount.cc77
-rw-r--r--src/amount.h4
-rw-r--r--src/balance.h24
-rw-r--r--src/chain.cc129
-rw-r--r--src/chain.h30
-rw-r--r--src/commodity.cc12
-rw-r--r--src/commodity.h26
-rw-r--r--src/convert.cc4
-rw-r--r--src/draft.cc6
-rw-r--r--src/emacs.cc19
-rw-r--r--src/filters.cc272
-rw-r--r--src/filters.h224
-rw-r--r--src/item.cc9
-rw-r--r--src/option.h35
-rw-r--r--src/output.cc157
-rw-r--r--src/output.h130
-rw-r--r--src/parser.cc9
-rw-r--r--src/parser.h5
-rw-r--r--src/post.cc94
-rw-r--r--src/post.h3
-rw-r--r--src/print.cc100
-rw-r--r--src/print.h10
-rw-r--r--src/py_account.cc6
-rw-r--r--src/py_commodity.cc39
-rw-r--r--src/py_journal.cc19
-rw-r--r--src/py_xact.cc4
-rw-r--r--src/query.cc53
-rw-r--r--src/query.h27
-rw-r--r--src/report.cc201
-rw-r--r--src/report.h53
-rw-r--r--src/scope.h24
-rw-r--r--src/session.cc4
-rw-r--r--src/session.h6
-rw-r--r--src/temps.cc51
-rw-r--r--src/temps.h6
-rw-r--r--src/textual.cc42
-rw-r--r--src/times.cc59
-rw-r--r--src/token.cc47
-rw-r--r--src/token.h7
-rw-r--r--src/value.cc54
-rw-r--r--src/xact.cc97
-rw-r--r--src/xml.h8
43 files changed, 1652 insertions, 549 deletions
diff --git a/src/account.cc b/src/account.cc
index e02d21d7..8d4341e7 100644
--- a/src/account.cc
+++ b/src/account.cc
@@ -42,9 +42,12 @@ account_t::~account_t()
{
TRACE_DTOR(account_t);
- foreach (accounts_map::value_type& pair, accounts)
- if (! pair.second->has_flags(ACCOUNT_TEMP))
+ foreach (accounts_map::value_type& pair, accounts) {
+ if (! pair.second->has_flags(ACCOUNT_TEMP) ||
+ has_flags(ACCOUNT_TEMP)) {
checked_delete(pair.second);
+ }
+ }
}
account_t * account_t::find_account(const string& name,
@@ -79,6 +82,14 @@ account_t * account_t::find_account(const string& name,
return NULL;
account = new account_t(this, first);
+
+ // An account created within a temporary or generated account is itself
+ // temporary or generated, so that the whole tree has the same status.
+ if (has_flags(ACCOUNT_TEMP))
+ account->add_flags(ACCOUNT_TEMP);
+ if (has_flags(ACCOUNT_GENERATED))
+ account->add_flags(ACCOUNT_GENERATED);
+
std::pair<accounts_map::iterator, bool> result
= accounts.insert(accounts_map::value_type(first, account));
assert(result.second);
diff --git a/src/amount.cc b/src/amount.cc
index 3a64577f..13f30755 100644
--- a/src/amount.cc
+++ b/src/amount.cc
@@ -165,8 +165,8 @@ namespace {
for (const char * p = buf; *p; p++) {
if (*p == '.') {
- if (commodity_t::european_by_default ||
- (comm && comm->has_flags(COMMODITY_STYLE_EUROPEAN)))
+ if (commodity_t::decimal_comma_by_default ||
+ (comm && comm->has_flags(COMMODITY_STYLE_DECIMAL_COMMA)))
out << ',';
else
out << *p;
@@ -179,8 +179,8 @@ namespace {
out << *p;
if (integer_digits > 3 && --integer_digits % 3 == 0) {
- if (commodity_t::european_by_default ||
- (comm && comm->has_flags(COMMODITY_STYLE_EUROPEAN)))
+ if (commodity_t::decimal_comma_by_default ||
+ (comm && comm->has_flags(COMMODITY_STYLE_DECIMAL_COMMA)))
out << '.';
else
out << ',';
@@ -594,6 +594,44 @@ void amount_t::in_place_round()
set_keep_precision(false);
}
+void amount_t::in_place_truncate()
+{
+#if 1
+ if (! quantity)
+ throw_(amount_error, _("Cannot truncate an uninitialized amount"));
+
+ _dup();
+
+ DEBUG("amount.truncate",
+ "Truncating " << *this << " to precision " << display_precision());
+
+ std::ostringstream out;
+ stream_out_mpq(out, MP(quantity), display_precision());
+
+ scoped_array<char> buf(new char [out.str().length() + 1]);
+ std::strcpy(buf.get(), out.str().c_str());
+
+ char * q = buf.get();
+ for (char * p = q; *p != '\0'; p++, q++) {
+ if (*p == '.') p++;
+ if (p != q) *q = *p;
+ }
+ *q = '\0';
+
+ mpq_set_str(MP(quantity), buf.get(), 10);
+
+ mpz_ui_pow_ui(temp, 10, display_precision());
+ mpq_set_z(tempq, temp);
+ mpq_div(MP(quantity), MP(quantity), tempq);
+
+ DEBUG("amount.truncate", "Truncated = " << *this);
+#else
+ // This naive implementation is straightforward, but extremely inefficient
+ // as it requires parsing the commodity too, which might be fully annotated.
+ *this = amount_t(to_string());
+#endif
+}
+
void amount_t::in_place_floor()
{
if (! quantity)
@@ -993,8 +1031,9 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags)
bool no_more_commas = false;
bool no_more_periods = false;
- bool european_style = (commodity_t::european_by_default ||
- commodity().has_flags(COMMODITY_STYLE_EUROPEAN));
+ bool decimal_comma_style
+ = (commodity_t::decimal_comma_by_default ||
+ commodity().has_flags(COMMODITY_STYLE_DECIMAL_COMMA));
new_quantity->prec = 0;
@@ -1005,16 +1044,16 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags)
if (no_more_periods)
throw_(amount_error, _("Too many periods in amount"));
- if (european_style) {
+ if (decimal_comma_style) {
if (decimal_offset % 3 != 0)
- throw_(amount_error, _("Incorrect use of european-style period"));
+ throw_(amount_error, _("Incorrect use of thousand-mark period"));
comm_flags |= COMMODITY_STYLE_THOUSANDS;
no_more_commas = true;
} else {
if (last_comma != string::npos) {
- european_style = true;
+ decimal_comma_style = true;
if (decimal_offset % 3 != 0)
- throw_(amount_error, _("Incorrect use of european-style period"));
+ throw_(amount_error, _("Incorrect use of thousand-mark period"));
} else {
no_more_periods = true;
new_quantity->prec = decimal_offset;
@@ -1029,9 +1068,9 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags)
if (no_more_commas)
throw_(amount_error, _("Too many commas in amount"));
- if (european_style) {
+ if (decimal_comma_style) {
if (last_period != string::npos) {
- throw_(amount_error, _("Incorrect use of european-style comma"));
+ throw_(amount_error, _("Incorrect use of decimal comma"));
} else {
no_more_commas = true;
new_quantity->prec = decimal_offset;
@@ -1041,12 +1080,12 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags)
if (decimal_offset % 3 != 0) {
if (last_comma != string::npos ||
last_period != string::npos) {
- throw_(amount_error, _("Incorrect use of American-style comma"));
+ throw_(amount_error, _("Incorrect use of thousand-mark comma"));
} else {
- european_style = true;
- no_more_commas = true;
- new_quantity->prec = decimal_offset;
- decimal_offset = 0;
+ decimal_comma_style = true;
+ no_more_commas = true;
+ new_quantity->prec = decimal_offset;
+ decimal_offset = 0;
}
} else {
comm_flags |= COMMODITY_STYLE_THOUSANDS;
@@ -1062,8 +1101,8 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags)
}
}
- if (european_style)
- comm_flags |= COMMODITY_STYLE_EUROPEAN;
+ if (decimal_comma_style)
+ comm_flags |= COMMODITY_STYLE_DECIMAL_COMMA;
if (flags.has_flags(PARSE_NO_MIGRATE)) {
// Can't call set_keep_precision here, because it assumes that `quantity'
diff --git a/src/amount.h b/src/amount.h
index 5c1bca46..ae0e5a69 100644
--- a/src/amount.h
+++ b/src/amount.h
@@ -346,9 +346,7 @@ public:
temp.in_place_truncate();
return temp;
}
- void in_place_truncate() {
- *this = amount_t(to_string());
- }
+ void in_place_truncate();
/** Yields an amount which has lost all of its extra precision, beyond what
the display precision of the commodity would have printed. */
diff --git a/src/balance.h b/src/balance.h
index f8455d49..5c00c55a 100644
--- a/src/balance.h
+++ b/src/balance.h
@@ -321,10 +321,8 @@ public:
return temp;
}
void in_place_round() {
- balance_t temp;
- foreach (const amounts_map::value_type& pair, amounts)
- temp += pair.second.rounded();
- *this = temp;
+ foreach (amounts_map::value_type& pair, amounts)
+ pair.second.in_place_round();
}
balance_t truncated() const {
@@ -333,10 +331,8 @@ public:
return temp;
}
void in_place_truncate() {
- balance_t temp;
- foreach (const amounts_map::value_type& pair, amounts)
- temp += pair.second.truncated();
- *this = temp;
+ foreach (amounts_map::value_type& pair, amounts)
+ pair.second.in_place_truncate();
}
balance_t floored() const {
@@ -345,10 +341,8 @@ public:
return temp;
}
void in_place_floor() {
- balance_t temp;
- foreach (const amounts_map::value_type& pair, amounts)
- temp += pair.second.floored();
- *this = temp;
+ foreach (amounts_map::value_type& pair, amounts)
+ pair.second.in_place_floor();
}
balance_t unrounded() const {
@@ -357,10 +351,8 @@ public:
return temp;
}
void in_place_unround() {
- balance_t temp;
- foreach (const amounts_map::value_type& pair, amounts)
- temp += pair.second.unrounded();
- *this = temp;
+ foreach (amounts_map::value_type& pair, amounts)
+ pair.second.in_place_unround();
}
balance_t reduced() const {
diff --git a/src/chain.cc b/src/chain.cc
index 86f639ad..b8c2eb0a 100644
--- a/src/chain.cc
+++ b/src/chain.cc
@@ -39,6 +39,73 @@
namespace ledger {
+post_handler_ptr chain_pre_post_handlers(report_t& report,
+ post_handler_ptr base_handler)
+{
+ post_handler_ptr handler(base_handler);
+
+ // anonymize_posts removes all meaningful information from xact payee's and
+ // account names, for the sake of creating useful bug reports.
+ if (report.HANDLED(anon))
+ handler.reset(new anonymize_posts(handler));
+
+ // This filter_posts will only pass through posts matching the `predicate'.
+ if (report.HANDLED(limit_)) {
+ DEBUG("report.predicate",
+ "Report predicate expression = " << report.HANDLER(limit_).str());
+ handler.reset(new filter_posts
+ (handler, predicate_t(report.HANDLER(limit_).str(),
+ report.what_to_keep()),
+ report));
+ }
+
+ // budget_posts takes a set of posts from a data file and uses them to
+ // generate "budget posts" which balance against the reported posts.
+ //
+ // forecast_posts is a lot like budget_posts, except that it adds xacts
+ // only for the future, and does not balance them against anything but the
+ // future balance.
+
+ if (report.budget_flags != BUDGET_NO_BUDGET) {
+ budget_posts * budget_handler = new budget_posts(handler,
+ report.budget_flags);
+ budget_handler->add_period_xacts(report.session.journal->period_xacts);
+ handler.reset(budget_handler);
+
+ // Apply this before the budget handler, so that only matching posts are
+ // calculated toward the budget. The use of filter_posts above will
+ // further clean the results so that no automated posts that don't match
+ // the filter get reported.
+ if (report.HANDLED(limit_))
+ handler.reset(new filter_posts
+ (handler, predicate_t(report.HANDLER(limit_).str(),
+ report.what_to_keep()),
+ report));
+ }
+ else if (report.HANDLED(forecast_while_)) {
+ forecast_posts * forecast_handler
+ = new forecast_posts(handler,
+ predicate_t(report.HANDLER(forecast_while_).str(),
+ report.what_to_keep()),
+ report,
+ report.HANDLED(forecast_years_) ?
+ static_cast<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;
+}
+
post_handler_ptr chain_post_handlers(report_t& report,
post_handler_ptr base_handler,
bool for_accounts_report)
@@ -86,7 +153,8 @@ post_handler_ptr chain_post_handlers(report_t& report,
report.HANDLED(unrealized)))
handler.reset(new changed_value_posts(handler, report,
for_accounts_report,
- report.HANDLED(unrealized)));
+ report.HANDLED(unrealized),
+ ! report.HANDLED(no_rounding)));
// calc_posts computes the running total. When this appears will determine,
// for example, whether filtered posts are included or excluded from the
@@ -188,65 +256,6 @@ post_handler_ptr chain_post_handlers(report_t& report,
if (report.HANDLED(related))
handler.reset(new related_posts(handler, report.HANDLED(related_all)));
- // anonymize_posts removes all meaningful information from xact payee's and
- // account names, for the sake of creating useful bug reports.
- if (report.HANDLED(anon))
- handler.reset(new anonymize_posts(handler));
-
- // This filter_posts will only pass through posts matching the `predicate'.
- if (report.HANDLED(limit_)) {
- DEBUG("report.predicate",
- "Report predicate expression = " << report.HANDLER(limit_).str());
- handler.reset(new filter_posts
- (handler, predicate_t(report.HANDLER(limit_).str(),
- report.what_to_keep()),
- report));
- }
-
- // budget_posts takes a set of posts from a data file and uses them to
- // generate "budget posts" which balance against the reported posts.
- //
- // forecast_posts is a lot like budget_posts, except that it adds xacts
- // only for the future, and does not balance them against anything but the
- // future balance.
-
- if (report.budget_flags != BUDGET_NO_BUDGET) {
- budget_posts * budget_handler = new budget_posts(handler,
- report.budget_flags);
- budget_handler->add_period_xacts(report.session.journal->period_xacts);
- handler.reset(budget_handler);
-
- // Apply this before the budget handler, so that only matching posts are
- // calculated toward the budget. The use of filter_posts above will
- // further clean the results so that no automated posts that don't match
- // the filter get reported.
- if (report.HANDLED(limit_))
- handler.reset(new filter_posts
- (handler, predicate_t(report.HANDLER(limit_).str(),
- report.what_to_keep()),
- report));
- }
- else if (report.HANDLED(forecast_while_)) {
- forecast_posts * forecast_handler
- = new forecast_posts(handler,
- predicate_t(report.HANDLER(forecast_while_).str(),
- report.what_to_keep()),
- report,
- report.HANDLED(forecast_years_) ?
- static_cast<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;
}
diff --git a/src/chain.h b/src/chain.h
index 94d54317..59b04eb8 100644
--- a/src/chain.h
+++ b/src/chain.h
@@ -65,27 +65,51 @@ public:
TRACE_DTOR(item_handler);
}
+ virtual void title(const string& str) {
+ if (handler)
+ handler->title(str);
+ }
+
virtual void flush() {
- if (handler.get())
+ if (handler)
handler->flush();
}
virtual void operator()(T& item) {
- if (handler.get()) {
+ if (handler) {
check_for_signal();
- (*handler.get())(item);
+ (*handler)(item);
}
}
+
+ virtual void clear() {
+ if (handler)
+ handler->clear();
+ }
};
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_pre_post_handlers(report_t& report,
+ post_handler_ptr base_handler);
+
post_handler_ptr
chain_post_handlers(report_t& report,
post_handler_ptr base_handler,
bool for_accounts_report = false);
+inline post_handler_ptr
+chain_handlers(report_t& report,
+ post_handler_ptr handler,
+ bool for_accounts_report = false) {
+ handler = chain_post_handlers(report, handler, for_accounts_report);
+ handler = chain_pre_post_handlers(report, handler);
+ return handler;
+}
+
} // namespace ledger
#endif // _CHAIN_H
diff --git a/src/commodity.cc b/src/commodity.cc
index 836a4269..1b85910f 100644
--- a/src/commodity.cc
+++ b/src/commodity.cc
@@ -38,7 +38,7 @@
namespace ledger {
-bool commodity_t::european_by_default = false;
+bool commodity_t::decimal_comma_by_default = false;
void commodity_t::history_t::add_price(commodity_t& source,
const datetime_t& date,
@@ -454,7 +454,7 @@ void commodity_t::parse_symbol(std::istream& in, string& symbol)
{
// Invalid commodity characters:
// SPACE, TAB, NEWLINE, RETURN
- // 0-9 . , ; - + * / ^ ? : & | ! =
+ // 0-9 . , ; : ? ! - + * / ^ & | =
// < > { } [ ] ( ) @
static int invalid_chars[256] = {
@@ -663,10 +663,10 @@ void to_xml(std::ostream& out, const commodity_t& comm,
push_xml x(out, "commodity", true);
out << " flags=\"";
- if (! (comm.has_flags(COMMODITY_STYLE_SUFFIXED))) out << 'P';
- if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) out << 'S';
- if (comm.has_flags(COMMODITY_STYLE_THOUSANDS)) out << 'T';
- if (comm.has_flags(COMMODITY_STYLE_EUROPEAN)) out << 'E';
+ if (! (comm.has_flags(COMMODITY_STYLE_SUFFIXED))) out << 'P';
+ if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) out << 'S';
+ if (comm.has_flags(COMMODITY_STYLE_THOUSANDS)) out << 'T';
+ if (comm.has_flags(COMMODITY_STYLE_DECIMAL_COMMA)) out << 'D';
out << '"';
x.close_attrs();
diff --git a/src/commodity.h b/src/commodity.h
index 10f209fa..53e3033f 100644
--- a/src/commodity.h
+++ b/src/commodity.h
@@ -155,16 +155,16 @@ protected:
class base_t : public noncopyable, public supports_flags<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
+#define COMMODITY_STYLE_DEFAULTS 0x000
+#define COMMODITY_STYLE_SUFFIXED 0x001
+#define COMMODITY_STYLE_SEPARATED 0x002
+#define COMMODITY_STYLE_DECIMAL_COMMA 0x004
+#define COMMODITY_STYLE_THOUSANDS 0x008
+#define COMMODITY_NOMARKET 0x010
+#define COMMODITY_BUILTIN 0x020
+#define COMMODITY_WALKED 0x040
+#define COMMODITY_KNOWN 0x080
+#define COMMODITY_PRIMARY 0x100
string symbol;
amount_t::precision_t precision;
@@ -179,8 +179,8 @@ protected:
public:
explicit base_t(const string& _symbol)
: supports_flags<uint_least16_t>
- (commodity_t::european_by_default ?
- static_cast<uint_least16_t>(COMMODITY_STYLE_EUROPEAN) :
+ (commodity_t::decimal_comma_by_default ?
+ static_cast<uint_least16_t>(COMMODITY_STYLE_DECIMAL_COMMA) :
static_cast<uint_least16_t>(COMMODITY_STYLE_DEFAULTS)),
symbol(_symbol), precision(0), searched(false) {
TRACE_CTOR(base_t, "const string&");
@@ -228,7 +228,7 @@ protected:
}
public:
- static bool european_by_default;
+ static bool decimal_comma_by_default;
virtual ~commodity_t() {
TRACE_DTOR(commodity_t);
diff --git a/src/convert.cc b/src/convert.cc
index 2e6da2f6..aa9bbb6f 100644
--- a/src/convert.cc
+++ b/src/convert.cc
@@ -117,7 +117,7 @@ value_t convert_command(call_scope_t& scope)
if (matched) {
DEBUG("convert.csv", "Ignored xact with code: " << *xact->code);
- delete xact; // ignore it
+ checked_delete(xact); // ignore it
}
else {
if (xact->posts.front()->account == NULL) {
@@ -135,7 +135,7 @@ value_t convert_command(call_scope_t& scope)
}
if (! journal.add_xact(xact)) {
- delete xact;
+ checked_delete(xact);
throw_(std::runtime_error,
_("Failed to finalize derived transaction (check commodities)"));
}
diff --git a/src/draft.cc b/src/draft.cc
index 18075731..69dc7025 100644
--- a/src/draft.cc
+++ b/src/draft.cc
@@ -240,6 +240,9 @@ void draft_t::parse_args(const value_t& args)
xact_t * draft_t::insert(journal_t& journal)
{
+ if (! tmpl)
+ return NULL;
+
if (tmpl->payee_mask.empty())
throw std::runtime_error(_("'xact' command requires at least a payee"));
@@ -528,7 +531,8 @@ value_t xact_command(call_scope_t& args)
// Only consider actual postings for the "xact" command
report.HANDLER(limit_).on(string("#xact"), "actual");
- report.xact_report(post_handler_ptr(new print_xacts(report)), *new_xact);
+ if (new_xact)
+ report.xact_report(post_handler_ptr(new print_xacts(report)), *new_xact);
return true;
}
diff --git a/src/emacs.cc b/src/emacs.cc
index d47f04ad..3c8bb256 100644
--- a/src/emacs.cc
+++ b/src/emacs.cc
@@ -40,18 +40,21 @@ namespace ledger {
void format_emacs_posts::write_xact(xact_t& xact)
{
- out << "\"" << xact.pos->pathname << "\" "
- << xact.pos->beg_line << " ";
+ if (xact.pos)
+ out << "\"" << xact.pos->pathname << "\" "
+ << xact.pos->beg_line << " ";
+ else
+ out << "\"\" " << -1 << " ";
tm when = gregorian::to_tm(xact.date());
std::time_t date = std::mktime(&when);
out << "(" << (date / 65536) << " " << (date % 65536) << " 0) ";
- if (! xact.code)
- out << "nil ";
- else
+ if (xact.code)
out << "\"" << *xact.code << "\" ";
+ else
+ out << "nil ";
if (xact.payee.empty())
out << "nil";
@@ -77,7 +80,11 @@ void format_emacs_posts::operator()(post_t& post)
out << "\n";
}
- out << " (" << post.pos->beg_line << " ";
+ if (post.pos)
+ out << " (" << post.pos->beg_line << " ";
+ else
+ out << " (" << -1 << " ";
+
out << "\"" << post.reported_account()->fullname() << "\" \""
<< post.amount << "\"";
diff --git a/src/filters.cc b/src/filters.cc
index 0c45d356..ad4b88a0 100644
--- a/src/filters.cc
+++ b/src/filters.cc
@@ -39,8 +39,51 @@
namespace ledger {
+void post_splitter::print_title(const value_t& val)
+{
+ if (! report.HANDLED(no_titles)) {
+ std::ostringstream buf;
+ val.print(buf);
+ post_chain->title(buf.str());
+ }
+}
+
+void post_splitter::flush()
+{
+ foreach (value_to_posts_map::value_type pair, posts_map) {
+ preflush_func(pair.first);
+
+ foreach (post_t * post, pair.second)
+ (*post_chain)(*post);
+
+ post_chain->flush();
+ post_chain->clear();
+
+ if (postflush_func)
+ (*postflush_func)(pair.first);
+ }
+}
+
+void post_splitter::operator()(post_t& post)
+{
+ bind_scope_t bound_scope(report, post);
+ value_t result(group_by_expr.calc(bound_scope));
+
+ if (! result.is_null()) {
+ value_to_posts_map::iterator i = posts_map.find(result);
+ if (i != posts_map.end()) {
+ (*i).second.push_back(&post);
+ } else {
+ std::pair<value_to_posts_map::iterator, bool> inserted
+ = posts_map.insert(value_to_posts_map::value_type(result, posts_list()));
+ assert(inserted.second);
+ (*inserted.first).second.push_back(&post);
+ }
+ }
+}
+
pass_down_posts::pass_down_posts(post_handler_ptr handler,
- posts_iterator& iter)
+ posts_iterator& iter)
: item_handler<post_t>(handler)
{
TRACE_CTOR(pass_down_posts, "post_handler_ptr, posts_iterator");
@@ -312,7 +355,7 @@ namespace {
if (functor)
(*functor)(post);
- DEBUG("filter.changed_value.rounding", "post.amount = " << post.amount);
+ DEBUG("filters.changed_value.rounding", "post.amount = " << post.amount);
(*handler)(post);
@@ -355,7 +398,7 @@ void collapse_posts::report_subtotal()
xact.payee = last_xact->payee;
xact._date = (is_valid(earliest_date) ?
earliest_date : last_xact->_date);
- DEBUG("filter.collapse", "Pseudo-xact date = " << *xact._date);
+ DEBUG("filters.collapse", "Pseudo-xact date = " << *xact._date);
handle_value(subtotal, &totals_account, &xact, temps, handler);
}
@@ -420,10 +463,12 @@ void related_posts::flush()
changed_value_posts::changed_value_posts(post_handler_ptr handler,
report_t& _report,
bool _for_accounts_report,
- bool _show_unrealized)
+ bool _show_unrealized,
+ bool _show_rounding)
: item_handler<post_t>(handler), report(_report),
for_accounts_report(_for_accounts_report),
- show_unrealized(_show_unrealized), last_post(NULL),
+ show_unrealized(_show_unrealized),
+ show_rounding(_show_rounding), last_post(NULL),
revalued_account(temps.create_account(_("<Revalued>"))),
rounding_account(temps.create_account(_("<Rounding>")))
{
@@ -458,6 +503,8 @@ changed_value_posts::changed_value_posts(post_handler_ptr handler,
void changed_value_posts::flush()
{
if (last_post && last_post->date() <= report.terminus.date()) {
+ if (! for_accounts_report)
+ output_intermediate_prices(*last_post, report.terminus.date());
output_revaluation(*last_post, report.terminus.date());
last_post = NULL;
}
@@ -469,7 +516,6 @@ void changed_value_posts::output_revaluation(post_t& post, const date_t& date)
if (is_valid(date))
post.xdata().date = date;
- value_t repriced_total;
try {
bind_scope_t bound_scope(report, post);
repriced_total = total_expr.calc(bound_scope);
@@ -480,14 +526,14 @@ void changed_value_posts::output_revaluation(post_t& post, const date_t& date)
}
post.xdata().date = date_t();
- DEBUG("filter.changed_value",
- "output_revaluation(last_balance) = " << last_total);
- DEBUG("filter.changed_value",
+ DEBUG("filters.changed_value",
+ "output_revaluation(last_total) = " << last_total);
+ DEBUG("filters.changed_value",
"output_revaluation(repriced_total) = " << repriced_total);
if (! last_total.is_null()) {
if (value_t diff = repriced_total - last_total) {
- DEBUG("filter.changed_value", "output_revaluation(strip(diff)) = "
+ DEBUG("filters.changed_value", "output_revaluation(strip(diff)) = "
<< diff.strip_annotations(report.what_to_keep()));
xact_t& xact = temps.create_xact();
@@ -505,9 +551,10 @@ void changed_value_posts::output_revaluation(post_t& post, const date_t& date)
/* total= */ repriced_total,
/* direct_amount= */ false,
/* mark_visited= */ false,
- /* functor= */ (optional<post_functor_t>
+ /* functor= */ (show_rounding ?
+ optional<post_functor_t>
(bind(&changed_value_posts::output_rounding,
- this, _1))));
+ this, _1)) : none));
}
else if (show_unrealized) {
handle_value
@@ -527,29 +574,146 @@ void changed_value_posts::output_revaluation(post_t& post, const date_t& date)
}
}
+void changed_value_posts::output_intermediate_prices(post_t& post,
+ const date_t& current)
+{
+ // To fix BZ#199, examine the balance of last_post and determine whether the
+ // price of that amount changed after its date and before the new post's
+ // date. If so, generate an output_revaluation for that price change.
+ // Mostly this is only going to occur if the user has a series of pricing
+ // entries, since a posting-based revaluation would be seen here as a post.
+
+ value_t display_total(last_total);
+
+ if (display_total.type() == value_t::SEQUENCE) {
+ xact_t& xact(temps.create_xact());
+
+ xact.payee = _("Commodities revalued");
+ xact._date = is_valid(current) ? current : post.date();
+
+ post_t& temp(temps.copy_post(post, xact));
+ temp.add_flags(ITEM_GENERATED);
+
+ post_t::xdata_t& xdata(temp.xdata());
+ if (is_valid(current))
+ xdata.date = current;
+
+ DEBUG("filters.revalued", "intermediate last_total = " << last_total);
+
+ switch (last_total.type()) {
+ case value_t::BOOLEAN:
+ case value_t::INTEGER:
+ last_total.in_place_cast(value_t::AMOUNT);
+ // fall through...
+
+ case value_t::AMOUNT:
+ temp.amount = last_total.as_amount();
+ break;
+
+ case value_t::BALANCE:
+ case value_t::SEQUENCE:
+ xdata.compound_value = last_total;
+ xdata.add_flags(POST_EXT_COMPOUND);
+ break;
+
+ case value_t::DATETIME:
+ case value_t::DATE:
+ default:
+ assert(false);
+ break;
+ }
+
+ bind_scope_t inner_scope(report, temp);
+ display_total = display_total_expr.calc(inner_scope);
+
+ DEBUG("filters.revalued", "intermediate display_total = " << display_total);
+ }
+
+ switch (display_total.type()) {
+ case value_t::VOID:
+ case value_t::INTEGER:
+ case value_t::SEQUENCE:
+ break;
+
+ case value_t::AMOUNT:
+ display_total.in_place_cast(value_t::BALANCE);
+ // fall through...
+
+ case value_t::BALANCE: {
+ commodity_t::history_map all_prices;
+
+ foreach (const balance_t::amounts_map::value_type& amt_comm,
+ display_total.as_balance().amounts) {
+ if (optional<commodity_t::varied_history_t&> hist =
+ amt_comm.first->varied_history()) {
+ foreach
+ (const commodity_t::history_by_commodity_map::value_type& comm_hist,
+ hist->histories) {
+ foreach (const commodity_t::history_map::value_type& price,
+ comm_hist.second.prices) {
+ if (price.first.date() > post.date() &&
+ price.first.date() < current) {
+ DEBUG("filters.revalued", post.date() << " < "
+ << price.first.date() << " < " << current);
+ DEBUG("filters.revalued", "inserting "
+ << price.second << " at " << price.first.date());
+ all_prices.insert(price);
+ }
+ }
+ }
+ }
+ }
+
+ // Choose the last price from each day as the price to use
+ typedef std::map<const date_t, bool> date_map;
+ date_map pricing_dates;
+
+ BOOST_REVERSE_FOREACH
+ (const commodity_t::history_map::value_type& price, all_prices) {
+ // This insert will fail if a later price has already been inserted
+ // for that date.
+ DEBUG("filters.revalued",
+ "re-inserting " << price.second << " at " << price.first.date());
+ pricing_dates.insert(date_map::value_type(price.first.date(), true));
+ }
+
+ // Go through the time-sorted prices list, outputting a revaluation for
+ // each price difference.
+ foreach (const date_map::value_type& price, pricing_dates) {
+ output_revaluation(post, price.first);
+ last_total = repriced_total;
+ }
+ break;
+ }
+ default:
+ assert(false);
+ break;
+ }
+}
+
void changed_value_posts::output_rounding(post_t& post)
{
bind_scope_t bound_scope(report, post);
value_t new_display_total(display_total_expr.calc(bound_scope));
- DEBUG("filter.changed_value.rounding",
+ DEBUG("filters.changed_value.rounding",
"rounding.new_display_total = " << new_display_total);
if (! last_display_total.is_null()) {
if (value_t repriced_amount = display_amount_expr.calc(bound_scope)) {
- DEBUG("filter.changed_value.rounding",
+ DEBUG("filters.changed_value.rounding",
"rounding.repriced_amount = " << repriced_amount);
value_t precise_display_total(new_display_total.truncated() -
repriced_amount.truncated());
- DEBUG("filter.changed_value.rounding",
+ DEBUG("filters.changed_value.rounding",
"rounding.precise_display_total = " << precise_display_total);
- DEBUG("filter.changed_value.rounding",
+ DEBUG("filters.changed_value.rounding",
"rounding.last_display_total = " << last_display_total);
if (value_t diff = precise_display_total - last_display_total) {
- DEBUG("filter.changed_value.rounding",
+ DEBUG("filters.changed_value.rounding",
"rounding.diff = " << diff);
xact_t& xact = temps.create_xact();
@@ -566,13 +730,16 @@ void changed_value_posts::output_rounding(post_t& post)
void changed_value_posts::operator()(post_t& post)
{
- if (last_post)
+ if (last_post) {
+ if (! for_accounts_report)
+ output_intermediate_prices(*last_post, post.date());
output_revaluation(*last_post, post.date());
+ }
if (changed_values_only)
post.xdata().add_flags(POST_EXT_DISPLAYED);
- if (! for_accounts_report)
+ if (! for_accounts_report && show_rounding)
output_rounding(post);
item_handler<post_t>::operator()(post);
@@ -793,41 +960,43 @@ void transfer_details::operator()(post_t& post)
bind_scope_t bound_scope(scope, temp);
value_t substitute(expr.calc(bound_scope));
- switch (which_element) {
- case SET_DATE:
- temp.xdata().date = substitute.to_date();
- break;
+ if (! substitute.is_null()) {
+ switch (which_element) {
+ case SET_DATE:
+ temp.xdata().date = substitute.to_date();
+ break;
- case SET_ACCOUNT: {
- string account_name = substitute.to_string();
- if (! account_name.empty() &&
- account_name[account_name.length() - 1] != ':') {
- account_t * prev_account = temp.account;
- temp.account->remove_post(&temp);
-
- account_name += ':';
- account_name += prev_account->fullname();
-
- std::list<string> account_names;
- split_string(account_name, ':', account_names);
- temp.account = create_temp_account_from_path(account_names, temps,
- xact.journal->master);
- temp.account->add_post(&temp);
-
- temp.account->add_flags(prev_account->flags());
- if (prev_account->has_xdata())
- temp.account->xdata().add_flags(prev_account->xdata().flags());
+ case SET_ACCOUNT: {
+ string account_name = substitute.to_string();
+ if (! account_name.empty() &&
+ account_name[account_name.length() - 1] != ':') {
+ account_t * prev_account = temp.account;
+ temp.account->remove_post(&temp);
+
+ account_name += ':';
+ account_name += prev_account->fullname();
+
+ std::list<string> account_names;
+ split_string(account_name, ':', account_names);
+ temp.account = create_temp_account_from_path(account_names, temps,
+ xact.journal->master);
+ temp.account->add_post(&temp);
+
+ temp.account->add_flags(prev_account->flags());
+ if (prev_account->has_xdata())
+ temp.account->xdata().add_flags(prev_account->xdata().flags());
+ }
+ break;
}
- break;
- }
- case SET_PAYEE:
- xact.payee = substitute.to_string();
- break;
+ case SET_PAYEE:
+ xact.payee = substitute.to_string();
+ break;
- default:
- assert(false);
- break;
+ default:
+ assert(false);
+ break;
+ }
}
item_handler<post_t>::operator()(temp);
@@ -869,7 +1038,8 @@ void budget_posts::report_budget_items(const date_t& date)
optional<date_t> begin = pair.first.start;
if (! begin) {
if (! pair.first.find_period(date))
- throw_(std::runtime_error, _("Something odd has happened"));
+ throw_(std::runtime_error,
+ _("Something odd has happened at date %1") << date);
begin = pair.first.start;
}
assert(begin);
diff --git a/src/filters.h b/src/filters.h
index 82fbf687..a66d8c47 100644
--- a/src/filters.h
+++ b/src/filters.h
@@ -52,6 +52,56 @@ namespace ledger {
//////////////////////////////////////////////////////////////////////
//
+// Posting collector
+//
+
+class post_splitter : public item_handler<post_t>
+{
+public:
+ typedef std::map<value_t, posts_list> value_to_posts_map;
+ typedef function<void (const value_t&)> custom_flusher_t;
+
+protected:
+ value_to_posts_map posts_map;
+ report_t& report;
+ post_handler_ptr post_chain;
+ expr_t group_by_expr;
+ custom_flusher_t preflush_func;
+ optional<custom_flusher_t> postflush_func;
+
+public:
+ post_splitter(report_t& _report,
+ post_handler_ptr _post_chain,
+ expr_t _group_by_expr)
+ : report(_report), post_chain(_post_chain),
+ group_by_expr(_group_by_expr),
+ preflush_func(bind(&post_splitter::print_title, this, _1)) {
+ TRACE_CTOR(post_splitter, "scope_t&, post_handler_ptr, expr_t");
+ }
+ virtual ~post_splitter() {
+ TRACE_DTOR(post_splitter);
+ }
+
+ void set_preflush_func(custom_flusher_t functor) {
+ preflush_func = functor;
+ }
+ void set_postflush_func(custom_flusher_t functor) {
+ postflush_func = functor;
+ }
+
+ virtual void print_title(const value_t& val);
+
+ virtual void flush();
+ virtual void operator()(post_t& post);
+
+ virtual void clear() {
+ posts_map.clear();
+ post_chain->clear();
+ }
+};
+
+//////////////////////////////////////////////////////////////////////
+//
// Posting filters
//
@@ -88,6 +138,11 @@ public:
virtual void operator()(post_t& post) {
posts.push_back(&post);
}
+
+ virtual void clear() {
+ posts.clear();
+ item_handler<post_t>::clear();
+ }
};
class posts_iterator;
@@ -149,27 +204,34 @@ public:
virtual void flush();
virtual void operator()(post_t& post);
+
+ virtual void clear() {
+ completed = false;
+ posts.clear();
+ xacts_seen = 0;
+ last_xact = NULL;
+
+ item_handler<post_t>::clear();
+ }
};
class sort_posts : public item_handler<post_t>
{
typedef std::deque<post_t *> posts_deque;
- posts_deque posts;
- const expr_t sort_order;
+ posts_deque posts;
+ expr_t sort_order;
sort_posts();
public:
- sort_posts(post_handler_ptr handler,
- const expr_t& _sort_order)
+ sort_posts(post_handler_ptr handler, const expr_t& _sort_order)
: item_handler<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)
+ sort_posts(post_handler_ptr handler, const string& _sort_order)
: item_handler<post_t>(handler),
sort_order(_sort_order) {
TRACE_CTOR(sort_posts,
@@ -189,6 +251,13 @@ public:
virtual void operator()(post_t& post) {
posts.push_back(&post);
}
+
+ virtual void clear() {
+ posts.clear();
+ sort_order.mark_uncompiled();
+
+ item_handler<post_t>::clear();
+ }
};
class sort_xacts : public item_handler<post_t>
@@ -199,14 +268,12 @@ class sort_xacts : public item_handler<post_t>
sort_xacts();
public:
- sort_xacts(post_handler_ptr handler,
- const expr_t& _sort_order)
+ sort_xacts(post_handler_ptr handler, const expr_t& _sort_order)
: sorter(handler, _sort_order) {
TRACE_CTOR(sort_xacts,
"post_handler_ptr, const value_expr&");
}
- sort_xacts(post_handler_ptr handler,
- const string& _sort_order)
+ sort_xacts(post_handler_ptr handler, const string& _sort_order)
: sorter(handler, _sort_order) {
TRACE_CTOR(sort_xacts,
"post_handler_ptr, const string&");
@@ -228,6 +295,13 @@ public:
last_xact = post.xact;
}
+
+ virtual void clear() {
+ sorter.clear();
+ last_xact = NULL;
+
+ item_handler<post_t>::clear();
+ }
};
class filter_posts : public item_handler<post_t>
@@ -255,6 +329,11 @@ public:
(*handler)(post);
}
}
+
+ virtual void clear() {
+ pred.mark_uncompiled();
+ item_handler<post_t>::clear();
+ }
};
class anonymize_posts : public item_handler<post_t>
@@ -274,6 +353,13 @@ public:
}
virtual void operator()(post_t& post);
+
+ virtual void clear() {
+ temps.clear();
+ last_xact = NULL;
+
+ item_handler<post_t>::clear();
+ }
};
class calc_posts : public item_handler<post_t>
@@ -297,6 +383,13 @@ public:
}
virtual void operator()(post_t& post);
+
+ virtual void clear() {
+ last_post = NULL;
+ amount_expr.mark_uncompiled();
+
+ item_handler<post_t>::clear();
+ }
};
class collapse_posts : public item_handler<post_t>
@@ -334,13 +427,29 @@ public:
}
virtual void flush() {
- report_subtotal();
+ report_subtotal();
item_handler<post_t>::flush();
}
void report_subtotal();
virtual void operator()(post_t& post);
+
+ virtual void clear() {
+ amount_expr.mark_uncompiled();
+ display_predicate.mark_uncompiled();
+ only_predicate.mark_uncompiled();
+
+ subtotal = value_t();
+ count = 0;
+ last_xact = NULL;
+ last_post = NULL;
+
+ temps.clear();
+ component_posts.clear();
+
+ item_handler<post_t>::clear();
+ }
};
class related_posts : public item_handler<post_t>
@@ -367,6 +476,11 @@ public:
post.xdata().add_flags(POST_EXT_RECEIVED);
posts.push_back(&post);
}
+
+ virtual void clear() {
+ posts.clear();
+ item_handler<post_t>::clear();
+ }
};
class changed_value_posts : public item_handler<post_t>
@@ -381,9 +495,11 @@ class changed_value_posts : public item_handler<post_t>
bool changed_values_only;
bool for_accounts_report;
bool show_unrealized;
+ bool show_rounding;
post_t * last_post;
value_t last_total;
value_t last_display_total;
+ value_t repriced_total;
temporaries_t temps;
account_t& revalued_account;
account_t& rounding_account;
@@ -396,7 +512,8 @@ public:
changed_value_posts(post_handler_ptr handler,
report_t& _report,
bool _for_accounts_report,
- bool _show_unrealized);
+ bool _show_unrealized,
+ bool _show_rounding);
virtual ~changed_value_posts() {
TRACE_DTOR(changed_value_posts);
@@ -405,9 +522,24 @@ public:
virtual void flush();
void output_revaluation(post_t& post, const date_t& current);
+ void output_intermediate_prices(post_t& post, const date_t& current);
void output_rounding(post_t& post);
virtual void operator()(post_t& post);
+
+ virtual void clear() {
+ display_amount_expr.mark_uncompiled();
+ total_expr.mark_uncompiled();
+ display_total_expr.mark_uncompiled();
+
+ last_post = NULL;
+ last_total = value_t();
+ last_display_total = value_t();
+
+ temps.clear();
+
+ item_handler<post_t>::clear();
+ }
};
class subtotal_posts : public item_handler<post_t>
@@ -469,10 +601,20 @@ public:
item_handler<post_t>::flush();
}
virtual void operator()(post_t& post);
+
+ virtual void clear() {
+ amount_expr.mark_uncompiled();
+ values.clear();
+ temps.clear();
+ component_posts.clear();
+
+ item_handler<post_t>::clear();
+ }
};
class interval_posts : public subtotal_posts
{
+ date_interval_t start_interval;
date_interval_t interval;
date_interval_t last_interval;
post_t * last_post;
@@ -489,8 +631,9 @@ public:
const date_interval_t& _interval,
bool _exact_periods = false,
bool _generate_empty_posts = false)
- : subtotal_posts(_handler, amount_expr), interval(_interval),
- last_post(NULL), empty_account(temps.create_account(_("<None>"))),
+ : subtotal_posts(_handler, amount_expr), start_interval(_interval),
+ interval(start_interval), last_post(NULL),
+ empty_account(temps.create_account(_("<None>"))),
exact_periods(_exact_periods),
generate_empty_posts(_generate_empty_posts) {
TRACE_CTOR(interval_posts,
@@ -510,6 +653,14 @@ public:
}
}
virtual void operator()(post_t& post);
+
+ virtual void clear() {
+ interval = start_interval;
+ last_interval = date_interval_t();
+ last_post = NULL;
+
+ item_handler<post_t>::clear();
+ }
};
class posts_as_equity : public subtotal_posts
@@ -537,6 +688,11 @@ public:
report_subtotal();
subtotal_posts::flush();
}
+
+ virtual void clear() {
+ last_post = NULL;
+ item_handler<post_t>::clear();
+ }
};
class by_payee_posts : public item_handler<post_t>
@@ -560,6 +716,13 @@ class by_payee_posts : public item_handler<post_t>
virtual void flush();
virtual void operator()(post_t& post);
+
+ virtual void clear() {
+ amount_expr.mark_uncompiled();
+ payee_subtotals.clear();
+
+ item_handler<post_t>::clear();
+ }
};
class transfer_details : public item_handler<post_t>
@@ -593,6 +756,13 @@ public:
}
virtual void operator()(post_t& post);
+
+ virtual void clear() {
+ expr.mark_uncompiled();
+ temps.clear();
+
+ item_handler<post_t>::clear();
+ }
};
class dow_posts : public subtotal_posts
@@ -614,6 +784,13 @@ public:
virtual void operator()(post_t& post) {
days_of_the_week[post.date().day_of_week()].push_back(&post);
}
+
+ virtual void clear() {
+ for (int i = 0; i < 7; i++)
+ days_of_the_week[i].clear();
+
+ item_handler<post_t>::clear();
+ }
};
class generate_posts : public item_handler<post_t>
@@ -640,6 +817,13 @@ public:
void add_period_xacts(period_xacts_list& period_xacts);
virtual void add_post(const date_interval_t& period, post_t& post);
+
+ virtual void clear() {
+ pending_posts.clear();
+ temps.clear();
+
+ item_handler<post_t>::clear();
+ }
};
class budget_posts : public generate_posts
@@ -690,6 +874,11 @@ class forecast_posts : public generate_posts
virtual void add_post(const date_interval_t& period, post_t& post);
virtual void flush();
+
+ virtual void clear() {
+ pred.mark_uncompiled();
+ item_handler<post_t>::clear();
+ }
};
//////////////////////////////////////////////////////////////////////
@@ -715,6 +904,13 @@ public:
virtual ~pass_down_accounts() {
TRACE_DTOR(pass_down_accounts);
}
+
+ virtual void clear() {
+ if (pred)
+ pred->mark_uncompiled();
+
+ item_handler<account_t>::clear();
+ }
};
} // namespace ledger
diff --git a/src/item.cc b/src/item.cc
index 14a0896f..0a22b260 100644
--- a/src/item.cc
+++ b/src/item.cc
@@ -227,7 +227,7 @@ namespace {
return NULL_VALUE;
}
value_t get_note(item_t& item) {
- return string_value(item.note ? *item.note : empty_string);
+ return item.note ? string_value(*item.note) : NULL_VALUE;
}
value_t has_tag(call_scope_t& args) {
@@ -260,7 +260,8 @@ namespace {
return false;
}
- value_t get_tag(call_scope_t& args) {
+ value_t get_tag(call_scope_t& args)
+ {
item_t& item(find_scope<item_t>(args));
optional<string> str;
@@ -292,14 +293,14 @@ namespace {
if (str)
return string_value(*str);
else
- return string_value(empty_string);
+ return NULL_VALUE;
}
value_t get_pathname(item_t& item) {
if (item.pos)
return string_value(item.pos->pathname.string());
else
- return string_value(empty_string);
+ return NULL_VALUE;
}
value_t get_beg_pos(item_t& item) {
diff --git a/src/option.h b/src/option.h
index 9688171e..f11497a4 100644
--- a/src/option.h
+++ b/src/option.h
@@ -90,13 +90,16 @@ public:
void report(std::ostream& out) const {
if (handled && source) {
+ out.width(24);
+ out << std::right << desc();
if (wants_arg) {
- out << desc() << " => ";
- value.dump(out);
+ out << " = ";
+ value.print(out, 42);
} else {
- out << desc();
+ out.width(45);
+ out << ' ';
}
- out << " <" << *source << ">" << std::endl;
+ out << std::left << *source << std::endl;
}
}
@@ -202,20 +205,20 @@ public:
};
#define BEGIN(type, name) \
- struct name ## _option_t : public option_t<type>
+ struct name ## option_t : public option_t<type>
#define CTOR(type, name) \
- name ## _option_t() : option_t<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)
+ 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 END(name) name ## handler
-#define COPY_OPT(name, other) name ## _handler(other.name ## _handler)
+#define COPY_OPT(name, other) name ## handler(other.name ## handler)
#define MAKE_OPT_HANDLER(type, x) \
expr_t::op_t::wrap_functor(bind(&option_t<type>::handler_wrapper, x, _1))
@@ -235,26 +238,26 @@ inline bool is_eq(const char * p, const char * n) {
#define OPT(name) \
if (is_eq(p, #name)) \
- return ((name ## _handler).parent = this, &(name ## _handler))
+ return ((name ## handler).parent = this, &(name ## handler))
#define OPT_ALT(name, alt) \
if (is_eq(p, #name) || is_eq(p, #alt)) \
- return ((name ## _handler).parent = this, &(name ## _handler))
+ return ((name ## handler).parent = this, &(name ## handler))
#define OPT_(name) \
if (! *(p + 1) || \
- ((name ## _handler).wants_arg && \
+ ((name ## handler).wants_arg && \
*(p + 1) == '_' && ! *(p + 2)) || \
is_eq(p, #name)) \
- return ((name ## _handler).parent = this, &(name ## _handler))
+ return ((name ## handler).parent = this, &(name ## handler))
#define OPT_CH(name) \
if (! *(p + 1) || \
- ((name ## _handler).wants_arg && \
+ ((name ## handler).wants_arg && \
*(p + 1) == '_' && ! *(p + 2))) \
- return ((name ## _handler).parent = this, &(name ## _handler))
+ return ((name ## handler).parent = this, &(name ## handler))
-#define HANDLER(name) name ## _handler
+#define HANDLER(name) name ## handler
#define HANDLED(name) HANDLER(name)
#define OPTION(type, name) \
diff --git a/src/output.cc b/src/output.cc
index 30775310..f697dee4 100644
--- a/src/output.cc
+++ b/src/output.cc
@@ -42,8 +42,10 @@ namespace ledger {
format_posts::format_posts(report_t& _report,
const string& format,
- const optional<string>& _prepend_format)
- : report(_report), last_xact(NULL), last_post(NULL)
+ const optional<string>& _prepend_format,
+ std::size_t _prepend_width)
+ : report(_report), prepend_width(_prepend_width),
+ last_xact(NULL), last_post(NULL), first_report_title(true)
{
TRACE_CTOR(format_posts, "report&, const string&, bool");
@@ -76,14 +78,34 @@ void format_posts::flush()
void format_posts::operator()(post_t& post)
{
- std::ostream& out(report.output_stream);
-
if (! post.has_xdata() ||
! post.xdata().has_flags(POST_EXT_DISPLAYED)) {
+ std::ostream& out(report.output_stream);
+
bind_scope_t bound_scope(report, post);
- if (prepend_format)
+ if (! report_title.empty()) {
+ if (first_report_title)
+ first_report_title = false;
+ else
+ out << '\n';
+
+ value_scope_t val_scope(string_value(report_title));
+ bind_scope_t inner_scope(bound_scope, val_scope);
+
+ format_t group_title_format;
+ group_title_format
+ .parse_format(report.HANDLER(group_title_format_).str());
+
+ out << group_title_format(inner_scope);
+
+ report_title = "";
+ }
+
+ if (prepend_format) {
+ out.width(prepend_width);
out << prepend_format(bound_scope);
+ }
if (last_xact != post.xact) {
if (last_xact) {
@@ -107,8 +129,10 @@ void format_posts::operator()(post_t& post)
format_accounts::format_accounts(report_t& _report,
const string& format,
- const optional<string>& _prepend_format)
- : report(_report), disp_pred()
+ const optional<string>& _prepend_format,
+ std::size_t _prepend_width)
+ : report(_report), prepend_width(_prepend_width), disp_pred(),
+ first_report_title(true)
{
TRACE_CTOR(format_accounts, "report&, const string&");
@@ -139,17 +163,37 @@ std::size_t format_accounts::post_account(account_t& account, const bool flat)
if (account.xdata().has_flags(ACCOUNT_EXT_TO_DISPLAY) &&
! account.xdata().has_flags(ACCOUNT_EXT_DISPLAYED)) {
+ std::ostream& out(report.output_stream);
+
DEBUG("account.display", "Displaying account: " << account.fullname());
account.xdata().add_flags(ACCOUNT_EXT_DISPLAYED);
bind_scope_t bound_scope(report, account);
- if (prepend_format)
- static_cast<std::ostream&>(report.output_stream)
- << prepend_format(bound_scope);
+ if (! report_title.empty()) {
+ if (first_report_title)
+ first_report_title = false;
+ else
+ out << '\n';
+
+ value_scope_t val_scope(string_value(report_title));
+ bind_scope_t inner_scope(bound_scope, val_scope);
+
+ format_t group_title_format;
+ group_title_format
+ .parse_format(report.HANDLER(group_title_format_).str());
- static_cast<std::ostream&>(report.output_stream)
- << account_line_format(bound_scope);
+ out << group_title_format(inner_scope);
+
+ report_title = "";
+ }
+
+ if (prepend_format) {
+ out.width(prepend_width);
+ out << prepend_format(bound_scope);
+ }
+
+ out << account_line_format(bound_scope);
return 1;
}
@@ -216,9 +260,11 @@ void format_accounts::flush()
bind_scope_t bound_scope(report, *report.session.journal->master);
out << separator_format(bound_scope);
- if (prepend_format)
+ if (prepend_format) {
+ static_cast<std::ostream&>(report.output_stream).width(prepend_width);
static_cast<std::ostream&>(report.output_stream)
<< prepend_format(bound_scope);
+ }
out << total_line_format(bound_scope);
}
@@ -232,4 +278,89 @@ void format_accounts::operator()(account_t& account)
posted_accounts.push_back(&account);
}
+void report_accounts::flush()
+{
+ std::ostream& out(report.output_stream);
+
+ foreach (accounts_pair& entry, accounts) {
+ if (report.HANDLED(count))
+ out << entry.second << ' ';
+ out << *entry.first << '\n';
+ }
+}
+
+void report_accounts::operator()(post_t& post)
+{
+ std::map<account_t *, std::size_t>::iterator i = accounts.find(post.account);
+ if (i == accounts.end())
+ accounts.insert(accounts_pair(post.account, 1));
+ else
+ (*i).second++;
+}
+
+void report_payees::flush()
+{
+ std::ostream& out(report.output_stream);
+
+ foreach (payees_pair& entry, payees) {
+ if (report.HANDLED(count))
+ out << entry.second << ' ';
+ out << entry.first << '\n';
+ }
+}
+
+void report_payees::operator()(post_t& post)
+{
+ std::map<string, std::size_t>::iterator i = payees.find(post.xact->payee);
+ if (i == payees.end())
+ payees.insert(payees_pair(post.xact->payee, 1));
+ else
+ (*i).second++;
+}
+
+void report_commodities::flush()
+{
+ std::ostream& out(report.output_stream);
+
+ foreach (commodities_pair& entry, commodities) {
+ if (report.HANDLED(count))
+ out << entry.second << ' ';
+ out << *entry.first << '\n';
+ }
+}
+
+void report_commodities::operator()(post_t& post)
+{
+ amount_t temp(post.amount.strip_annotations(report.what_to_keep()));
+ commodity_t& comm(temp.commodity());
+
+ std::map<commodity_t *, std::size_t>::iterator i = commodities.find(&comm);
+ if (i == commodities.end())
+ commodities.insert(commodities_pair(&comm, 1));
+ else
+ (*i).second++;
+
+ if (comm.has_annotation()) {
+ annotated_commodity_t& ann_comm(as_annotated_commodity(comm));
+ if (ann_comm.details.price) {
+ std::map<commodity_t *, std::size_t>::iterator i =
+ commodities.find(&ann_comm.details.price->commodity());
+ if (i == commodities.end())
+ commodities.insert
+ (commodities_pair(&ann_comm.details.price->commodity(), 1));
+ else
+ (*i).second++;
+ }
+ }
+
+ if (post.cost) {
+ amount_t temp_cost(post.cost->strip_annotations(report.what_to_keep()));
+ i = commodities.find(&temp_cost.commodity());
+ if (i == commodities.end())
+ commodities.insert(commodities_pair(&temp_cost.commodity(), 1));
+ else
+ (*i).second++;
+ }
+}
+
} // namespace ledger
diff --git a/src/output.h b/src/output.h
index 7618e567..a19c6235 100644
--- a/src/output.h
+++ b/src/output.h
@@ -56,23 +56,40 @@ 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;
+ report_t& report;
+ format_t first_line_format;
+ format_t next_lines_format;
+ format_t between_format;
+ format_t prepend_format;
+ std::size_t prepend_width;
+ xact_t * last_xact;
+ post_t * last_post;
+ bool first_report_title;
+ string report_title;
public:
format_posts(report_t& _report, const string& format,
- const optional<string>& _prepend_format = none);
+ const optional<string>& _prepend_format = none,
+ std::size_t _prepend_width = 0);
virtual ~format_posts() {
TRACE_DTOR(format_posts);
}
+ virtual void title(const string& str) {
+ report_title = str;
+ }
+
virtual void flush();
virtual void operator()(post_t& post);
+
+ virtual void clear() {
+ last_xact = NULL;
+ last_post = NULL;
+
+ report_title = "";
+
+ item_handler<post_t>::clear();
+ }
};
class format_accounts : public item_handler<account_t>
@@ -83,13 +100,17 @@ protected:
format_t total_line_format;
format_t separator_format;
format_t prepend_format;
+ std::size_t prepend_width;
predicate_t disp_pred;
+ bool first_report_title;
+ string report_title;
std::list<account_t *> posted_accounts;
public:
format_accounts(report_t& _report, const string& _format,
- const optional<string>& _prepend_format = none);
+ const optional<string>& _prepend_format = none,
+ std::size_t _prepend_width = 0);
virtual ~format_accounts() {
TRACE_DTOR(format_accounts);
}
@@ -97,10 +118,101 @@ public:
std::pair<std::size_t, std::size_t>
mark_accounts(account_t& account, const bool flat);
+ virtual void title(const string& str) {
+ report_title = str;
+ }
+
virtual std::size_t post_account(account_t& account, const bool flat);
virtual void flush();
virtual void operator()(account_t& account);
+
+ virtual void clear() {
+ disp_pred.mark_uncompiled();
+ posted_accounts.clear();
+
+ report_title = "";
+
+ item_handler<account_t>::clear();
+ }
+};
+
+class report_accounts : public item_handler<post_t>
+{
+protected:
+ report_t& report;
+
+ std::map<account_t *, std::size_t> accounts;
+
+ typedef std::map<account_t *, std::size_t>::value_type accounts_pair;
+
+public:
+ report_accounts(report_t& _report) : report(_report) {
+ TRACE_CTOR(report_accounts, "report&");
+ }
+ virtual ~report_accounts() {
+ TRACE_DTOR(report_accounts);
+ }
+
+ virtual void flush();
+ virtual void operator()(post_t& post);
+
+ virtual void clear() {
+ accounts.clear();
+ item_handler<post_t>::clear();
+ }
+};
+
+class report_payees : public item_handler<post_t>
+{
+protected:
+ report_t& report;
+
+ std::map<string, std::size_t> payees;
+
+ typedef std::map<string, std::size_t>::value_type payees_pair;
+
+public:
+ report_payees(report_t& _report) : report(_report) {
+ TRACE_CTOR(report_payees, "report&");
+ }
+ virtual ~report_payees() {
+ TRACE_DTOR(report_payees);
+ }
+
+ virtual void flush();
+ virtual void operator()(post_t& post);
+
+ virtual void clear() {
+ payees.clear();
+ item_handler<post_t>::clear();
+ }
+};
+
+class report_commodities : public item_handler<post_t>
+{
+protected:
+ report_t& report;
+
+ std::map<commodity_t *, std::size_t> commodities;
+
+ typedef std::map<commodity_t *, std::size_t>::value_type commodities_pair;
+
+public:
+ report_commodities(report_t& _report) : report(_report) {
+ TRACE_CTOR(report_commodities, "report&");
+ }
+ virtual ~report_commodities() {
+ TRACE_DTOR(report_commodities);
+ }
+
+ virtual void flush();
+ virtual void operator()(post_t& post);
+
+ virtual void clear() {
+ commodities.clear();
+ item_handler<post_t>::clear();
+ }
};
} // namespace ledger
diff --git a/src/parser.cc b/src/parser.cc
index e8e987cb..f52949ce 100644
--- a/src/parser.cc
+++ b/src/parser.cc
@@ -79,9 +79,7 @@ expr_t::parser_t::parse_value_term(std::istream& in,
case token_t::LPAREN:
node = parse_value_expr(in, tflags.plus_flags(PARSE_PARTIAL)
.minus_flags(PARSE_SINGLE));
- tok = next_token(in, tflags);
- if (tok.kind != token_t::RPAREN)
- tok.expected(')');
+ tok = next_token(in, tflags, ')');
if (node->kind == op_t::O_CONS) {
ptr_op_t prev(node);
@@ -383,10 +381,7 @@ expr_t::parser_t::parse_querycolon_expr(std::istream& in,
throw_(parse_error,
_("%1 operator not followed by argument") << tok.symbol);
- token_t& next_tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT));
- if (next_tok.kind != token_t::COLON)
- next_tok.expected(':');
-
+ next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT), ':');
prev = node->right();
ptr_op_t subnode = new op_t(op_t::O_COLON);
subnode->set_left(prev);
diff --git a/src/parser.h b/src/parser.h
index 5eba4ffd..aab48830 100644
--- a/src/parser.h
+++ b/src/parser.h
@@ -52,11 +52,12 @@ class expr_t::parser_t : public noncopyable
mutable token_t lookahead;
mutable bool use_lookahead;
- token_t& next_token(std::istream& in, const parse_flags_t& tflags) const {
+ token_t& next_token(std::istream& in, const parse_flags_t& tflags,
+ const char expecting = '\0') const {
if (use_lookahead)
use_lookahead = false;
else
- lookahead.next(in, tflags);
+ lookahead.next(in, tflags, expecting);
return lookahead;
}
diff --git a/src/post.cc b/src/post.cc
index 183fb901..7dc15830 100644
--- a/src/post.cc
+++ b/src/post.cc
@@ -142,11 +142,15 @@ namespace {
return value_t(static_cast<scope_t *>(post.xact));
}
+ value_t get_xact_id(post_t& post) {
+ return static_cast<long>(post.xact_id());
+ }
+
value_t get_code(post_t& post) {
if (post.xact->code)
return string_value(*post.xact->code);
else
- return string_value(empty_string);
+ return NULL_VALUE;
}
value_t get_payee(post_t& post) {
@@ -154,9 +158,13 @@ namespace {
}
value_t get_note(post_t& post) {
- string note = post.note ? *post.note : empty_string;
- note += post.xact->note ? *post.xact->note : empty_string;
- return string_value(note);
+ if (post.note || post.xact->note) {
+ string note = post.note ? *post.note : empty_string;
+ note += post.xact->note ? *post.xact->note : empty_string;
+ return string_value(note);
+ } else {
+ return NULL_VALUE;
+ }
}
value_t get_magnitude(post_t& post) {
@@ -183,11 +191,21 @@ namespace {
}
value_t get_commodity(post_t& post) {
- return string_value(post.amount.commodity().symbol());
+ if (post.has_xdata() &&
+ post.xdata().has_flags(POST_EXT_COMPOUND))
+ return string_value(post.xdata().compound_value.to_amount()
+ .commodity().symbol());
+ else
+ return string_value(post.amount.commodity().symbol());
}
value_t get_commodity_is_primary(post_t& post) {
- return post.amount.commodity().has_flags(COMMODITY_PRIMARY);
+ if (post.has_xdata() &&
+ post.xdata().has_flags(POST_EXT_COMPOUND))
+ return post.xdata().compound_value.to_amount()
+ .commodity().has_flags(COMMODITY_PRIMARY);
+ else
+ return post.amount.commodity().has_flags(COMMODITY_PRIMARY);
}
value_t get_has_cost(post_t& post) {
@@ -222,7 +240,7 @@ namespace {
return 1L;
}
- value_t get_account(call_scope_t& scope)
+ value_t account_name(call_scope_t& scope)
{
in_context_t<post_t> env(scope, "&v");
@@ -265,14 +283,32 @@ namespace {
} else {
name = env->reported_account()->fullname();
}
+ return string_value(name);
+ }
- if (env->has_flags(POST_VIRTUAL)) {
- if (env->must_balance())
- name = string("[") + name + "]";
- else
- name = string("(") + name + ")";
+ value_t get_display_account(call_scope_t& scope)
+ {
+ in_context_t<post_t> env(scope, "&v");
+
+ value_t acct = account_name(scope);
+ if (acct.is_string()) {
+ if (env->has_flags(POST_VIRTUAL)) {
+ if (env->must_balance())
+ acct = string_value(string("[") + acct.as_string() + "]");
+ else
+ acct = string_value(string("(") + acct.as_string() + ")");
+ }
}
- return string_value(name);
+ return acct;
+ }
+
+ value_t get_account(call_scope_t& scope)
+ {
+ return account_name(scope);
+ }
+
+ value_t get_account_id(post_t& post) {
+ return static_cast<long>(post.account_id());
}
value_t get_account_base(post_t& post) {
@@ -351,6 +387,8 @@ expr_t::ptr_op_t post_t::lookup(const symbol_t::kind_t kind,
return WRAP_FUNCTOR(get_account);
else if (name == "account_base")
return WRAP_FUNCTOR(get_wrapper<&get_account_base>);
+ else if (name == "account_id")
+ return WRAP_FUNCTOR(get_wrapper<&get_account_id>);
else if (name == "any")
return WRAP_FUNCTOR(&fn_any);
else if (name == "all")
@@ -378,7 +416,9 @@ expr_t::ptr_op_t post_t::lookup(const symbol_t::kind_t kind,
break;
case 'd':
- if (name == "depth")
+ if (name == "display_account")
+ return WRAP_FUNCTOR(get_display_account);
+ else if (name == "depth")
return WRAP_FUNCTOR(get_wrapper<&get_account_depth>);
else if (name == "datetime")
return WRAP_FUNCTOR(get_wrapper<&get_datetime>);
@@ -444,6 +484,8 @@ expr_t::ptr_op_t post_t::lookup(const symbol_t::kind_t kind,
case 'x':
if (name == "xact")
return WRAP_FUNCTOR(get_wrapper<&get_xact>);
+ else if (name == "xact_id")
+ return WRAP_FUNCTOR(get_wrapper<&get_xact_id>);
break;
case 'N':
@@ -479,6 +521,30 @@ amount_t post_t::resolve_expr(scope_t& scope, expr_t& expr)
}
}
+std::size_t post_t::xact_id() const
+{
+ std::size_t id = 1;
+ foreach (post_t * p, xact->posts) {
+ if (p == this)
+ return id;
+ id++;
+ }
+ assert(! "Failed to find posting within its transaction");
+ return 0;
+}
+
+std::size_t post_t::account_id() const
+{
+ std::size_t id = 1;
+ foreach (post_t * p, account->posts) {
+ if (p == this)
+ return id;
+ id++;
+ }
+ assert(! "Failed to find posting within its transaction");
+ return 0;
+}
+
bool post_t::valid() const
{
if (! xact) {
diff --git a/src/post.h b/src/post.h
index 272cd9d8..226d6289 100644
--- a/src/post.h
+++ b/src/post.h
@@ -118,6 +118,9 @@ public:
amount_t resolve_expr(scope_t& scope, expr_t& expr);
+ std::size_t xact_id() const;
+ std::size_t account_id() const;
+
bool valid() const;
struct xdata_t : public supports_flags<uint_least16_t>
diff --git a/src/print.cc b/src/print.cc
index 34f5af51..a8aa5872 100644
--- a/src/print.cc
+++ b/src/print.cc
@@ -42,15 +42,20 @@ namespace ledger {
print_xacts::print_xacts(report_t& _report,
bool _print_raw)
- : report(_report), print_raw(_print_raw)
+ : report(_report), print_raw(_print_raw), first_title(true)
{
TRACE_CTOR(print_xacts, "report&, bool");
}
namespace {
- void print_note(std::ostream& out, const string& note)
+ void print_note(std::ostream& out,
+ const string& note,
+ const std::size_t columns,
+ const std::size_t prior_width)
{
- if (note.length() > 15)
+ // The 4 is for four leading spaces at the beginning of the posting, and
+ // the 3 is for two spaces and a semi-colon before the note.
+ if (columns > 0 && note.length() > columns - (prior_width + 3))
out << "\n ;";
else
out << " ;";
@@ -71,23 +76,40 @@ namespace {
void print_xact(report_t& report, std::ostream& out, xact_t& xact)
{
- out << format_date(item_t::use_effective_date ?
+ format_type_t format_type = FMT_WRITTEN;
+ optional<const char *> format;
+
+ if (report.HANDLED(date_format_)) {
+ format_type = FMT_CUSTOM;
+ format = report.HANDLER(date_format_).str().c_str();
+ }
+
+ std::ostringstream buf;
+
+ buf << format_date(item_t::use_effective_date ?
xact.date() : xact.actual_date(),
- FMT_WRITTEN);
+ format_type, format);
if (! item_t::use_effective_date && xact.effective_date())
- out << '=' << format_date(*xact.effective_date(), FMT_WRITTEN);
- out << ' ';
+ buf << '=' << format_date(*xact.effective_date(),
+ format_type, format);
+ buf << ' ';
- out << (xact.state() == item_t::CLEARED ? "* " :
+ buf << (xact.state() == item_t::CLEARED ? "* " :
(xact.state() == item_t::PENDING ? "! " : ""));
if (xact.code)
- out << '(' << *xact.code << ") ";
+ buf << '(' << *xact.code << ") ";
+
+ buf << xact.payee;
- out << xact.payee;
+ string leader = buf.str();
+ out << leader;
+
+ std::size_t columns = (report.HANDLED(columns_) ?
+ report.HANDLER(columns_).value.to_long() : 80);
if (xact.note)
- print_note(out, *xact.note);
+ print_note(out, *xact.note, columns, unistring(leader).length());
out << '\n';
if (xact.metadata) {
@@ -132,20 +154,37 @@ namespace {
buf << ')';
}
- if (! post->has_flags(POST_CALCULATED) || report.HANDLED(print_virtual)) {
- unistring name(buf.str());
+ unistring name(buf.str());
+
+ std::size_t account_width =
+ (report.HANDLER(account_width_).specified ?
+ report.HANDLER(account_width_).value.to_long() : 36);
+ if (account_width < name.length())
+ account_width = name.length();
+
+ if (! post->has_flags(POST_CALCULATED) || report.HANDLED(print_virtual)) {
out << name.extract();
- int slip = 36 - static_cast<int>(name.length());
- if (slip > 0)
- out << string(slip, ' ');
+ int slip = (static_cast<int>(account_width) -
+ static_cast<int>(name.length()));
+ if (slip > 0) {
+ out.width(slip);
+ out << ' ';
+ }
+
+ std::ostringstream amtbuf;
string amt;
if (post->amount_expr) {
amt = post->amount_expr->text();
} else {
+ std::size_t amount_width =
+ (report.HANDLER(amount_width_).specified ?
+ report.HANDLER(amount_width_).value.to_long() : 12);
+
std::ostringstream amt_str;
- report.scrub(post->amount).print(amt_str, 12, -1, true);
+ report.scrub(post->amount)
+ .print(amt_str, static_cast<int>(amount_width), -1, true);
amt = amt_str.str();
}
@@ -154,29 +193,44 @@ namespace {
int amt_slip = (static_cast<int>(amt.length()) -
static_cast<int>(trimmed_amt.length()));
if (slip + amt_slip < 2)
- out << string(2 - (slip + amt_slip), ' ');
- out << amt;
+ amtbuf << string(2 - (slip + amt_slip), ' ');
+ amtbuf << amt;
if (post->cost && ! post->has_flags(POST_CALCULATED)) {
if (post->has_flags(POST_COST_IN_FULL))
- out << " @@ " << report.scrub(post->cost->abs());
+ amtbuf << " @@ " << report.scrub(post->cost->abs());
else
- out << " @ " << report.scrub((*post->cost / post->amount).abs());
+ amtbuf << " @ " << report.scrub((*post->cost / post->amount).abs());
}
if (post->assigned_amount)
- out << " = " << report.scrub(*post->assigned_amount);
+ amtbuf << " = " << report.scrub(*post->assigned_amount);
+
+ string trailer = amtbuf.str();
+ out << trailer;
+
+ account_width += unistring(trailer).length();
} else {
out << buf.str();
}
if (post->note)
- print_note(out, *post->note);
+ print_note(out, *post->note, columns, 4 + account_width);
out << '\n';
}
}
}
+void print_xacts::title(const string&)
+{
+ if (first_title) {
+ first_title = false;
+ } else {
+ std::ostream& out(report.output_stream);
+ out << '\n';
+ }
+}
+
void print_xacts::flush()
{
std::ostream& out(report.output_stream);
diff --git a/src/print.h b/src/print.h
index f323b153..5263ec91 100644
--- a/src/print.h
+++ b/src/print.h
@@ -62,6 +62,7 @@ protected:
xacts_present_map xacts_present;
xacts_list xacts;
bool print_raw;
+ bool first_title;
public:
print_xacts(report_t& _report, bool _print_raw = false);
@@ -69,8 +70,17 @@ public:
TRACE_DTOR(print_xacts);
}
+ virtual void title(const string&);
+
virtual void flush();
virtual void operator()(post_t& post);
+
+ virtual void clear() {
+ xacts_present.clear();
+ xacts.clear();
+
+ item_handler<post_t>::clear();
+ }
};
diff --git a/src/py_account.cc b/src/py_account.cc
index 3114cc0b..2b860a24 100644
--- a/src/py_account.cc
+++ b/src/py_account.cc
@@ -207,11 +207,11 @@ void export_account()
.def("__len__", accounts_len)
.def("__getitem__", accounts_getitem, return_internal_reference<>())
- .def("__iter__", range<return_internal_reference<> >
+ .def("__iter__", python::range<return_internal_reference<> >
(&account_t::accounts_begin, &account_t::accounts_end))
- .def("accounts", range<return_internal_reference<> >
+ .def("accounts", python::range<return_internal_reference<> >
(&account_t::accounts_begin, &account_t::accounts_end))
- .def("posts", range<return_internal_reference<> >
+ .def("posts", python::range<return_internal_reference<> >
(&account_t::posts_begin, &account_t::posts_end))
.def("has_xdata", &account_t::has_xdata)
diff --git a/src/py_commodity.cc b/src/py_commodity.cc
index 22a4f153..fc7e8c3e 100644
--- a/src/py_commodity.cc
+++ b/src/py_commodity.cc
@@ -295,13 +295,16 @@ void export_commodity()
.def("keys", py_pool_keys)
.def("has_key", py_pool_contains)
.def("__contains__", py_pool_contains)
- .def("__iter__", range<return_value_policy<reference_existing_object> >
+ .def("__iter__",
+ python::range<return_value_policy<reference_existing_object> >
(py_pool_commodities_begin, py_pool_commodities_end))
- .def("iteritems", range<return_value_policy<reference_existing_object> >
+ .def("iteritems",
+ python::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> >
+ .def("iterkeys", python::range<>(py_pool_commodities_keys_begin,
+ py_pool_commodities_keys_end))
+ .def("itervalues",
+ python::range<return_value_policy<reference_existing_object> >
(py_pool_commodities_values_begin, py_pool_commodities_values_end))
;
@@ -309,16 +312,16 @@ void export_commodity()
scope().attr("commodities") = commodity_pool_t::current_pool;
- scope().attr("COMMODITY_STYLE_DEFAULTS") = COMMODITY_STYLE_DEFAULTS;
- scope().attr("COMMODITY_STYLE_SUFFIXED") = COMMODITY_STYLE_SUFFIXED;
- scope().attr("COMMODITY_STYLE_SEPARATED") = COMMODITY_STYLE_SEPARATED;
- scope().attr("COMMODITY_STYLE_EUROPEAN") = COMMODITY_STYLE_EUROPEAN;
- scope().attr("COMMODITY_STYLE_THOUSANDS") = COMMODITY_STYLE_THOUSANDS;
- scope().attr("COMMODITY_NOMARKET") = COMMODITY_NOMARKET;
- scope().attr("COMMODITY_BUILTIN") = COMMODITY_BUILTIN;
- scope().attr("COMMODITY_WALKED") = COMMODITY_WALKED;
- scope().attr("COMMODITY_KNOWN") = COMMODITY_KNOWN;
- scope().attr("COMMODITY_PRIMARY") = COMMODITY_PRIMARY;
+ scope().attr("COMMODITY_STYLE_DEFAULTS") = COMMODITY_STYLE_DEFAULTS;
+ scope().attr("COMMODITY_STYLE_SUFFIXED") = COMMODITY_STYLE_SUFFIXED;
+ scope().attr("COMMODITY_STYLE_SEPARATED") = COMMODITY_STYLE_SEPARATED;
+ scope().attr("COMMODITY_STYLE_DECIMAL_COMMA") = COMMODITY_STYLE_DECIMAL_COMMA;
+ scope().attr("COMMODITY_STYLE_THOUSANDS") = COMMODITY_STYLE_THOUSANDS;
+ scope().attr("COMMODITY_NOMARKET") = COMMODITY_NOMARKET;
+ scope().attr("COMMODITY_BUILTIN") = COMMODITY_BUILTIN;
+ scope().attr("COMMODITY_WALKED") = COMMODITY_WALKED;
+ scope().attr("COMMODITY_KNOWN") = COMMODITY_KNOWN;
+ scope().attr("COMMODITY_PRIMARY") = COMMODITY_PRIMARY;
class_< commodity_t, boost::noncopyable > ("Commodity", no_init)
#if 1
@@ -331,9 +334,9 @@ void export_commodity()
.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))
+ .add_static_property("decimal_comma_by_default",
+ make_getter(&commodity_t::decimal_comma_by_default),
+ make_setter(&commodity_t::decimal_comma_by_default))
.def("__str__", &commodity_t::symbol)
.def("__unicode__", py_commodity_unicode)
diff --git a/src/py_journal.cc b/src/py_journal.cc
index 81ce290d..1848adc4 100644
--- a/src/py_journal.cc
+++ b/src/py_journal.cc
@@ -226,8 +226,8 @@ void export_journal()
class_< collect_posts, bases<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> > >
+ .def("__iter__", python::range<return_internal_reference<1,
+ with_custodian_and_ward_postcall<1, 0> > >
(&collect_posts::begin, &collect_posts::end))
;
@@ -236,8 +236,9 @@ void export_journal()
.def("__len__", &collector_wrapper::length)
.def("__getitem__", posts_getitem, return_internal_reference<1,
with_custodian_and_ward_postcall<0, 1> >())
- .def("__iter__", range<return_value_policy<reference_existing_object,
- with_custodian_and_ward_postcall<0, 1> > >
+ .def("__iter__",
+ python::range<return_value_policy<reference_existing_object,
+ with_custodian_and_ward_postcall<0, 1> > >
(&collector_wrapper::begin, &collector_wrapper::end))
;
@@ -296,15 +297,15 @@ void export_journal()
with_custodian_and_ward_postcall<0, 1> >())
#endif
- .def("__iter__", range<return_internal_reference<> >
+ .def("__iter__", python::range<return_internal_reference<> >
(&journal_t::xacts_begin, &journal_t::xacts_end))
- .def("xacts", range<return_internal_reference<> >
+ .def("xacts", python::range<return_internal_reference<> >
(&journal_t::xacts_begin, &journal_t::xacts_end))
- .def("auto_xacts", range<return_internal_reference<> >
+ .def("auto_xacts", python::range<return_internal_reference<> >
(&journal_t::auto_xacts_begin, &journal_t::auto_xacts_end))
- .def("period_xacts", range<return_internal_reference<> >
+ .def("period_xacts", python::range<return_internal_reference<> >
(&journal_t::period_xacts_begin, &journal_t::period_xacts_end))
- .def("sources", range<return_internal_reference<> >
+ .def("sources", python::range<return_internal_reference<> >
(&journal_t::sources_begin, &journal_t::sources_end))
.def("read", py_read)
diff --git a/src/py_xact.cc b/src/py_xact.cc
index c4e1fd32..6553a67f 100644
--- a/src/py_xact.cc
+++ b/src/py_xact.cc
@@ -98,9 +98,9 @@ void export_xact()
.def("finalize", &xact_base_t::finalize)
- .def("__iter__", range<return_internal_reference<> >
+ .def("__iter__", python::range<return_internal_reference<> >
(&xact_t::posts_begin, &xact_t::posts_end))
- .def("posts", range<return_internal_reference<> >
+ .def("posts", python::range<return_internal_reference<> >
(&xact_t::posts_begin, &xact_t::posts_end))
.def("valid", &xact_base_t::valid)
diff --git a/src/query.cc b/src/query.cc
index 1f086df8..363c6f73 100644
--- a/src/query.cc
+++ b/src/query.cc
@@ -53,45 +53,51 @@ query_t::lexer_t::token_t query_t::lexer_t::next_token()
}
}
- 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 '\'':
+ case '"':
case '/': {
string pat;
- bool found_end_slash = false;
+ char closing = *arg_i;
+ bool found_closing = false;
for (++arg_i; arg_i != arg_end; ++arg_i) {
if (*arg_i == '\\') {
if (++arg_i == arg_end)
throw_(parse_error, _("Unexpected '\\' at end of pattern"));
}
- else if (*arg_i == '/') {
+ else if (*arg_i == closing) {
++arg_i;
- found_end_slash = true;
+ found_closing = true;
break;
}
pat.push_back(*arg_i);
}
- if (! found_end_slash)
- throw_(parse_error, _("Expected '/' at end of pattern"));
+ if (! found_closing)
+ throw_(parse_error, _("Expected '%1' at end of pattern") << closing);
if (pat.empty())
throw_(parse_error, _("Match pattern is empty"));
return token_t(token_t::TERM, pat);
}
+ }
+
+ if (multiple_args && consume_next_arg) {
+ consume_next_arg = false;
+ token_t tok(token_t::TERM, string(arg_i, arg_end));
+ arg_i = arg_end;
+ return tok;
+ }
+
+ resume:
+ bool consume_next = false;
+ switch (*arg_i) {
+ case ' ':
+ case '\t':
+ case '\r':
+ case '\n':
+ if (++arg_i == arg_end)
+ return next_token();
+ goto resume;
case '(': ++arg_i; return token_t(token_t::LPAREN);
case ')': ++arg_i; return token_t(token_t::RPAREN);
@@ -101,7 +107,10 @@ query_t::lexer_t::token_t query_t::lexer_t::next_token()
case '@': ++arg_i; return token_t(token_t::TOK_PAYEE);
case '#': ++arg_i; return token_t(token_t::TOK_CODE);
case '%': ++arg_i; return token_t(token_t::TOK_META);
- case '=': ++arg_i; return token_t(token_t::TOK_EQ);
+ case '=':
+ ++arg_i;
+ consume_next_arg = true;
+ return token_t(token_t::TOK_EQ);
case '\\':
consume_next = true;
diff --git a/src/query.h b/src/query.h
index 2b0bc75d..59adfd72 100644
--- a/src/query.h
+++ b/src/query.h
@@ -62,6 +62,7 @@ public:
bool consume_whitespace;
bool consume_next_arg;
+ bool multiple_args;
public:
struct token_t
@@ -177,10 +178,11 @@ public:
token_t token_cache;
lexer_t(value_t::sequence_t::const_iterator _begin,
- value_t::sequence_t::const_iterator _end)
+ value_t::sequence_t::const_iterator _end,
+ bool _multiple_args = true)
: begin(_begin), end(_end),
- consume_whitespace(false),
- consume_next_arg(false)
+ consume_whitespace(false), consume_next_arg(false),
+ multiple_args(_multiple_args)
{
TRACE_CTOR(query_t::lexer_t, "");
assert(begin != end);
@@ -192,6 +194,7 @@ public:
arg_i(lexer.arg_i), arg_end(lexer.arg_end),
consume_whitespace(lexer.consume_whitespace),
consume_next_arg(lexer.consume_next_arg),
+ multiple_args(lexer.multiple_args),
token_cache(lexer.token_cache)
{
TRACE_CTOR(query_t::lexer_t, "copy");
@@ -227,8 +230,8 @@ protected:
expr_t::ptr_op_t parse_query_expr(lexer_t::token_t::kind_t tok_context);
public:
- parser_t(const value_t& _args)
- : args(_args), lexer(args.begin(), args.end()) {
+ parser_t(const value_t& _args, bool multiple_args = true)
+ : args(_args), lexer(args.begin(), args.end(), multiple_args) {
TRACE_CTOR(query_t::parser_t, "");
}
parser_t(const parser_t& parser)
@@ -261,28 +264,30 @@ public:
TRACE_CTOR(query_t, "copy");
}
query_t(const string& arg,
- const keep_details_t& _what_to_keep = keep_details_t())
+ const keep_details_t& _what_to_keep = keep_details_t(),
+ bool multiple_args = true)
: predicate_t(_what_to_keep) {
TRACE_CTOR(query_t, "string, keep_details_t");
if (! arg.empty()) {
value_t temp(string_value(arg));
- parse_args(temp.to_sequence());
+ parse_args(temp.to_sequence(), multiple_args);
}
}
query_t(const value_t& args,
- const keep_details_t& _what_to_keep = keep_details_t())
+ const keep_details_t& _what_to_keep = keep_details_t(),
+ bool multiple_args = true)
: predicate_t(_what_to_keep) {
TRACE_CTOR(query_t, "value_t, keep_details_t");
if (! args.empty())
- parse_args(args);
+ parse_args(args, multiple_args);
}
virtual ~query_t() {
TRACE_DTOR(query_t);
}
- void parse_args(const value_t& args) {
+ void parse_args(const value_t& args, bool multiple_args = true) {
if (! parser)
- parser = parser_t(args);
+ parser = parser_t(args, multiple_args);
ptr = parser->parse(); // expr_t::ptr
}
diff --git a/src/report.cc b/src/report.cc
index 1180c019..90de9a3f 100644
--- a/src/report.cc
+++ b/src/report.cc
@@ -119,6 +119,8 @@ void report_t::normalize_options(const string& verb)
HANDLER(meta_).str() + "\"))");
}
}
+ if (! HANDLED(prepend_width_))
+ HANDLER(prepend_width_).on_with(string("?normalize"), static_cast<long>(0));
if (verb == "print" || verb == "xact" || verb == "dump") {
HANDLER(related).on_only(string("?normalize"));
@@ -143,9 +145,6 @@ void report_t::normalize_options(const string& verb)
// then ignore the period since the begin/end are the only interesting
// details.
if (HANDLED(period_)) {
- if (! HANDLED(sort_all_))
- HANDLER(sort_xacts_).on_only(string("?normalize"));
-
date_interval_t interval(HANDLER(period_).str());
optional<date_t> begin = interval.begin(session.current_year);
@@ -162,6 +161,8 @@ void report_t::normalize_options(const string& verb)
if (! interval.duration)
HANDLER(period_).off();
+ else if (! HANDLED(sort_all_))
+ HANDLER(sort_xacts_).on_only(string("?normalize"));
}
// If -j or -J were specified, set the appropriate format string now so as
@@ -273,77 +274,158 @@ void report_t::parse_query_args(const value_t& args, const string& whence)
}
}
+namespace {
+ struct posts_flusher
+ {
+ report_t& report;
+ post_handler_ptr handler;
+
+ posts_flusher(report_t& _report, post_handler_ptr _handler)
+ : report(_report), handler(_handler) {}
+
+ void operator()(const value_t&) {
+ report.session.journal->clear_xdata();
+ }
+ };
+}
+
void report_t::posts_report(post_handler_ptr handler)
{
+ handler = chain_post_handlers(*this, handler);
+ if (HANDLED(group_by_)) {
+ std::auto_ptr<post_splitter>
+ splitter(new post_splitter(*this, handler, HANDLER(group_by_).expr));
+ splitter->set_postflush_func(posts_flusher(*this, handler));
+ handler = post_handler_ptr(splitter.release());
+ }
+ handler = chain_pre_post_handlers(*this, handler);
+
journal_posts_iterator walker(*session.journal.get());
- pass_down_posts(chain_post_handlers(*this, handler), walker);
- session.journal->clear_xdata();
+ pass_down_posts(handler, walker);
+
+ if (! HANDLED(group_by_))
+ posts_flusher(*this, handler)(value_t());
}
void report_t::generate_report(post_handler_ptr handler)
{
HANDLER(limit_).on(string("#generate"), "actual");
+ handler = chain_handlers(*this, handler);
+
generate_posts_iterator walker
(session, HANDLED(seed_) ?
static_cast<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);
+ pass_down_posts(handler, walker);
}
void report_t::xact_report(post_handler_ptr handler, xact_t& xact)
{
+ handler = chain_handlers(*this, handler);
+
xact_posts_iterator walker(xact);
- pass_down_posts(chain_post_handlers(*this, handler), walker);
+ pass_down_posts(handler, walker);
+
xact.clear_xdata();
}
+namespace {
+ struct accounts_title_printer
+ {
+ report_t& report;
+ acct_handler_ptr handler;
+
+ accounts_title_printer(report_t& _report, acct_handler_ptr _handler)
+ : report(_report), handler(_handler) {}
+
+ void operator()(const value_t& val)
+ {
+ if (! report.HANDLED(no_titles)) {
+ std::ostringstream buf;
+ val.print(buf);
+ handler->title(buf.str());
+ }
+ }
+ };
+
+ struct accounts_flusher
+ {
+ report_t& report;
+ acct_handler_ptr handler;
+
+ accounts_flusher(report_t& _report, acct_handler_ptr _handler)
+ : report(_report), handler(_handler) {}
+
+ void operator()(const value_t&)
+ {
+ report.HANDLER(amount_).expr.mark_uncompiled();
+ report.HANDLER(total_).expr.mark_uncompiled();
+ report.HANDLER(display_amount_).expr.mark_uncompiled();
+ report.HANDLER(display_total_).expr.mark_uncompiled();
+ report.HANDLER(revalued_total_).expr.mark_uncompiled();
+
+ scoped_ptr<accounts_iterator> iter;
+ if (! report.HANDLED(sort_)) {
+ iter.reset(new basic_accounts_iterator(*report.session.journal->master));
+ } else {
+ expr_t sort_expr(report.HANDLER(sort_).str());
+ sort_expr.set_context(&report);
+ iter.reset(new sorted_accounts_iterator(*report.session.journal->master,
+ sort_expr, report.HANDLED(flat)));
+ }
+
+ if (report.HANDLED(display_)) {
+ DEBUG("report.predicate",
+ "Display predicate = " << report.HANDLER(display_).str());
+ pass_down_accounts(handler, *iter.get(),
+ predicate_t(report.HANDLER(display_).str(),
+ report.what_to_keep()),
+ report);
+ } else {
+ pass_down_accounts(handler, *iter.get());
+ }
+
+ report.session.journal->clear_xdata();
+ }
+ };
+}
+
void report_t::accounts_report(acct_handler_ptr handler)
{
- journal_posts_iterator walker(*session.journal.get());
-
- // The lifetime of the chain object controls the lifetime of all temporary
- // objects created within it during the call to pass_down_posts, which will
- // be needed later by the pass_down_accounts.
post_handler_ptr chain =
- chain_post_handlers(*this, post_handler_ptr(new ignore_posts), true);
- pass_down_posts(chain, walker);
+ chain_post_handlers(*this, post_handler_ptr(new ignore_posts),
+ /* for_accounts_report= */ true);
+ if (HANDLED(group_by_)) {
+ std::auto_ptr<post_splitter>
+ splitter(new post_splitter(*this, chain, HANDLER(group_by_).expr));
- HANDLER(amount_).expr.mark_uncompiled();
- HANDLER(total_).expr.mark_uncompiled();
- HANDLER(display_amount_).expr.mark_uncompiled();
- HANDLER(display_total_).expr.mark_uncompiled();
- HANDLER(revalued_total_).expr.mark_uncompiled();
+ splitter->set_preflush_func(accounts_title_printer(*this, handler));
+ splitter->set_postflush_func(accounts_flusher(*this, handler));
- scoped_ptr<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)));
+ chain = post_handler_ptr(splitter.release());
}
+ chain = chain_pre_post_handlers(*this, chain);
- if (HANDLED(display_)) {
- DEBUG("report.predicate",
- "Display predicate = " << HANDLER(display_).str());
- pass_down_accounts(handler, *iter.get(),
- predicate_t(HANDLER(display_).str(), what_to_keep()),
- *this);
- } else {
- pass_down_accounts(handler, *iter.get());
- }
+ // The lifetime of the chain object controls the lifetime of all temporary
+ // objects created within it during the call to pass_down_posts, which will
+ // be needed later by the pass_down_accounts.
+ journal_posts_iterator walker(*session.journal.get());
+ pass_down_posts(chain, walker);
- session.journal->clear_xdata();
+ if (! HANDLED(group_by_))
+ accounts_flusher(*this, handler)(value_t());
}
void report_t::commodities_report(post_handler_ptr handler)
{
+ handler = chain_handlers(*this, handler);
+
posts_commodities_iterator walker(*session.journal.get());
- pass_down_posts(chain_post_handlers(*this, handler), walker);
+ pass_down_posts(handler, walker);
+
session.journal->clear_xdata();
}
@@ -853,6 +935,7 @@ option_t<report_t> * report_t::lookup_option(const char * p)
else OPT(columns_);
else OPT_ALT(basis, cost);
else OPT_(current);
+ else OPT(count);
break;
case 'd':
OPT(daily);
@@ -886,6 +969,8 @@ option_t<report_t> * report_t::lookup_option(const char * p)
break;
case 'g':
OPT(gain);
+ else OPT(group_by_);
+ else OPT(group_title_format_);
break;
case 'h':
OPT(head_);
@@ -914,6 +999,8 @@ option_t<report_t> * report_t::lookup_option(const char * p)
case 'n':
OPT_CH(collapse);
else OPT(no_color);
+ else OPT(no_rounding);
+ else OPT(no_titles);
else OPT(no_total);
else OPT(now_);
break;
@@ -936,6 +1023,7 @@ option_t<report_t> * report_t::lookup_option(const char * p)
else OPT(pricedb_format_);
else OPT(payee_width_);
else OPT(prepend_format_);
+ else OPT(prepend_width_);
else OPT(print_virtual);
break;
case 'q':
@@ -1222,12 +1310,19 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
case symbol_t::COMMAND:
switch (*p) {
+ case 'a':
+ if (is_eq(p, "accounts"))
+ return WRAP_FUNCTOR(reporter<>(new report_accounts(*this), *this,
+ "#accounts"));
+ break;
+
case 'b':
if (*(p + 1) == '\0' || is_eq(p, "bal") || is_eq(p, "balance")) {
return expr_t::op_t::wrap_functor
(reporter<account_t, acct_handler_ptr, &report_t::accounts_report>
(new format_accounts(*this, report_format(HANDLER(balance_format_)),
- maybe_format(HANDLER(prepend_format_))),
+ maybe_format(HANDLER(prepend_format_)),
+ HANDLER(prepend_width_).value.to_long()),
*this, "#balance"));
}
else if (is_eq(p, "budget")) {
@@ -1240,7 +1335,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
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_))),
+ maybe_format(HANDLER(prepend_format_)),
+ HANDLER(prepend_width_).value.to_long()),
*this, "#budget"));
}
break;
@@ -1250,7 +1346,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
return WRAP_FUNCTOR
(reporter<>
(new format_posts(*this, report_format(HANDLER(csv_format_)),
- maybe_format(HANDLER(prepend_format_))),
+ maybe_format(HANDLER(prepend_format_)),
+ HANDLER(prepend_width_).value.to_long()),
*this, "#csv"));
}
else if (is_eq(p, "cleared")) {
@@ -1259,11 +1356,17 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
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_))),
+ maybe_format(HANDLER(prepend_format_)),
+ HANDLER(prepend_width_).value.to_long()),
*this, "#cleared"));
}
- else if (is_eq(p, "convert"))
+ else if (is_eq(p, "convert")) {
return WRAP_FUNCTOR(convert_command);
+ }
+ else if (is_eq(p, "commodities")) {
+ return WRAP_FUNCTOR(reporter<>(new report_commodities(*this), *this,
+ "#commodities"));
+ }
break;
case 'e':
@@ -1288,14 +1391,19 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
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_))),
+ maybe_format(HANDLER(prepend_format_)),
+ HANDLER(prepend_width_).value.to_long()),
*this, "#prices"));
else if (is_eq(p, "pricedb"))
return expr_t::op_t::wrap_functor
(reporter<post_t, post_handler_ptr, &report_t::commodities_report>
(new format_posts(*this, report_format(HANDLER(pricedb_format_)),
- maybe_format(HANDLER(prepend_format_))),
+ maybe_format(HANDLER(prepend_format_)),
+ HANDLER(prepend_width_).value.to_long()),
*this, "#pricedb"));
+ else if (is_eq(p, "payees"))
+ return WRAP_FUNCTOR(reporter<>(new report_payees(*this), *this,
+ "#payees"));
break;
case 'r':
@@ -1303,7 +1411,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
return WRAP_FUNCTOR
(reporter<>
(new format_posts(*this, report_format(HANDLER(register_format_)),
- maybe_format(HANDLER(prepend_format_))),
+ maybe_format(HANDLER(prepend_format_)),
+ HANDLER(prepend_width_).value.to_long()),
*this, "#register"));
else if (is_eq(p, "reload"))
return MAKE_FUNCTOR(report_t::reload_command);
diff --git a/src/report.h b/src/report.h
index 6b10dbcc..6fa238f0 100644
--- a/src/report.h
+++ b/src/report.h
@@ -256,6 +256,8 @@ public:
HANDLER(forecast_years_).report(out);
HANDLER(format_).report(out);
HANDLER(gain).report(out);
+ HANDLER(group_by_).report(out);
+ HANDLER(group_title_format_).report(out);
HANDLER(head_).report(out);
HANDLER(invert).report(out);
HANDLER(limit_).report(out);
@@ -267,6 +269,8 @@ public:
HANDLER(market).report(out);
HANDLER(meta_).report(out);
HANDLER(monthly).report(out);
+ HANDLER(no_rounding).report(out);
+ HANDLER(no_titles).report(out);
HANDLER(no_total).report(out);
HANDLER(now_).report(out);
HANDLER(only_).report(out);
@@ -280,6 +284,7 @@ public:
HANDLER(plot_amount_format_).report(out);
HANDLER(plot_total_format_).report(out);
HANDLER(prepend_format_).report(out);
+ HANDLER(prepend_width_).report(out);
HANDLER(price).report(out);
HANDLER(prices_format_).report(out);
HANDLER(pricedb_format_).report(out);
@@ -372,7 +377,7 @@ public:
OPTION__(report_t, balance_format_, CTOR(report_t, balance_format_) {
on(none,
- "%(justify(scrub(display_total), 20, -1, true, color))"
+ "%(justify(scrub(display_total), 20, 20 + prepend_width, true, color))"
" %(!options.flat ? depth_spacer : \"\")"
"%-(ansify_if(partial_account(options.flat), blue if color))\n%/"
"%$1\n%/"
@@ -432,8 +437,9 @@ public:
OPTION__(report_t, cleared_format_, CTOR(report_t, cleared_format_) {
on(none,
- "%(justify(scrub(get_at(total_expr, 0)), 16, -1, true, color))"
- " %(justify(scrub(get_at(total_expr, 1)), 16, -1, true, color))"
+ "%(justify(scrub(get_at(total_expr, 0)), 16, 16 + prepend_width, "
+ " true, color)) %(justify(scrub(get_at(total_expr, 1)), 18, "
+ " 36 + prepend_width, true, color))"
" %(latest_cleared ? format_date(latest_cleared) : \" \")"
" %(!options.flat ? depth_spacer : \"\")"
"%-(ansify_if(partial_account(options.flat), blue if color))\n%/"
@@ -454,15 +460,17 @@ public:
});
OPTION(report_t, columns_);
+ OPTION(report_t, count);
OPTION__(report_t, csv_format_, CTOR(report_t, csv_format_) {
on(none,
"%(quoted(date)),"
+ "%(quoted(code)),"
"%(quoted(payee)),"
- "%(quoted(account)),"
- "%(quoted(scrub(display_amount))),"
+ "%(quoted(display_account)),"
+ "%(quoted(commodity)),"
+ "%(quoted(quantity(scrub(display_amount)))),"
"%(quoted(cleared ? \"*\" : (pending ? \"!\" : \"\"))),"
- "%(quoted(code)),"
"%(quoted(join(note | xact.note)))\n");
});
@@ -590,6 +598,22 @@ public:
" - get_at(total_expr, 1)");
});
+ OPTION__
+ (report_t, group_by_,
+ expr_t expr;
+ CTOR(report_t, group_by_) {}
+ void set_expr(const optional<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, group_title_format_, CTOR(report_t, group_title_format_) {
+ on(none, "%(value)\n");
+ });
+
OPTION(report_t, head_);
OPTION_(report_t, invert, DO() {
@@ -632,6 +656,8 @@ public:
parent->HANDLER(color).off();
});
+ OPTION(report_t, no_rounding);
+ OPTION(report_t, no_titles);
OPTION(report_t, no_total);
OPTION_(report_t, now_, DO_(args) {
@@ -735,6 +761,9 @@ public:
});
OPTION(report_t, prepend_format_);
+ OPTION_(report_t, prepend_width_, DO_(args) {
+ value = args[1].to_long();
+ });
OPTION_(report_t, price, DO() { // -I
parent->HANDLER(display_amount_)
@@ -745,13 +774,13 @@ public:
OPTION__(report_t, prices_format_, CTOR(report_t, prices_format_) {
on(none,
- "%(date) %-8(account) %(justify(scrub(display_amount), 12, "
+ "%(date) %-8(display_account) %(justify(scrub(display_amount), 12, "
" 2 + 9 + 8 + 12, true, color))\n");
});
OPTION__(report_t, pricedb_format_, CTOR(report_t, pricedb_format_) {
on(none,
- "P %(datetime) %(account) %(scrub(display_amount))\n");
+ "P %(datetime) %(display_account) %(scrub(display_amount))\n");
});
OPTION(report_t, print_virtual);
@@ -778,14 +807,14 @@ public:
" if color & date > today))"
" %(ansify_if(justify(truncated(payee, payee_width), payee_width), "
" bold if color & !cleared & actual))"
- " %(ansify_if(justify(truncated(account, account_width, abbrev_len), "
- " account_width), blue if color))"
+ " %(ansify_if(justify(truncated(display_account, account_width, "
+ " abbrev_len), account_width), blue if color))"
" %(justify(scrub(display_amount), amount_width, "
" 3 + meta_width + date_width + payee_width + account_width"
- " + amount_width, true, color))"
+ " + amount_width + prepend_width, true, color))"
" %(justify(scrub(display_total), total_width, "
" 4 + meta_width + date_width + payee_width + account_width"
- " + amount_width + total_width, true, color))\n%/"
+ " + amount_width + total_width + prepend_width, true, color))\n%/"
"%(justify(\" \", 2 + date_width + payee_width))"
"%$3 %$4 %$5\n");
});
diff --git a/src/scope.h b/src/scope.h
index 30ba6823..1e6f24a1 100644
--- a/src/scope.h
+++ b/src/scope.h
@@ -354,6 +354,30 @@ inline T& find_scope(child_scope_t& scope, bool skip_this = true)
return reinterpret_cast<T&>(scope); // never executed
}
+class value_scope_t : public scope_t
+{
+ value_t value;
+
+ value_t get_value(call_scope_t&) {
+ return value;
+ }
+
+public:
+ value_scope_t(const value_t& _value) : value(_value) {}
+
+ virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind,
+ const string& name)
+ {
+ if (kind != symbol_t::FUNCTION)
+ return NULL;
+
+ if (name == "value")
+ return MAKE_FUNCTOR(value_scope_t::get_value);
+
+ return NULL;
+ }
+};
+
} // namespace ledger
#endif // _SCOPE_H
diff --git a/src/session.cc b/src/session.cc
index 8e5536b0..f8befde4 100644
--- a/src/session.cc
+++ b/src/session.cc
@@ -196,9 +196,7 @@ option_t<session_t> * session_t::lookup_option(const char * p)
break;
case 'd':
OPT(download); // -Q
- break;
- case 'e':
- OPT(european);
+ else OPT(decimal_comma);
break;
case 'f':
OPT_(file_); // -f
diff --git a/src/session.h b/src/session.h
index de1771ad..10f636bb 100644
--- a/src/session.h
+++ b/src/session.h
@@ -80,7 +80,7 @@ public:
{
HANDLER(cache_).report(out);
HANDLER(download).report(out);
- HANDLER(european).report(out);
+ HANDLER(decimal_comma).report(out);
HANDLER(file_).report(out);
HANDLER(input_date_format_).report(out);
HANDLER(master_account_).report(out);
@@ -101,8 +101,8 @@ public:
OPTION(session_t, cache_);
OPTION(session_t, download); // -Q
- OPTION_(session_t, european, DO() {
- commodity_t::european_by_default = true;
+ OPTION_(session_t, decimal_comma, DO() {
+ commodity_t::decimal_comma_by_default = true;
});
OPTION__
diff --git a/src/temps.cc b/src/temps.cc
index 68b9ffa0..7a630176 100644
--- a/src/temps.cc
+++ b/src/temps.cc
@@ -38,26 +38,6 @@
namespace ledger {
-temporaries_t::~temporaries_t()
-{
- if (post_temps) {
- foreach (post_t& post, *post_temps) {
- if (! post.xact->has_flags(ITEM_TEMP))
- post.xact->remove_post(&post);
-
- if (post.account && ! post.account->has_flags(ACCOUNT_TEMP))
- post.account->remove_post(&post);
- }
- }
-
- if (acct_temps) {
- foreach (account_t& acct, *acct_temps) {
- if (acct.parent && ! acct.parent->has_flags(ACCOUNT_TEMP))
- acct.parent->remove_account(&acct);
- }
- }
-}
-
xact_t& temporaries_t::copy_xact(xact_t& origin)
{
if (! xact_temps)
@@ -91,9 +71,9 @@ post_t& temporaries_t::copy_post(post_t& origin, xact_t& xact,
post_temps->push_back(origin);
post_t& temp(post_temps->back());
+ temp.add_flags(ITEM_TEMP);
if (account)
temp.account = account;
- temp.add_flags(ITEM_TEMP);
temp.account->add_post(&temp);
xact.add_post(&temp);
@@ -109,8 +89,8 @@ post_t& temporaries_t::create_post(xact_t& xact, account_t * account)
post_temps->push_back(post_t(account));
post_t& temp(post_temps->back());
- temp.account = account;
temp.add_flags(ITEM_TEMP);
+ temp.account = account;
temp.account->add_post(&temp);
xact.add_post(&temp);
@@ -127,11 +107,36 @@ account_t& temporaries_t::create_account(const string& name,
acct_temps->push_back(account_t(parent, name));
account_t& temp(acct_temps->back());
+ temp.add_flags(ACCOUNT_TEMP);
if (parent)
parent->add_account(&temp);
- temp.add_flags(ACCOUNT_TEMP);
return temp;
}
+void temporaries_t::clear()
+{
+ if (post_temps) {
+ foreach (post_t& post, *post_temps) {
+ if (! post.xact->has_flags(ITEM_TEMP))
+ post.xact->remove_post(&post);
+
+ if (post.account && ! post.account->has_flags(ACCOUNT_TEMP))
+ post.account->remove_post(&post);
+ }
+ post_temps->clear();
+ }
+
+ if (xact_temps)
+ xact_temps->clear();
+
+ if (acct_temps) {
+ foreach (account_t& acct, *acct_temps) {
+ if (acct.parent && ! acct.parent->has_flags(ACCOUNT_TEMP))
+ acct.parent->remove_account(&acct);
+ }
+ acct_temps->clear();
+ }
+}
+
} // namespace ledger
diff --git a/src/temps.h b/src/temps.h
index ac6d08cd..210bbf63 100644
--- a/src/temps.h
+++ b/src/temps.h
@@ -51,7 +51,9 @@ class temporaries_t
optional<std::list<account_t> > acct_temps;
public:
- ~temporaries_t();
+ ~temporaries_t() {
+ clear();
+ }
xact_t& copy_xact(xact_t& origin);
xact_t& create_xact();
@@ -69,6 +71,8 @@ public:
account_t& last_account() {
return acct_temps->back();
}
+
+ void clear();
};
} // namespace ledger
diff --git a/src/textual.cc b/src/textual.cc
index dfca7943..9a49edd4 100644
--- a/src/textual.cc
+++ b/src/textual.cc
@@ -95,7 +95,6 @@ namespace {
public:
parse_context_t& context;
instance_t * parent;
- account_t * master;
accounts_map account_aliases;
const path * original_file;
path pathname;
@@ -109,7 +108,6 @@ namespace {
instance_t(parse_context_t& _context,
std::istream& _in,
- account_t * _master = NULL,
const path * _original_file = NULL,
instance_t * _parent = NULL);
@@ -200,30 +198,17 @@ namespace {
instance_t::instance_t(parse_context_t& _context,
std::istream& _in,
- account_t * _master,
const path * _original_file,
instance_t * _parent)
- : context(_context), parent(_parent), master(_master),
- original_file(_original_file), in(_in)
+ : context(_context), parent(_parent), original_file(_original_file),
+ pathname(original_file ? *original_file : "/dev/stdin"), in(_in)
{
TRACE_CTOR(instance_t, "...");
-
- if (! master)
- master = context.journal.master;
- context.state_stack.push_front(master);
-
- if (_original_file)
- pathname = *_original_file;
- else
- pathname = "/dev/stdin";
}
instance_t::~instance_t()
{
TRACE_DTOR(instance_t);
-
- assert(! context.state_stack.empty());
- context.state_stack.pop_front();
}
void instance_t::parse()
@@ -411,8 +396,7 @@ void instance_t::read_next_directive()
#if defined(TIMELOG_SUPPORT)
-void instance_t::clock_in_directive(char * line,
- bool /*capitalized*/)
+void instance_t::clock_in_directive(char * line, bool /*capitalized*/)
{
string datetime(line, 2, 19);
@@ -441,8 +425,7 @@ void instance_t::clock_in_directive(char * line,
context.timelog.clock_in(event);
}
-void instance_t::clock_out_directive(char * line,
- bool /*capitalized*/)
+void instance_t::clock_out_directive(char * line, bool /*capitalized*/)
{
string datetime(line, 2, 19);
@@ -543,7 +526,7 @@ void instance_t::automated_xact_directive(char * line)
std::auto_ptr<auto_xact_t> ae
(new auto_xact_t(query_t(string(skip_ws(line + 1)),
- keep_details_t(true, true, true))));
+ keep_details_t(true, true, true), false)));
ae->pos = position_t();
ae->pos->pathname = pathname;
ae->pos->beg_pos = line_beg_pos;
@@ -680,7 +663,12 @@ void instance_t::include_directive(char * line)
for (filesystem::directory_iterator iter(parent_path);
iter != end;
++iter) {
- if (is_regular_file(*iter)) {
+#if BOOST_VERSION <= 103500
+ if (is_regular(*iter))
+#else
+ if (is_regular_file(*iter))
+#endif
+ {
#if BOOST_VERSION >= 103700
string base = (*iter).filename();
#else // BOOST_VERSION >= 103700
@@ -689,7 +677,7 @@ void instance_t::include_directive(char * line)
if (glob.match(base)) {
path inner_file(*iter);
ifstream stream(inner_file);
- instance_t instance(context, stream, master, &inner_file, this);
+ instance_t instance(context, stream, &inner_file, this);
instance.parse();
files_found = true;
}
@@ -713,7 +701,7 @@ void instance_t::master_account_directive(char * line)
void instance_t::end_directive(char * kind)
{
- string name(kind);
+ string name(kind ? kind : "");
if ((name.empty() || name == "account") && ! context.front_is_account())
throw_(std::runtime_error,
@@ -1451,8 +1439,10 @@ std::size_t journal_t::parse(std::istream& in,
parse_context_t context(*this, scope);
context.strict = strict;
+ if (master || this->master)
+ context.state_stack.push_front(master ? master : this->master);
- instance_t instance(context, in, master, original_file);
+ instance_t instance(context, in, original_file);
instance.parse();
TRACE_STOP(parsing_total, 1);
diff --git a/src/times.cc b/src/times.cc
index be488baf..a7906aee 100644
--- a/src/times.cc
+++ b/src/times.cc
@@ -197,25 +197,33 @@ namespace {
optional_year year,
date_traits_t * traits = NULL)
{
- date_t when;
+ VERIFY(std::strlen(date_str) < 127);
- 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);
+ char buf[128];
+ std::strcpy(buf, date_str);
- for (char * p = buf; *p; p++)
- if (*p == '.' || *p == '-')
- *p = '/';
+ for (char * p = buf; *p; p++)
+ if (*p == '.' || *p == '-')
+ *p = '/';
- when = io.parse(buf);
- }
+ date_t when = io.parse(buf);
if (! when.is_not_a_date()) {
- DEBUG("times.parse", "Parsed date string: " << date_str);
- DEBUG("times.parse", "Parsed result is: " << when);
+ DEBUG("times.parse", "Passed date string: " << date_str);
+ DEBUG("times.parse", "Parsed date string: " << buf);
+ DEBUG("times.parse", "Parsed result is: " << when);
+ DEBUG("times.parse", "Formatted result is: " << io.format(when));
+
+ string when_str = io.format(when);
+
+ const char * p = when_str.c_str();
+ const char * q = buf;
+ for (; *p && *q; p++, q++) {
+ if (*p != *q && *p == '0') p++;
+ if (! *p || *p != *q) break;
+ }
+ if (*p != '\0' || *q != '\0')
+ throw_(date_error, _("Invalid date: %1") << date_str);
if (traits)
*traits = io.traits;
@@ -1299,14 +1307,14 @@ date_parser_t::lexer_t::token_t date_parser_t::lexer_t::next_token()
// "2009/08/01", but also dates that fit the user's --input-date-format,
// assuming their format fits in one argument and begins with a digit.
if (std::isdigit(*begin)) {
- try {
- string::const_iterator i = begin;
- for (i = begin; i != end && ! std::isspace(*i); i++) {}
- assert(i != begin);
+ string::const_iterator i = begin;
+ for (i = begin; i != end && ! std::isspace(*i); i++) {}
+ assert(i != begin);
- string possible_date(start, i);
- date_traits_t traits;
+ string possible_date(start, i);
+ try {
+ date_traits_t traits;
date_t when = parse_date_mask(possible_date.c_str(), none, &traits);
if (! when.is_not_a_date()) {
begin = i;
@@ -1314,7 +1322,12 @@ date_parser_t::lexer_t::token_t date_parser_t::lexer_t::next_token()
token_t::content_t(date_specifier_t(when, traits)));
}
}
- catch (...) {}
+ catch (date_error&) {
+ if (contains(possible_date, "/") ||
+ contains(possible_date, "-") ||
+ contains(possible_date, "."))
+ throw;
+ }
}
start = begin;
@@ -1457,7 +1470,7 @@ std::string format_datetime(const datetime_t& when,
if (format_type == FMT_WRITTEN) {
return written_datetime_io->format(when);
}
- else if (format_type == FMT_CUSTOM || format) {
+ else if (format_type == FMT_CUSTOM && format) {
datetime_io_map::iterator i = temp_datetime_io.find(*format);
if (i != temp_datetime_io.end()) {
return (*i).second->format(when);
@@ -1483,7 +1496,7 @@ std::string format_date(const date_t& when,
if (format_type == FMT_WRITTEN) {
return written_date_io->format(when);
}
- else if (format_type == FMT_CUSTOM || format) {
+ else if (format_type == FMT_CUSTOM && format) {
date_io_map::iterator i = temp_date_io.find(*format);
if (i != temp_date_io.end()) {
return (*i).second->format(when);
diff --git a/src/token.cc b/src/token.cc
index a34cdcd0..add97b8b 100644
--- a/src/token.cc
+++ b/src/token.cc
@@ -138,7 +138,8 @@ void expr_t::token_t::parse_ident(std::istream& in)
value.set_string(buf);
}
-void expr_t::token_t::next(std::istream& in, const parse_flags_t& pflags)
+void expr_t::token_t::next(std::istream& in, const parse_flags_t& pflags,
+ const char expecting)
{
if (in.eof()) {
kind = TOK_EOF;
@@ -423,6 +424,13 @@ void expr_t::token_t::next(std::istream& in, const parse_flags_t& pflags)
expected('\0', c);
parse_ident(in);
+
+ if (value.as_string().length() == 0) {
+ kind = ERROR;
+ symbol[0] = c;
+ symbol[1] = '\0';
+ unexpected(expecting);
+ }
} else {
kind = VALUE;
value = temp;
@@ -447,21 +455,38 @@ void expr_t::token_t::rewind(std::istream& in)
}
-void expr_t::token_t::unexpected()
+void expr_t::token_t::unexpected(const char wanted)
{
kind_t prev_kind = kind;
kind = ERROR;
- switch (prev_kind) {
- case TOK_EOF:
- throw_(parse_error, _("Unexpected end of expression"));
- case IDENT:
- throw_(parse_error, _("Unexpected symbol '%1'") << value);
- case VALUE:
- throw_(parse_error, _("Unexpected value '%1'") << value);
- default:
- throw_(parse_error, _("Unexpected token '%1'") << symbol);
+ if (wanted == '\0') {
+ switch (prev_kind) {
+ case TOK_EOF:
+ throw_(parse_error, _("Unexpected end of expression"));
+ case IDENT:
+ throw_(parse_error, _("Unexpected symbol '%1'") << value);
+ case VALUE:
+ throw_(parse_error, _("Unexpected value '%1'") << value);
+ default:
+ throw_(parse_error, _("Unexpected expression token '%1'") << symbol);
+ }
+ } else {
+ switch (prev_kind) {
+ case TOK_EOF:
+ throw_(parse_error,
+ _("Unexpected end of expression (wanted '%1')" << wanted));
+ case IDENT:
+ throw_(parse_error,
+ _("Unexpected symbol '%1' (wanted '%2')") << value << wanted);
+ case VALUE:
+ throw_(parse_error,
+ _("Unexpected value '%1' (wanted '%2')") << value << wanted);
+ default:
+ throw_(parse_error, _("Unexpected expression token '%1' (wanted '%2')")
+ << symbol << wanted);
+ }
}
}
diff --git a/src/token.h b/src/token.h
index 8d70996b..582373cb 100644
--- a/src/token.h
+++ b/src/token.h
@@ -124,10 +124,11 @@ struct expr_t::token_t : public noncopyable
int parse_reserved_word(std::istream& in);
void parse_ident(std::istream& in);
- void next(std::istream& in, const parse_flags_t& flags);
+ void next(std::istream& in, const parse_flags_t& flags,
+ const char expecting = '\0');
void rewind(std::istream& in);
- void unexpected();
- void expected(char wanted, char c = '\0');
+ void unexpected(const char wanted = '\0');
+ void expected(const char wanted, char c = '\0');
};
} // namespace ledger
diff --git a/src/value.cc b/src/value.cc
index ce9b0e6a..a967eeb8 100644
--- a/src/value.cc
+++ b/src/value.cc
@@ -832,7 +832,7 @@ bool value_t::is_equal_to(const value_t& val) const
break;
}
- throw_(value_error, _("Cannot compare %1 by %2") << label() << val.label());
+ throw_(value_error, _("Cannot compare %1 to %2") << label() << val.label());
return *this;
}
@@ -840,6 +840,23 @@ bool value_t::is_equal_to(const value_t& val) const
bool value_t::is_less_than(const value_t& val) const
{
switch (type()) {
+ case BOOLEAN:
+ if (val.is_boolean()) {
+ if (as_boolean()) {
+ if (! val.as_boolean())
+ return false;
+ else
+ return false;
+ }
+ else if (! as_boolean()) {
+ if (! val.as_boolean())
+ return false;
+ else
+ return true;
+ }
+ }
+ break;
+
case DATETIME:
if (val.is_datetime())
return as_datetime() < val.as_datetime();
@@ -935,6 +952,22 @@ bool value_t::is_less_than(const value_t& val) const
bool value_t::is_greater_than(const value_t& val) const
{
switch (type()) {
+ if (val.is_boolean()) {
+ if (as_boolean()) {
+ if (! val.as_boolean())
+ return true;
+ else
+ return false;
+ }
+ else if (! as_boolean()) {
+ if (! val.as_boolean())
+ return false;
+ else
+ return false;
+ }
+ }
+ break;
+
case DATETIME:
if (val.is_datetime())
return as_datetime() > val.as_datetime();
@@ -1042,8 +1075,27 @@ void value_t::in_place_cast(type_t cast_type)
}
switch (type()) {
+ case VOID:
+ switch (cast_type) {
+ case INTEGER:
+ set_long(0L);
+ return;
+ case AMOUNT:
+ set_amount(0L);
+ return;
+ case STRING:
+ set_string("");
+ return;
+ default:
+ break;
+ }
+ break;
+
case BOOLEAN:
switch (cast_type) {
+ case INTEGER:
+ set_long(as_boolean() ? 1L : 0L);
+ return;
case AMOUNT:
set_amount(as_boolean() ? 1L : 0L);
return;
diff --git a/src/xact.cc b/src/xact.cc
index 344f66ea..569e5869 100644
--- a/src/xact.cc
+++ b/src/xact.cc
@@ -172,44 +172,8 @@ bool xact_base_t::finalize()
add_post(null_post);
}
- if (null_post != NULL) {
- // If one post has no value at all, its value will become the inverse of
- // the rest. If multiple commodities are involved, multiple posts are
- // generated to balance them all.
-
- DEBUG("xact.finalize", "there was a null posting");
-
- if (balance.is_balance()) {
- bool first = true;
- const balance_t& bal(balance.as_balance());
- foreach (const balance_t::amounts_map::value_type& pair, bal.amounts) {
- if (first) {
- null_post->amount = pair.second.negated();
- null_post->add_flags(POST_CALCULATED);
- first = false;
- } else {
- post_t * p = new post_t(null_post->account, pair.second.negated(),
- ITEM_GENERATED | POST_CALCULATED);
- p->set_state(null_post->state());
- add_post(p);
- }
- }
- }
- else if (balance.is_amount()) {
- null_post->amount = balance.as_amount().negated();
- null_post->add_flags(POST_CALCULATED);
- }
- else if (balance.is_long()) {
- null_post->amount = amount_t(- balance.as_long());
- null_post->add_flags(POST_CALCULATED);
- }
- else if (! balance.is_null() && ! balance.is_realzero()) {
- throw_(balance_error, _("Transaction does not balance"));
- }
- balance = NULL_VALUE;
- }
- else if (balance.is_balance() &&
- balance.as_balance().amounts.size() == 2) {
+ if (balance.is_balance() &&
+ balance.as_balance().amounts.size() == 2) {
// When an xact involves two different commodities (regardless of how
// many posts there are) determine the conversion ratio by dividing the
// total value of one commodity by the total value of the other. This
@@ -293,12 +257,6 @@ bool xact_base_t::finalize()
}
}
- // Now that the post list has its final form, calculate the balance once
- // more in terms of total cost, accounting for any possible gain/loss
- // amounts.
-
- DEBUG("xact.finalize", "resolved balance = " << balance);
-
posts_list copy(posts);
foreach (post_t * post, copy) {
@@ -340,7 +298,44 @@ bool xact_base_t::finalize()
}
}
- DEBUG("xact.finalize", "final balance = " << balance);
+ if (null_post != NULL) {
+ // If one post has no value at all, its value will become the inverse of
+ // the rest. If multiple commodities are involved, multiple posts are
+ // generated to balance them all.
+
+ DEBUG("xact.finalize", "there was a null posting");
+
+ if (balance.is_balance()) {
+ bool first = true;
+ const balance_t& bal(balance.as_balance());
+ foreach (const balance_t::amounts_map::value_type& pair, bal.amounts) {
+ if (first) {
+ null_post->amount = pair.second.negated();
+ null_post->add_flags(POST_CALCULATED);
+ first = false;
+ } else {
+ post_t * p = new post_t(null_post->account, pair.second.negated(),
+ ITEM_GENERATED | POST_CALCULATED);
+ p->set_state(null_post->state());
+ add_post(p);
+ }
+ }
+ }
+ else if (balance.is_amount()) {
+ null_post->amount = balance.as_amount().negated();
+ null_post->add_flags(POST_CALCULATED);
+ }
+ else if (balance.is_long()) {
+ null_post->amount = amount_t(- balance.as_long());
+ null_post->add_flags(POST_CALCULATED);
+ }
+ else if (! balance.is_null() && ! balance.is_realzero()) {
+ throw_(balance_error, _("Transaction does not balance"));
+ }
+ balance = NULL_VALUE;
+
+ }
+ DEBUG("xact.finalize", "resolved balance = " << balance);
if (! balance.is_null() && ! balance.is_zero()) {
add_error_context(item_context(*this, _("While balancing transaction")));
@@ -473,7 +468,7 @@ namespace {
if (xact.code)
return string_value(*xact.code);
else
- return string_value(empty_string);
+ return NULL_VALUE;
}
value_t get_payee(xact_t& xact) {
@@ -716,8 +711,14 @@ void auto_xact_t::extend_xact(xact_base_t& xact)
account_t * account = post->account;
string fullname = account->fullname();
assert(! fullname.empty());
- if (fullname == "$account" || fullname == "@account")
- account = initial_post->account;
+
+ if (contains(fullname, "$account")) {
+ fullname = regex_replace(fullname, regex("\\$account\\>"),
+ initial_post->account->fullname());
+ while (account->parent)
+ account = account->parent;
+ account = account->find_account(fullname);
+ }
// Copy over details so that the resulting post is a mirror of
// the automated xact's one.
diff --git a/src/xml.h b/src/xml.h
index 320096f8..5d14dab3 100644
--- a/src/xml.h
+++ b/src/xml.h
@@ -83,6 +83,14 @@ public:
virtual void flush();
virtual void operator()(post_t& post);
+
+ virtual void clear() {
+ commodities.clear();
+ transactions_set.clear();
+ transactions.clear();
+
+ item_handler<post_t>::clear();
+ }
};
} // namespace ledger