summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/account.cc2
-rw-r--r--src/amount.cc13
-rw-r--r--src/annotate.cc50
-rw-r--r--src/chain.cc14
-rw-r--r--src/commodity.h23
-rw-r--r--src/convert.cc26
-rw-r--r--src/expr.cc5
-rw-r--r--src/filters.cc120
-rw-r--r--src/filters.h41
-rw-r--r--src/format.cc2
-rw-r--r--src/item.cc45
-rw-r--r--src/item.h22
-rw-r--r--src/output.cc7
-rw-r--r--src/pool.cc20
-rw-r--r--src/post.cc28
-rw-r--r--src/post.h20
-rw-r--r--src/precmd.cc19
-rw-r--r--src/predicate.cc6
-rw-r--r--src/predicate.h20
-rw-r--r--src/print.cc2
-rw-r--r--src/query.cc207
-rw-r--r--src/query.h108
-rw-r--r--src/report.cc99
-rw-r--r--src/report.h71
-rw-r--r--src/scope.h2
-rw-r--r--src/session.cc10
-rw-r--r--src/textual.cc14
-rw-r--r--src/times.cc209
-rw-r--r--src/times.h6
-rw-r--r--src/value.cc20
-rw-r--r--src/xact.cc123
31 files changed, 952 insertions, 402 deletions
diff --git a/src/account.cc b/src/account.cc
index ceeb1c12..3e0ad086 100644
--- a/src/account.cc
+++ b/src/account.cc
@@ -626,7 +626,7 @@ void account_t::xdata_t::details_t::update(post_t& post,
if (gather_all) {
accounts_referenced.insert(post.account->fullname());
- payees_referenced.insert(post.xact->payee);
+ payees_referenced.insert(post.payee());
}
}
diff --git a/src/amount.cc b/src/amount.cc
index 9817f464..1fbc96c8 100644
--- a/src/amount.cc
+++ b/src/amount.cc
@@ -743,21 +743,24 @@ amount_t::value(const optional<datetime_t>& moment,
optional<price_point_t> point;
optional<commodity_t&> comm(in_terms_of);
- if (comm && commodity().referent() == comm->referent()) {
- return *this;
- }
- else if (has_annotation() && annotation().price) {
+ if (has_annotation() && annotation().price) {
if (annotation().has_flags(ANNOTATION_PRICE_FIXATED)) {
point = price_point_t();
point->price = *annotation().price;
+ DEBUG("commodity.prices.find",
+ "amount_t::value: fixated price = " << point->price);
}
- else if (! in_terms_of) {
+ else if (! comm) {
comm = annotation().price->commodity();
}
}
if (! point) {
+ if (comm && commodity().referent() == comm->referent())
+ return *this;
+
point = commodity().find_price(comm, moment);
+
// Whether a price was found or not, check whether we should attempt
// to download a price from the Internet. This is done if (a) no
// price was found, or (b) the price is "stale" according to the
diff --git a/src/annotate.cc b/src/annotate.cc
index 08b73443..33c0aebb 100644
--- a/src/annotate.cc
+++ b/src/annotate.cc
@@ -166,15 +166,27 @@ annotated_commodity_t::strip_annotations(const keep_details_t& what_to_keep)
commodity_t * new_comm;
- bool keep_price = (what_to_keep.keep_price &&
- (! what_to_keep.only_actuals ||
- ! details.has_flags(ANNOTATION_PRICE_CALCULATED)));
- bool keep_date = (what_to_keep.keep_date &&
- (! what_to_keep.only_actuals ||
- ! details.has_flags(ANNOTATION_DATE_CALCULATED)));
- bool keep_tag = (what_to_keep.keep_tag &&
- (! what_to_keep.only_actuals ||
- ! details.has_flags(ANNOTATION_TAG_CALCULATED)));
+ bool keep_price =
+ ((what_to_keep.keep_price ||
+ (details.has_flags(ANNOTATION_PRICE_FIXATED) &&
+ has_flags(COMMODITY_SAW_ANN_PRICE_FLOAT) &&
+ has_flags(COMMODITY_SAW_ANN_PRICE_FIXATED))) &&
+ (! what_to_keep.only_actuals ||
+ ! details.has_flags(ANNOTATION_PRICE_CALCULATED)));
+ bool keep_date =
+ (what_to_keep.keep_date &&
+ (! what_to_keep.only_actuals ||
+ ! details.has_flags(ANNOTATION_DATE_CALCULATED)));
+ bool keep_tag =
+ (what_to_keep.keep_tag &&
+ (! what_to_keep.only_actuals ||
+ ! details.has_flags(ANNOTATION_TAG_CALCULATED)));
+
+ DEBUG("commodity.annotated.strip",
+ "Reducing commodity " << *this << std::endl
+ << " keep price " << keep_price << " "
+ << " keep date " << keep_date << " "
+ << " keep tag " << keep_tag);
if ((keep_price && details.price) ||
(keep_date && details.date) ||
@@ -184,12 +196,24 @@ annotated_commodity_t::strip_annotations(const keep_details_t& what_to_keep)
(referent(), annotation_t(keep_price ? details.price : none,
keep_date ? details.date : none,
keep_tag ? details.tag : none));
- } else {
- new_comm = &referent();
+
+ // Transfer over any relevant annotation flags, as they still apply.
+ if (new_comm->annotated) {
+ annotation_t& new_details(as_annotated_commodity(*new_comm).details);
+ if (keep_price)
+ new_details.add_flags(details.flags() &
+ (ANNOTATION_PRICE_CALCULATED |
+ ANNOTATION_PRICE_FIXATED));
+ if (keep_date)
+ new_details.add_flags(details.flags() & ANNOTATION_DATE_CALCULATED);
+ if (keep_tag)
+ new_details.add_flags(details.flags() & ANNOTATION_TAG_CALCULATED);
+ }
+
+ return *new_comm;
}
- assert(new_comm);
- return *new_comm;
+ return referent();
}
void annotated_commodity_t::write_annotations
diff --git a/src/chain.cc b/src/chain.cc
index 64550663..67f2c8d5 100644
--- a/src/chain.cc
+++ b/src/chain.cc
@@ -67,8 +67,8 @@ post_handler_ptr chain_pre_post_handlers(post_handler_ptr base_handler,
// future balance.
if (report.budget_flags != BUDGET_NO_BUDGET) {
- budget_posts * budget_handler = new budget_posts(handler,
- report.budget_flags);
+ budget_posts * budget_handler =
+ new budget_posts(handler, report.terminus.date(), report.budget_flags);
budget_handler->add_period_xacts(report.session.journal->period_xacts);
handler.reset(budget_handler);
@@ -202,8 +202,8 @@ post_handler_ptr chain_post_handlers(post_handler_ptr base_handler,
// period_posts is like subtotal_posts, but it subtotals according to time
// periods rather than totalling everything.
//
- // dow_posts is like period_posts, except that it reports all the posts
- // that fall on each subsequent day of the week.
+ // day_of_week_posts is like period_posts, except that it reports
+ // all the posts that fall on each subsequent day of the week.
if (report.HANDLED(equity))
handler.reset(new posts_as_equity(handler, expr));
else if (report.HANDLED(subtotal))
@@ -211,7 +211,7 @@ post_handler_ptr chain_post_handlers(post_handler_ptr base_handler,
}
if (report.HANDLED(dow))
- handler.reset(new dow_posts(handler, expr));
+ handler.reset(new day_of_week_posts(handler, expr));
else if (report.HANDLED(by_payee))
handler.reset(new by_payee_posts(handler, expr));
@@ -258,6 +258,10 @@ post_handler_ptr chain_post_handlers(post_handler_ptr base_handler,
if (report.HANDLED(related))
handler.reset(new related_posts(handler, report.HANDLED(related_all)));
+ if (report.HANDLED(inject_))
+ handler.reset(new inject_posts(handler, report.HANDLED(inject_).str(),
+ report.session.journal->master));
+
return handler;
}
diff --git a/src/commodity.h b/src/commodity.h
index 24cd5a08..fcd26da0 100644
--- a/src/commodity.h
+++ b/src/commodity.h
@@ -164,16 +164,19 @@ 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_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
+#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
+#define COMMODITY_SAW_ANNOTATED 0x200
+#define COMMODITY_SAW_ANN_PRICE_FLOAT 0x400
+#define COMMODITY_SAW_ANN_PRICE_FIXATED 0x800
string symbol;
amount_t::precision_t precision;
diff --git a/src/convert.cc b/src/convert.cc
index d7ee52b7..5d3f23fa 100644
--- a/src/convert.cc
+++ b/src/convert.cc
@@ -97,18 +97,20 @@ value_t convert_command(call_scope_t& args)
}
bool matched = false;
- post_map_t::iterator i = post_map.find(- xact->posts.front()->amount);
- if (i != post_map.end()) {
- std::list<post_t *>& post_list((*i).second);
- foreach (post_t * post, post_list) {
- if (xact->code && post->xact->code &&
- *xact->code == *post->xact->code) {
- matched = true;
- break;
- }
- else if (xact->actual_date() == post->actual_date()) {
- matched = true;
- break;
+ if (! xact->posts.front()->amount.is_null()) {
+ post_map_t::iterator i = post_map.find(- xact->posts.front()->amount);
+ if (i != post_map.end()) {
+ std::list<post_t *>& post_list((*i).second);
+ foreach (post_t * post, post_list) {
+ if (xact->code && post->xact->code &&
+ *xact->code == *post->xact->code) {
+ matched = true;
+ break;
+ }
+ else if (xact->actual_date() == post->actual_date()) {
+ matched = true;
+ break;
+ }
}
}
}
diff --git a/src/expr.cc b/src/expr.cc
index 0769d575..5bc537d9 100644
--- a/src/expr.cc
+++ b/src/expr.cc
@@ -52,8 +52,9 @@ void expr_t::parse(std::istream& in, const parse_flags_t& flags,
in.seekg(start_pos, std::ios::beg);
scoped_array<char> buf
(new char[static_cast<std::size_t>(end_pos - start_pos) + 1]);
- in.read(buf.get(), end_pos - start_pos);
- buf[end_pos - start_pos] = '\0';
+ int len = static_cast<int>(end_pos) - static_cast<int>(start_pos);
+ in.read(buf.get(), len);
+ buf[len] = '\0';
set_text(buf.get());
}
else {
diff --git a/src/filters.cc b/src/filters.cc
index 03baadc6..1dd410d3 100644
--- a/src/filters.cc
+++ b/src/filters.cc
@@ -266,6 +266,7 @@ void anonymize_posts::operator()(post_t& post)
copy_xact_details = true;
}
xact_t& xact = temps.last_xact();
+ xact.code = none;
if (copy_xact_details) {
xact.copy_details(*post.xact);
@@ -523,7 +524,7 @@ display_filter_posts::display_filter_posts(post_handler_ptr handler,
bool _show_rounding)
: item_handler<post_t>(handler), report(_report),
show_rounding(_show_rounding),
- rounding_account(temps.create_account(_("<Rounding>"))),
+ rounding_account(temps.create_account(_("<Adjustment>"))),
revalued_account(temps.create_account(_("<Revalued>")))
{
TRACE_CTOR(display_filter_posts,
@@ -1042,10 +1043,10 @@ void by_payee_posts::flush()
void by_payee_posts::operator()(post_t& post)
{
- payee_subtotals_map::iterator i = payee_subtotals.find(post.xact->payee);
+ payee_subtotals_map::iterator i = payee_subtotals.find(post.payee());
if (i == payee_subtotals.end()) {
payee_subtotals_pair
- temp(post.xact->payee,
+ temp(post.payee(),
shared_ptr<subtotal_posts>(new subtotal_posts(handler, amount_expr)));
std::pair<payee_subtotals_map::iterator, bool> result
= payee_subtotals.insert(temp);
@@ -1073,7 +1074,7 @@ void transfer_details::operator()(post_t& post)
if (! substitute.is_null()) {
switch (which_element) {
case SET_DATE:
- temp.xdata().date = substitute.to_date();
+ temp._date = substitute.to_date();
break;
case SET_ACCOUNT: {
@@ -1112,7 +1113,7 @@ void transfer_details::operator()(post_t& post)
item_handler<post_t>::operator()(temp);
}
-void dow_posts::flush()
+void day_of_week_posts::flush()
{
for (int i = 0; i < 7; i++) {
foreach (post_t * post, days_of_the_week[i])
@@ -1143,20 +1144,44 @@ void budget_posts::report_budget_items(const date_t& date)
bool reported;
do {
+ std::list<pending_posts_list::iterator> posts_to_erase;
+
reported = false;
- foreach (pending_posts_list::value_type& pair, pending_posts) {
+ for (pending_posts_list::iterator i = pending_posts.begin();
+ i != pending_posts.end();
+ i++) {
+ pending_posts_list::value_type& pair(*i);
+
optional<date_t> begin = pair.first.start;
if (! begin) {
- if (! pair.first.find_period(date))
+ optional<date_t> range_begin;
+ if (pair.first.range)
+ range_begin = pair.first.range->begin();
+
+ DEBUG("budget.generate", "Finding period for pending post");
+ if (! pair.first.find_period(range_begin ? *range_begin : date))
continue;
+ if (! pair.first.start)
+ throw_(std::logic_error,
+ _("Failed to find period for periodic transaction"));
begin = pair.first.start;
}
- assert(begin);
+
+#if defined(DEBUG_ON)
+ DEBUG("budget.generate", "begin = " << *begin);
+ DEBUG("budget.generate", "date = " << date);
+ if (pair.first.finish)
+ DEBUG("budget.generate", "pair.first.finish = " << *pair.first.finish);
+#endif
if (*begin <= date &&
(! pair.first.finish || *begin < *pair.first.finish)) {
post_t& post = *pair.second;
+ ++pair.first;
+ if (! pair.first.start)
+ posts_to_erase.push_back(i);
+
DEBUG("budget.generate", "Reporting budget for "
<< post.reported_account()->fullname());
@@ -1176,14 +1201,14 @@ void budget_posts::report_budget_items(const date_t& date)
temp.xdata().add_flags(POST_EXT_COMPOUND);
}
- ++pair.first;
- begin = *pair.first.start;
-
item_handler<post_t>::operator()(temp);
reported = true;
}
}
+
+ foreach (pending_posts_list::iterator& i, posts_to_erase)
+ pending_posts.erase(i);
} while (reported);
}
@@ -1215,6 +1240,14 @@ void budget_posts::operator()(post_t& post)
}
}
+void budget_posts::flush()
+{
+ if (flags & BUDGET_BUDGETED)
+ report_budget_items(terminus);
+
+ item_handler<post_t>::flush();
+}
+
void forecast_posts::add_post(const date_interval_t& period, post_t& post)
{
date_interval_t i(period);
@@ -1274,17 +1307,16 @@ void forecast_posts::flush()
least = i;
}
- date_t& begin = *(*least).first.start;
#if !defined(NO_ASSERTS)
if ((*least).first.finish)
- assert(begin < *(*least).first.finish);
+ assert(*(*least).first.start < *(*least).first.finish);
#endif
// If the next date in the series for this periodic posting is more than 5
// years beyond the last valid post we generated, drop it from further
// consideration.
- date_t next = *(*least).first.next;
- assert(next > begin);
+ date_t& next(*(*least).first.next);
+ assert(next > *(*least).first.start);
if (static_cast<std::size_t>((next - last).days()) >
static_cast<std::size_t>(365U) * forecast_years) {
@@ -1295,15 +1327,13 @@ void forecast_posts::flush()
continue;
}
- begin = next;
-
// `post' refers to the posting defined in the period transaction. We
// make a copy of it within a temporary transaction with the payee
// "Forecast transaction".
post_t& post = *(*least).second;
xact_t& xact = temps.create_xact();
xact.payee = _("Forecast transaction");
- xact._date = begin;
+ xact._date = next;
post_t& temp = temps.copy_post(post, xact);
// Submit the generated posting
@@ -1338,6 +1368,60 @@ void forecast_posts::flush()
item_handler<post_t>::flush();
}
+inject_posts::inject_posts(post_handler_ptr handler,
+ const string& tag_list,
+ account_t * master)
+ : item_handler<post_t>(handler)
+{
+ TRACE_CTOR(inject_posts, "post_handler_ptr, string");
+
+ scoped_array<char> buf(new char[tag_list.length() + 1]);
+ std::strcpy(buf.get(), tag_list.c_str());
+
+ for (char * q = std::strtok(buf.get(), ",");
+ q;
+ q = std::strtok(NULL, ",")) {
+
+ std::list<string> account_names;
+ split_string(q, ':', account_names);
+ account_t * account =
+ create_temp_account_from_path(account_names, temps, master);
+ account->add_flags(ACCOUNT_GENERATED);
+
+ tags_list.push_back
+ (tags_list_pair(q, tag_mapping_pair(account, tag_injected_set())));
+ }
+}
+
+void inject_posts::operator()(post_t& post)
+{
+ foreach (tags_list_pair& pair, tags_list) {
+ optional<value_t> tag_value = post.get_tag(pair.first, false);
+ if (! tag_value &&
+ pair.second.second.find(post.xact) == pair.second.second.end()) {
+ // When checking if the transaction has the tag, only inject once
+ // per transaction.
+ pair.second.second.insert(post.xact);
+ tag_value = post.xact->get_tag(pair.first);
+ }
+
+ if (tag_value) {
+ xact_t& xact = temps.copy_xact(*post.xact);
+ xact._date = post.date();
+ xact.add_flags(ITEM_GENERATED);
+ post_t& temp = temps.copy_post(post, xact);
+
+ temp.account = pair.second.first;
+ temp.amount = tag_value->to_amount();
+ temp.add_flags(ITEM_GENERATED);
+
+ item_handler<post_t>::operator()(temp);
+ }
+ }
+
+ item_handler<post_t>::operator()(post);
+}
+
pass_down_accounts::pass_down_accounts(acct_handler_ptr handler,
accounts_iterator& iter,
const optional<predicate_t>& _pred,
diff --git a/src/filters.h b/src/filters.h
index 9102d4f1..08dd18d5 100644
--- a/src/filters.h
+++ b/src/filters.h
@@ -813,19 +813,19 @@ public:
}
};
-class dow_posts : public subtotal_posts
+class day_of_week_posts : public subtotal_posts
{
posts_list days_of_the_week[7];
- dow_posts();
+ day_of_week_posts();
public:
- dow_posts(post_handler_ptr handler, expr_t& amount_expr)
+ day_of_week_posts(post_handler_ptr handler, expr_t& amount_expr)
: subtotal_posts(handler, amount_expr) {
- TRACE_CTOR(dow_posts, "post_handler_ptr, bool");
+ TRACE_CTOR(day_of_week_posts, "post_handler_ptr, bool");
}
- virtual ~dow_posts() throw() {
- TRACE_DTOR(dow_posts);
+ virtual ~day_of_week_posts() throw() {
+ TRACE_DTOR(day_of_week_posts);
}
virtual void flush();
@@ -882,14 +882,16 @@ class budget_posts : public generate_posts
#define BUDGET_WRAP_VALUES 0x04
uint_least8_t flags;
+ date_t terminus;
budget_posts();
public:
budget_posts(post_handler_ptr handler,
- uint_least8_t _flags = BUDGET_BUDGETED)
- : generate_posts(handler), flags(_flags) {
- TRACE_CTOR(budget_posts, "post_handler_ptr, uint_least8_t");
+ date_t _terminus,
+ uint_least8_t _flags = BUDGET_BUDGETED)
+ : generate_posts(handler), flags(_flags), terminus(_terminus) {
+ TRACE_CTOR(budget_posts, "post_handler_ptr, date_t, uint_least8_t");
}
virtual ~budget_posts() throw() {
TRACE_DTOR(budget_posts);
@@ -897,6 +899,7 @@ public:
void report_budget_items(const date_t& date);
+ virtual void flush();
virtual void operator()(post_t& post);
};
@@ -929,6 +932,26 @@ class forecast_posts : public generate_posts
}
};
+class inject_posts : public item_handler<post_t>
+{
+ typedef std::set<xact_t *> tag_injected_set;
+ typedef std::pair<account_t *, tag_injected_set> tag_mapping_pair;
+ typedef std::pair<string, tag_mapping_pair> tags_list_pair;
+
+ std::list<tags_list_pair> tags_list;
+ temporaries_t temps;
+
+ public:
+ inject_posts(post_handler_ptr handler, const string& tag_list,
+ account_t * master);
+
+ virtual ~inject_posts() throw() {
+ TRACE_DTOR(inject_posts);
+ }
+
+ virtual void operator()(post_t& post);
+};
+
//////////////////////////////////////////////////////////////////////
//
// Account filters
diff --git a/src/format.cc b/src/format.cc
index 946dcf80..93ce4ed4 100644
--- a/src/format.cc
+++ b/src/format.cc
@@ -540,7 +540,7 @@ string format_t::truncate(const unistring& ustr,
else
adjust = std::size_t
(std::ceil(double(overflow) *
- ((double(*i + counter) * double(iteration)) /
+ ((double(*i + counter*3) * double(iteration)) /
(double(len_minus_last) - double(counter)))));
DEBUG("format.abbrev", "Weight calc: (" << overflow
<< " * (((" << *i << " + " << counter << ") * "
diff --git a/src/item.cc b/src/item.cc
index 63f0f3a9..9290ab2f 100644
--- a/src/item.cc
+++ b/src/item.cc
@@ -37,7 +37,7 @@ namespace ledger {
bool item_t::use_effective_date = false;
-bool item_t::has_tag(const string& tag) const
+bool item_t::has_tag(const string& tag, bool) const
{
DEBUG("item.meta", "Checking if item has tag: " << tag);
if (! metadata) {
@@ -57,7 +57,7 @@ bool item_t::has_tag(const string& tag) const
}
bool item_t::has_tag(const mask_t& tag_mask,
- const optional<mask_t>& value_mask) const
+ const optional<mask_t>& value_mask, bool) const
{
if (metadata) {
foreach (const string_map::value_type& data, *metadata) {
@@ -72,7 +72,7 @@ bool item_t::has_tag(const mask_t& tag_mask,
return false;
}
-optional<value_t> item_t::get_tag(const string& tag) const
+ optional<value_t> item_t::get_tag(const string& tag, bool) const
{
DEBUG("item.meta", "Getting item tag: " << tag);
if (metadata) {
@@ -87,7 +87,8 @@ optional<value_t> item_t::get_tag(const string& tag) const
}
optional<value_t> item_t::get_tag(const mask_t& tag_mask,
- const optional<mask_t>& value_mask) const
+ const optional<mask_t>& value_mask,
+ bool) const
{
if (metadata) {
foreach (const string_map::value_type& data, *metadata) {
@@ -138,26 +139,26 @@ void item_t::parse_tags(const char * p,
scope_t& scope,
bool overwrite_existing)
{
- if (const char * b = std::strchr(p, '[')) {
- if (*(b + 1) != '\0' &&
- (std::isdigit(*(b + 1)) || *(b + 1) == '=')) {
- if (const char * e = std::strchr(p, ']')) {
- char buf[256];
- std::strncpy(buf, b + 1, e - b - 1);
- buf[e - b - 1] = '\0';
-
- if (char * p = std::strchr(buf, '=')) {
- *p++ = '\0';
- _date_eff = parse_date(p);
+ if (! std::strchr(p, ':')) {
+ if (const char * b = std::strchr(p, '[')) {
+ if (*(b + 1) != '\0' &&
+ (std::isdigit(*(b + 1)) || *(b + 1) == '=')) {
+ if (const char * e = std::strchr(p, ']')) {
+ char buf[256];
+ std::strncpy(buf, b + 1, e - b - 1);
+ buf[e - b - 1] = '\0';
+
+ if (char * p = std::strchr(buf, '=')) {
+ *p++ = '\0';
+ _date_eff = parse_date(p);
+ }
+ if (buf[0])
+ _date = parse_date(buf);
}
- if (buf[0])
- _date = parse_date(buf);
}
}
- }
-
- if (! std::strchr(p, ':'))
return;
+ }
scoped_array<char> buf(new char[std::strlen(p) + 1]);
@@ -165,6 +166,7 @@ void item_t::parse_tags(const char * p,
string tag;
bool by_value = false;
+ bool first = true;
for (char * q = std::strtok(buf.get(), " \t");
q;
q = std::strtok(NULL, " \t")) {
@@ -190,7 +192,7 @@ void item_t::parse_tags(const char * p,
(*i).second.second = true;
}
}
- else if (q[len - 1] == ':') { // a metadata setting
+ else if (first && q[len - 1] == ':') { // a metadata setting
int index = 1;
if (q[len - 2] == ':') {
by_value = true;
@@ -198,6 +200,7 @@ void item_t::parse_tags(const char * p,
}
tag = string(q, len - index);
}
+ first = false;
}
}
diff --git a/src/item.h b/src/item.h
index 8018db9a..79d2f23b 100644
--- a/src/item.h
+++ b/src/item.h
@@ -149,13 +149,17 @@ public:
return ! (*this == xact);
}
- virtual bool has_tag(const string& tag) const;
- virtual bool has_tag(const mask_t& tag_mask,
- const optional<mask_t>& value_mask = none) const;
-
- virtual optional<value_t> get_tag(const string& tag) const;
- virtual optional<value_t> get_tag(const mask_t& tag_mask,
- const optional<mask_t>& value_mask = none) const;
+ virtual bool has_tag(const string& tag,
+ bool inherit = true) const;
+ virtual bool has_tag(const mask_t& tag_mask,
+ const optional<mask_t>& value_mask = none,
+ bool inherit = true) const;
+
+ virtual optional<value_t> get_tag(const string& tag,
+ bool inherit = true) const;
+ virtual optional<value_t> get_tag(const mask_t& tag_mask,
+ const optional<mask_t>& value_mask = none,
+ bool inherit = true) const;
virtual string_map::iterator
set_tag(const string& tag,
@@ -171,6 +175,10 @@ public:
static bool use_effective_date;
+ virtual bool has_date() const {
+ return _date;
+ }
+
virtual date_t date() const {
assert(_date);
if (use_effective_date)
diff --git a/src/output.cc b/src/output.cc
index de0444cf..f53e60c9 100644
--- a/src/output.cc
+++ b/src/output.cc
@@ -228,7 +228,8 @@ format_accounts::mark_accounts(account_t& account, const bool flat)
if ((! flat && to_display > 1) ||
((flat || to_display != 1 ||
account.has_xflags(ACCOUNT_EXT_VISITED)) &&
- (report.HANDLED(empty) || report.fn_display_total(call_scope)) &&
+ (report.HANDLED(empty) ||
+ report.display_value(report.fn_display_total(call_scope))) &&
disp_pred(bound_scope))) {
account.xdata().add_flags(ACCOUNT_EXT_TO_DISPLAY);
DEBUG("account.display", "Marking account as TO_DISPLAY");
@@ -313,9 +314,9 @@ void report_payees::flush()
void report_payees::operator()(post_t& post)
{
- std::map<string, std::size_t>::iterator i = payees.find(post.xact->payee);
+ std::map<string, std::size_t>::iterator i = payees.find(post.payee());
if (i == payees.end())
- payees.insert(payees_pair(post.xact->payee, 1));
+ payees.insert(payees_pair(post.payee(), 1));
else
(*i).second++;
}
diff --git a/src/pool.cc b/src/pool.cc
index 618a43c5..20b585dd 100644
--- a/src/pool.cc
+++ b/src/pool.cc
@@ -172,12 +172,21 @@ commodity_pool_t::create(commodity_t& comm,
const string& mapping_key)
{
assert(comm);
+ assert(! comm.has_annotation());
assert(details);
assert(! mapping_key.empty());
std::auto_ptr<commodity_t> commodity
(new annotated_commodity_t(&comm, details));
+ comm.add_flags(COMMODITY_SAW_ANNOTATED);
+ if (details.price) {
+ if (details.has_flags(ANNOTATION_PRICE_FIXATED))
+ comm.add_flags(COMMODITY_SAW_ANN_PRICE_FIXATED);
+ else
+ comm.add_flags(COMMODITY_SAW_ANN_PRICE_FLOAT);
+ }
+
commodity->qualified_symbol = comm.symbol();
assert(! commodity->qualified_symbol->empty());
@@ -254,7 +263,13 @@ commodity_pool_t::exchange(const amount_t& amount,
DEBUG("commodity.prices.add", "exchange: per-unit-cost = " << per_unit_cost);
- if (! per_unit_cost.is_realzero())
+ // Do not record commodity exchanges where amount's commodity has a
+ // fixated price, since this does not establish a market value for the
+ // base commodity.
+ if (! per_unit_cost.is_realzero() &&
+ (current_annotation == NULL ||
+ ! (current_annotation->price &&
+ current_annotation->has_flags(ANNOTATION_PRICE_FIXATED))))
exchange(commodity, per_unit_cost, moment ? *moment : CURRENT_TIME());
cost_breakdown_t breakdown;
@@ -276,6 +291,9 @@ commodity_pool_t::exchange(const amount_t& amount,
moment->date() : optional<date_t>(), tag);
annotation.add_flags(ANNOTATION_PRICE_CALCULATED);
+ if (current_annotation &&
+ current_annotation->has_flags(ANNOTATION_PRICE_FIXATED))
+ annotation.add_flags(ANNOTATION_PRICE_FIXATED);
if (moment)
annotation.add_flags(ANNOTATION_DATE_CALCULATED);
if (tag)
diff --git a/src/post.cc b/src/post.cc
index 675749fc..4fc34892 100644
--- a/src/post.cc
+++ b/src/post.cc
@@ -39,40 +39,42 @@
namespace ledger {
-bool post_t::has_tag(const string& tag) const
+bool post_t::has_tag(const string& tag, bool inherit) const
{
if (item_t::has_tag(tag))
return true;
- if (xact)
+ if (inherit && xact)
return xact->has_tag(tag);
return false;
}
bool post_t::has_tag(const mask_t& tag_mask,
- const optional<mask_t>& value_mask) const
+ const optional<mask_t>& value_mask,
+ bool inherit) const
{
if (item_t::has_tag(tag_mask, value_mask))
return true;
- if (xact)
+ if (inherit && xact)
return xact->has_tag(tag_mask, value_mask);
return false;
}
-optional<value_t> post_t::get_tag(const string& tag) const
+optional<value_t> post_t::get_tag(const string& tag, bool inherit) const
{
if (optional<value_t> value = item_t::get_tag(tag))
return value;
- if (xact)
+ if (inherit && xact)
return xact->get_tag(tag);
return none;
}
optional<value_t> post_t::get_tag(const mask_t& tag_mask,
- const optional<mask_t>& value_mask) const
+ const optional<mask_t>& value_mask,
+ bool inherit) const
{
if (optional<value_t> value = item_t::get_tag(tag_mask, value_mask))
return value;
- if (xact)
+ if (inherit && xact)
return xact->get_tag(tag_mask, value_mask);
return none;
}
@@ -123,6 +125,14 @@ optional<date_t> post_t::effective_date() const
return date;
}
+string post_t::payee() const
+{
+ if (optional<value_t> post_payee = get_tag(_("Payee")))
+ return post_payee->as_string();
+ else
+ return xact->payee;
+}
+
namespace {
value_t get_this(post_t& post) {
return scope_value(&post);
@@ -160,7 +170,7 @@ namespace {
}
value_t get_payee(post_t& post) {
- return string_value(post.xact->payee);
+ return string_value(post.payee());
}
value_t get_note(post_t& post) {
diff --git a/src/post.h b/src/post.h
index aec81e89..e1535a46 100644
--- a/src/post.h
+++ b/src/post.h
@@ -99,19 +99,25 @@ public:
TRACE_DTOR(post_t);
}
- virtual bool has_tag(const string& tag) const;
- virtual bool has_tag(const mask_t& tag_mask,
- const optional<mask_t>& value_mask = none) const;
-
- virtual optional<value_t> get_tag(const string& tag) const;
- virtual optional<value_t> get_tag(const mask_t& tag_mask,
- const optional<mask_t>& value_mask = none) const;
+ virtual bool has_tag(const string& tag,
+ bool inherit = true) const;
+ virtual bool has_tag(const mask_t& tag_mask,
+ const optional<mask_t>& value_mask = none,
+ bool inherit = true) const;
+
+ virtual optional<value_t> get_tag(const string& tag,
+ bool inherit = true) const;
+ virtual optional<value_t> get_tag(const mask_t& tag_mask,
+ const optional<mask_t>& value_mask = none,
+ bool inherit = true) const;
virtual date_t value_date() const;
virtual date_t date() const;
virtual date_t actual_date() const;
virtual optional<date_t> effective_date() const;
+ string payee() const;
+
bool must_balance() const {
return ! has_flags(POST_VIRTUAL) || has_flags(POST_MUST_BALANCE);
}
diff --git a/src/precmd.cc b/src/precmd.cc
index 95f3e875..8ca27fd8 100644
--- a/src/precmd.cc
+++ b/src/precmd.cc
@@ -200,26 +200,23 @@ value_t query_command(call_scope_t& args)
args.value().dump(out);
out << std::endl << std::endl;
- query_t query(args.value(), report.what_to_keep());
- if (query) {
+ query_t query(args.value(), report.what_to_keep(),
+ ! report.HANDLED(collapse));
+ if (query.has_query(query_t::QUERY_LIMIT)) {
call_scope_t sub_args(static_cast<scope_t&>(args));
- sub_args.push_back(string_value(query.text()));
+ sub_args.push_back(string_value(query.get_query(query_t::QUERY_LIMIT)));
parse_command(sub_args);
}
- if (query.tokens_remaining()) {
+ if (query.has_query(query_t::QUERY_SHOW)) {
out << std::endl << _("====== Display predicate ======")
<< std::endl << std::endl;
- query.parse_again();
+ call_scope_t disp_sub_args(static_cast<scope_t&>(args));
+ disp_sub_args.push_back(string_value(query.get_query(query_t::QUERY_SHOW)));
- if (query) {
- call_scope_t disp_sub_args(static_cast<scope_t&>(args));
- disp_sub_args.push_back(string_value(query.text()));
-
- parse_command(disp_sub_args);
- }
+ parse_command(disp_sub_args);
}
return NULL_VALUE;
diff --git a/src/predicate.cc b/src/predicate.cc
index 369120e6..fd301a7d 100644
--- a/src/predicate.cc
+++ b/src/predicate.cc
@@ -37,10 +37,4 @@
namespace ledger {
-predicate_t::predicate_t(const query_t& other)
- : expr_t(other), what_to_keep(other.what_to_keep)
-{
- TRACE_CTOR(predicate_t, "query_t");
-}
-
} // namespace ledger
diff --git a/src/predicate.h b/src/predicate.h
index fc99c3c6..673f1d5d 100644
--- a/src/predicate.h
+++ b/src/predicate.h
@@ -48,8 +48,6 @@
namespace ledger {
-class query_t;
-
class predicate_t : public expr_t
{
public:
@@ -63,15 +61,21 @@ public:
: expr_t(other), what_to_keep(other.what_to_keep) {
TRACE_CTOR(predicate_t, "copy");
}
- predicate_t(const query_t& other);
-
- predicate_t(const string& str, const keep_details_t& _what_to_keep,
- const parse_flags_t& flags = PARSE_DEFAULT)
+ predicate_t(ptr_op_t _ptr,
+ const keep_details_t& _what_to_keep,
+ scope_t * _context = NULL)
+ : expr_t(_ptr, _context), what_to_keep(_what_to_keep) {
+ TRACE_CTOR(predicate_t, "ptr_op_t, keep_details_t, scope_t *");
+ }
+ predicate_t(const string& str,
+ const keep_details_t& _what_to_keep,
+ const parse_flags_t& flags = PARSE_DEFAULT)
: expr_t(str, flags), what_to_keep(_what_to_keep) {
TRACE_CTOR(predicate_t, "string, keep_details_t, parse_flags_t");
}
- predicate_t(std::istream& in, const keep_details_t& _what_to_keep,
- const parse_flags_t& flags = PARSE_DEFAULT)
+ predicate_t(std::istream& in,
+ const keep_details_t& _what_to_keep,
+ const parse_flags_t& flags = PARSE_DEFAULT)
: expr_t(in, flags), what_to_keep(_what_to_keep) {
TRACE_CTOR(predicate_t, "std::istream&, keep_details_t, parse_flags_t");
}
diff --git a/src/print.cc b/src/print.cc
index de35a31d..03c87884 100644
--- a/src/print.cc
+++ b/src/print.cc
@@ -201,7 +201,7 @@ namespace {
}
if (post->assigned_amount)
- amtbuf << " = " << post->assigned_amount;
+ amtbuf << " = " << *post->assigned_amount;
string trailer = amtbuf.str();
out << trailer;
diff --git a/src/query.cc b/src/query.cc
index 404c101f..bed6afae 100644
--- a/src/query.cc
+++ b/src/query.cc
@@ -53,7 +53,12 @@ query_t::lexer_t::token_t query_t::lexer_t::next_token()
}
}
+ resume:
switch (*arg_i) {
+ case '\0':
+ assert(false);
+ break;
+
case '\'':
case '"':
case '/': {
@@ -84,13 +89,17 @@ query_t::lexer_t::token_t query_t::lexer_t::next_token()
if (multiple_args && consume_next_arg) {
consume_next_arg = false;
token_t tok(token_t::TERM, string(arg_i, arg_end));
+ prev_arg_i = arg_i;
arg_i = arg_end;
return tok;
}
- resume:
bool consume_next = false;
switch (*arg_i) {
+ case '\0':
+ assert(false);
+ break;
+
case ' ':
case '\t':
case '\r':
@@ -121,15 +130,20 @@ query_t::lexer_t::token_t query_t::lexer_t::next_token()
string::const_iterator beg = arg_i;
for (; arg_i != arg_end; ++arg_i) {
switch (*arg_i) {
+ case '\0':
+ assert(false);
+ break;
+
case ' ':
case '\t':
case '\n':
case '\r':
- if (! consume_whitespace)
+ if (! multiple_args && ! consume_whitespace)
goto test_ident;
else
ident.push_back(*arg_i);
break;
+
case '(':
case ')':
case '&':
@@ -170,20 +184,16 @@ test_ident:
return token_t(token_t::TOK_META);
else if (ident == "data")
return token_t(token_t::TOK_META);
- else if (ident == "show") {
- // The "show" keyword is special, and separates a limiting predicate
- // from a display predicate.
- DEBUG("pred.show", "string = " << (*begin).as_string());
- return token_t(token_t::END_REACHED);
- }
-#if 0
- // jww (2009-11-06): This is disabled for the time being.
- else if (ident == "date") {
- // The date keyword takes the whole of the next string as its argument.
- consume_whitespace = true;
- return token_t(token_t::TOK_DATE);
- }
-#endif
+ else if (ident == "show")
+ return token_t(token_t::TOK_SHOW);
+ else if (ident == "bold")
+ return token_t(token_t::TOK_BOLD);
+ else if (ident == "for")
+ return token_t(token_t::TOK_FOR);
+ else if (ident == "since")
+ return token_t(token_t::TOK_SINCE);
+ else if (ident == "until")
+ return token_t(token_t::TOK_UNTIL);
else if (ident == "expr") {
// The expr keyword takes the whole of the next string as its argument.
consume_next_arg = true;
@@ -238,10 +248,15 @@ query_t::parser_t::parse_query_term(query_t::lexer_t::token_t::kind_t tok_contex
lexer_t::token_t tok = lexer.next_token();
switch (tok.kind) {
+ case lexer_t::token_t::TOK_SHOW:
+ case lexer_t::token_t::TOK_BOLD:
+ case lexer_t::token_t::TOK_FOR:
+ case lexer_t::token_t::TOK_SINCE:
+ case lexer_t::token_t::TOK_UNTIL:
case lexer_t::token_t::END_REACHED:
+ lexer.push_token(tok);
break;
- case lexer_t::token_t::TOK_DATE:
case lexer_t::token_t::TOK_CODE:
case lexer_t::token_t::TOK_PAYEE:
case lexer_t::token_t::TOK_NOTE:
@@ -257,41 +272,6 @@ query_t::parser_t::parse_query_term(query_t::lexer_t::token_t::kind_t tok_contex
case lexer_t::token_t::TERM:
assert(tok.value);
switch (tok_context) {
- case lexer_t::token_t::TOK_DATE: {
- expr_t::ptr_op_t ident = new expr_t::op_t(expr_t::op_t::IDENT);
- ident->set_ident("date");
-
- date_interval_t interval(*tok.value);
-
- if (interval.start) {
- node = new expr_t::op_t(expr_t::op_t::O_GTE);
- node->set_left(ident);
-
- expr_t::ptr_op_t arg1 = new expr_t::op_t(expr_t::op_t::VALUE);
- arg1->set_value(*interval.start);
- node->set_right(arg1);
- }
-
- if (interval.finish) {
- expr_t::ptr_op_t lt = new expr_t::op_t(expr_t::op_t::O_LT);
- lt->set_left(ident);
-
- expr_t::ptr_op_t arg1 = new expr_t::op_t(expr_t::op_t::VALUE);
- arg1->set_value(*interval.finish);
- lt->set_right(arg1);
-
- if (node) {
- expr_t::ptr_op_t prev(node);
- node = new expr_t::op_t(expr_t::op_t::O_AND);
- node->set_left(prev);
- node->set_right(lt);
- } else {
- node = lt;
- }
- }
- break;
- }
-
case lexer_t::token_t::TOK_EXPR:
node = expr_t(*tok.value).get_op();
break;
@@ -357,7 +337,7 @@ query_t::parser_t::parse_query_term(query_t::lexer_t::token_t::kind_t tok_contex
break;
case lexer_t::token_t::LPAREN:
- node = parse_query_expr(tok_context);
+ node = parse_query_expr(tok_context, true);
tok = lexer.next_token();
if (tok.kind != lexer_t::token_t::RPAREN)
tok.expected(')');
@@ -447,18 +427,121 @@ query_t::parser_t::parse_or_expr(lexer_t::token_t::kind_t tok_context)
}
expr_t::ptr_op_t
-query_t::parser_t::parse_query_expr(lexer_t::token_t::kind_t tok_context)
+query_t::parser_t::parse_query_expr(lexer_t::token_t::kind_t tok_context,
+ bool subexpression)
{
- if (expr_t::ptr_op_t node = parse_or_expr(tok_context)) {
- if (expr_t::ptr_op_t next = parse_query_expr(tok_context)) {
- expr_t::ptr_op_t prev(node);
- node = new expr_t::op_t(expr_t::op_t::O_OR);
- node->set_left(prev);
- node->set_right(next);
+ expr_t::ptr_op_t limiter;
+
+ while (expr_t::ptr_op_t next = parse_or_expr(tok_context)) {
+ if (! limiter) {
+ limiter = next;
+ } else {
+ expr_t::ptr_op_t prev(limiter);
+ limiter = new expr_t::op_t(expr_t::op_t::O_OR);
+ limiter->set_left(prev);
+ limiter->set_right(next);
}
- return node;
}
- return expr_t::ptr_op_t();
+
+ if (! subexpression) {
+ if (limiter)
+ query_map.insert
+ (query_map_t::value_type
+ (QUERY_LIMIT, predicate_t(limiter, what_to_keep).print_to_str()));
+
+ lexer_t::token_t tok = lexer.peek_token();
+ while (tok.kind != lexer_t::token_t::END_REACHED) {
+ switch (tok.kind) {
+ case lexer_t::token_t::TOK_SHOW: {
+ lexer.next_token();
+
+ expr_t::ptr_op_t node;
+ while (expr_t::ptr_op_t next = parse_or_expr(tok_context)) {
+ if (! node) {
+ node = next;
+ } else {
+ expr_t::ptr_op_t prev(node);
+ node = new expr_t::op_t(expr_t::op_t::O_OR);
+ node->set_left(prev);
+ node->set_right(next);
+ }
+ }
+
+ if (node)
+ query_map.insert
+ (query_map_t::value_type
+ (QUERY_SHOW, predicate_t(node, what_to_keep).print_to_str()));
+ break;
+ }
+
+ case lexer_t::token_t::TOK_BOLD: {
+ lexer.next_token();
+
+ expr_t::ptr_op_t node = parse_or_expr(tok_context);
+ while (expr_t::ptr_op_t next = parse_or_expr(tok_context)) {
+ expr_t::ptr_op_t prev(node);
+ node = new expr_t::op_t(expr_t::op_t::O_OR);
+ node->set_left(prev);
+ node->set_right(next);
+ }
+
+ if (node)
+ query_map.insert
+ (query_map_t::value_type
+ (QUERY_BOLD, predicate_t(node, what_to_keep).print_to_str()));
+ break;
+ }
+
+ case lexer_t::token_t::TOK_FOR:
+ case lexer_t::token_t::TOK_SINCE:
+ case lexer_t::token_t::TOK_UNTIL: {
+ tok = lexer.next_token();
+
+ string for_string;
+
+ if (tok.kind == lexer_t::token_t::TOK_SINCE)
+ for_string = "since";
+ else if (tok.kind == lexer_t::token_t::TOK_UNTIL)
+ for_string = "until";
+
+ lexer.consume_next_arg = true;
+ tok = lexer.peek_token();
+
+ while (tok.kind != lexer_t::token_t::END_REACHED) {
+ tok = lexer.next_token();
+ assert(tok.kind == lexer_t::token_t::TERM);
+
+ if (*tok.value == "show" || *tok.value == "bold" ||
+ *tok.value == "for" || *tok.value == "since" ||
+ *tok.value == "until") {
+ lexer.token_cache = lexer_t::token_t();
+ lexer.arg_i = lexer.prev_arg_i;
+ lexer.consume_next_arg = false;
+ break;
+ }
+
+ if (! for_string.empty())
+ for_string += " ";
+ for_string += *tok.value;
+
+ lexer.consume_next_arg = true;
+ tok = lexer.peek_token();
+ }
+
+ if (! for_string.empty())
+ query_map.insert(query_map_t::value_type(QUERY_FOR, for_string));
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ tok = lexer.peek_token();
+ }
+ }
+
+ return limiter;
}
} // namespace ledger
diff --git a/src/query.h b/src/query.h
index 02f8f4e7..5a4651a0 100644
--- a/src/query.h
+++ b/src/query.h
@@ -46,7 +46,7 @@
namespace ledger {
-class query_t : public predicate_t
+class query_t
{
protected:
class parser_t;
@@ -60,6 +60,7 @@ public:
value_t::sequence_t::const_iterator begin;
value_t::sequence_t::const_iterator end;
+ string::const_iterator prev_arg_i;
string::const_iterator arg_i;
string::const_iterator arg_end;
@@ -81,7 +82,6 @@ public:
TOK_OR,
TOK_EQ,
- TOK_DATE,
TOK_CODE,
TOK_PAYEE,
TOK_NOTE,
@@ -89,6 +89,12 @@ public:
TOK_META,
TOK_EXPR,
+ TOK_SHOW,
+ TOK_BOLD,
+ TOK_FOR,
+ TOK_SINCE,
+ TOK_UNTIL,
+
TERM,
END_REACHED
@@ -131,13 +137,17 @@ public:
case TOK_AND: return "TOK_AND";
case TOK_OR: return "TOK_OR";
case TOK_EQ: return "TOK_EQ";
- case TOK_DATE: return "TOK_DATE";
case TOK_CODE: return "TOK_CODE";
case TOK_PAYEE: return "TOK_PAYEE";
case TOK_NOTE: return "TOK_NOTE";
case TOK_ACCOUNT: return "TOK_ACCOUNT";
case TOK_META: return "TOK_META";
case TOK_EXPR: return "TOK_EXPR";
+ case TOK_SHOW: return "TOK_SHOW";
+ case TOK_BOLD: return "TOK_BOLD";
+ case TOK_FOR: return "TOK_FOR";
+ case TOK_SINCE: return "TOK_SINCE";
+ case TOK_UNTIL: return "TOK_UNTIL";
case TERM: return string("TERM(") + *value + ")";
case END_REACHED: return "END_REACHED";
}
@@ -153,13 +163,17 @@ public:
case TOK_AND: return "and";
case TOK_OR: return "or";
case TOK_EQ: return "=";
- case TOK_DATE: return "date";
case TOK_CODE: return "code";
case TOK_PAYEE: return "payee";
case TOK_NOTE: return "note";
case TOK_ACCOUNT: return "account";
case TOK_META: return "meta";
case TOK_EXPR: return "expr";
+ case TOK_SHOW: return "show";
+ case TOK_BOLD: return "bold";
+ case TOK_FOR: return "for";
+ case TOK_SINCE: return "since";
+ case TOK_UNTIL: return "until";
case END_REACHED: return "<EOF>";
@@ -197,8 +211,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)
+ multiple_args(lexer.multiple_args), token_cache(lexer.token_cache)
{
TRACE_CTOR(query_t::lexer_t, "copy");
}
@@ -218,24 +231,39 @@ public:
}
};
+ enum kind_t {
+ QUERY_LIMIT,
+ QUERY_SHOW,
+ QUERY_BOLD,
+ QUERY_FOR
+ };
+
+ typedef std::map<kind_t, string> query_map_t;
+
protected:
class parser_t
{
friend class query_t;
- value_t args;
- lexer_t lexer;
+ value_t args;
+ lexer_t lexer;
+ keep_details_t what_to_keep;
+ query_map_t query_map;
expr_t::ptr_op_t parse_query_term(lexer_t::token_t::kind_t tok_context);
expr_t::ptr_op_t parse_unary_expr(lexer_t::token_t::kind_t tok_context);
expr_t::ptr_op_t parse_and_expr(lexer_t::token_t::kind_t tok_context);
expr_t::ptr_op_t parse_or_expr(lexer_t::token_t::kind_t tok_context);
- expr_t::ptr_op_t parse_query_expr(lexer_t::token_t::kind_t tok_context);
+ expr_t::ptr_op_t parse_query_expr(lexer_t::token_t::kind_t tok_context,
+ bool subexpression = false);
public:
- 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 value_t& _args,
+ const keep_details_t& _what_to_keep = keep_details_t(),
+ bool multiple_args = true)
+ : args(_args), lexer(args.begin(), args.end(), multiple_args),
+ what_to_keep(_what_to_keep) {
+ TRACE_CTOR(query_t::parser_t, "value_t, keep_details_t, bool");
}
parser_t(const parser_t& parser)
: args(parser.args), lexer(parser.lexer) {
@@ -245,8 +273,8 @@ protected:
TRACE_DTOR(query_t::parser_t);
}
- expr_t::ptr_op_t parse() {
- return parse_query_expr(lexer_t::token_t::TOK_ACCOUNT);
+ expr_t::ptr_op_t parse(bool subexpression = false) {
+ return parse_query_expr(lexer_t::token_t::TOK_ACCOUNT, subexpression);
}
bool tokens_remaining() {
@@ -257,55 +285,61 @@ protected:
};
optional<parser_t> parser;
+ query_map_t predicates;
public:
query_t() {
TRACE_CTOR(query_t, "");
}
query_t(const query_t& other)
- : predicate_t(other) {
+ : parser(other.parser), predicates(other.predicates) {
TRACE_CTOR(query_t, "copy");
}
- query_t(const string& arg,
- 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");
+ query_t(const string& arg,
+ const keep_details_t& what_to_keep = keep_details_t(),
+ bool multiple_args = true) {
+ TRACE_CTOR(query_t, "string, keep_details_t, bool");
if (! arg.empty()) {
value_t temp(string_value(arg));
- parse_args(temp.to_sequence(), multiple_args);
+ parse_args(temp.to_sequence(), what_to_keep, multiple_args);
}
}
- query_t(const value_t& args,
- 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");
+ query_t(const value_t& args,
+ const keep_details_t& what_to_keep = keep_details_t(),
+ bool multiple_args = true) {
+ TRACE_CTOR(query_t, "value_t, keep_details_t, bool");
if (! args.empty())
- parse_args(args, multiple_args);
+ parse_args(args, what_to_keep, multiple_args);
}
virtual ~query_t() {
TRACE_DTOR(query_t);
}
- void parse_args(const value_t& args, bool multiple_args = true) {
+ expr_t::ptr_op_t
+ parse_args(const value_t& args,
+ const keep_details_t& what_to_keep = keep_details_t(),
+ bool multiple_args = true,
+ bool subexpression = false) {
if (! parser)
- parser = parser_t(args, multiple_args);
- ptr = parser->parse(); // expr_t::ptr
+ parser = parser_t(args, what_to_keep, multiple_args);
+ return parser->parse(subexpression);
}
- void parse_again() {
- assert(parser);
- ptr = parser->parse(); // expr_t::ptr
+ bool has_query(const kind_t& id) const {
+ return parser && parser->query_map.find(id) != parser->query_map.end();
+ }
+ string get_query(const kind_t& id) const {
+ if (parser) {
+ query_map_t::const_iterator i = parser->query_map.find(id);
+ if (i != parser->query_map.end())
+ return (*i).second;
+ }
+ return empty_string;
}
bool tokens_remaining() {
return parser && parser->tokens_remaining();
}
-
- virtual string text() {
- return print_to_str();
- }
};
} // namespace ledger
diff --git a/src/report.cc b/src/report.cc
index 9626283a..9d733674 100644
--- a/src/report.cc
+++ b/src/report.cc
@@ -145,26 +145,8 @@ void report_t::normalize_options(const string& verb)
// using -b or -e). Then, if no _duration_ was specified (such as monthly),
// then ignore the period since the begin/end are the only interesting
// details.
- if (HANDLED(period_)) {
- date_interval_t interval(HANDLER(period_).str());
-
- optional<date_t> begin = interval.begin();
- optional<date_t> end = interval.end();
-
- if (! HANDLED(begin_) && begin) {
- string predicate = "date>=[" + to_iso_extended_string(*begin) + "]";
- HANDLER(limit_).on(string("?normalize"), predicate);
- }
- if (! HANDLED(end_) && end) {
- string predicate = "date<[" + to_iso_extended_string(*end) + "]";
- HANDLER(limit_).on(string("?normalize"), predicate);
- }
-
- if (! interval.duration)
- HANDLER(period_).off();
- else if (! HANDLED(sort_all_))
- HANDLER(sort_xacts_).on_only(string("?normalize"));
- }
+ if (HANDLED(period_))
+ normalize_period();
// If -j or -J were specified, set the appropriate format string now so as
// to avoid option ordering issues were we to have done it during the
@@ -254,24 +236,52 @@ void report_t::normalize_options(const string& verb)
}
}
+void report_t::normalize_period()
+{
+ date_interval_t interval(HANDLER(period_).str());
+
+ optional<date_t> begin = interval.begin();
+ optional<date_t> end = interval.end();
+
+ if (! HANDLED(begin_) && begin) {
+ string predicate = "date>=[" + to_iso_extended_string(*begin) + "]";
+ HANDLER(limit_).on(string("?normalize"), predicate);
+ }
+ if (! HANDLED(end_) && end) {
+ string predicate = "date<[" + to_iso_extended_string(*end) + "]";
+ HANDLER(limit_).on(string("?normalize"), predicate);
+ }
+
+ if (! interval.duration)
+ HANDLER(period_).off();
+ else if (! HANDLED(sort_all_))
+ HANDLER(sort_xacts_).on_only(string("?normalize"));
+}
+
void report_t::parse_query_args(const value_t& args, const string& whence)
{
query_t query(args, what_to_keep());
- if (query) {
- HANDLER(limit_).on(whence, query.text());
- DEBUG("report.predicate",
- "Predicate = " << HANDLER(limit_).str());
+ if (query.has_query(query_t::QUERY_LIMIT)) {
+ HANDLER(limit_).on(whence, query.get_query(query_t::QUERY_LIMIT));
+ DEBUG("report.predicate", "Limit predicate = " << HANDLER(limit_).str());
}
- if (query.tokens_remaining()) {
- query.parse_again();
- if (query) {
- HANDLER(display_).on(whence, query.text());
+ if (query.has_query(query_t::QUERY_SHOW)) {
+ HANDLER(display_).on(whence, query.get_query(query_t::QUERY_SHOW));
+ DEBUG("report.predicate", "Display predicate = " << HANDLER(display_).str());
+ }
- DEBUG("report.predicate",
- "Display predicate = " << HANDLER(display_).str());
- }
+ if (query.has_query(query_t::QUERY_BOLD)) {
+ HANDLER(bold_if_).set_expr(whence, query.get_query(query_t::QUERY_BOLD));
+ DEBUG("report.predicate", "Bolding predicate = " << HANDLER(bold_if_).str());
+ }
+
+ if (query.has_query(query_t::QUERY_FOR)) {
+ HANDLER(period_).on(whence, query.get_query(query_t::QUERY_FOR));
+ DEBUG("report.predicate", "Report period = " << HANDLER(period_).str());
+
+ normalize_period(); // it needs normalization
}
}
@@ -428,6 +438,15 @@ void report_t::commodities_report(post_handler_ptr handler)
session.journal->clear_xdata();
}
+value_t report_t::display_value(const value_t& val)
+{
+ value_t temp(val.strip_annotations(what_to_keep()));
+ if (HANDLED(base))
+ return temp;
+ else
+ return temp.unreduced();
+}
+
value_t report_t::fn_amount_expr(call_scope_t& scope)
{
return HANDLER(amount_).expr.calc(scope);
@@ -448,6 +467,14 @@ value_t report_t::fn_display_total(call_scope_t& scope)
return HANDLER(display_total_).expr.calc(scope);
}
+value_t report_t::fn_should_bold(call_scope_t& scope)
+{
+ if (HANDLED(bold_if_))
+ return HANDLER(bold_if_).expr.calc(scope);
+ else
+ return false;
+}
+
value_t report_t::fn_market(call_scope_t& args)
{
optional<datetime_t> moment = (args.has<datetime_t>(1) ?
@@ -533,11 +560,7 @@ value_t report_t::fn_print(call_scope_t& args)
value_t report_t::fn_scrub(call_scope_t& args)
{
- value_t temp(args.value().strip_annotations(what_to_keep()));
- if (HANDLED(base))
- return temp;
- else
- return temp.unreduced();
+ return display_value(args.value());
}
value_t report_t::fn_rounded(call_scope_t& args)
@@ -900,6 +923,7 @@ option_t<report_t> * report_t::lookup_option(const char * p)
else OPT(base);
else OPT_ALT(basis, cost);
else OPT_(begin_);
+ else OPT(bold_if_);
else OPT(budget);
else OPT(by_payee);
break;
@@ -955,6 +979,7 @@ option_t<report_t> * report_t::lookup_option(const char * p)
break;
case 'i':
OPT(invert);
+ else OPT(inject_);
break;
case 'j':
OPT_CH(amount_data);
@@ -1220,6 +1245,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
return MAKE_FUNCTOR(report_t::fn_scrub);
else if (is_eq(p, "strip"))
return MAKE_FUNCTOR(report_t::fn_strip);
+ else if (is_eq(p, "should_bold"))
+ return MAKE_FUNCTOR(report_t::fn_should_bold);
break;
case 't':
diff --git a/src/report.h b/src/report.h
index eff375cc..59a632e6 100644
--- a/src/report.h
+++ b/src/report.h
@@ -126,6 +126,7 @@ public:
}
void normalize_options(const string& verb);
+ void normalize_period();
void parse_query_args(const value_t& args, const string& whence);
void posts_report(post_handler_ptr handler);
@@ -134,10 +135,13 @@ public:
void accounts_report(acct_handler_ptr handler);
void commodities_report(post_handler_ptr handler);
+ value_t display_value(const value_t& val);
+
value_t fn_amount_expr(call_scope_t& scope);
value_t fn_total_expr(call_scope_t& scope);
value_t fn_display_amount(call_scope_t& scope);
value_t fn_display_total(call_scope_t& scope);
+ value_t fn_should_bold(call_scope_t& scope);
value_t fn_market(call_scope_t& scope);
value_t fn_get_at(call_scope_t& scope);
value_t fn_is_seq(call_scope_t& scope);
@@ -260,6 +264,7 @@ public:
HANDLER(group_by_).report(out);
HANDLER(group_title_format_).report(out);
HANDLER(head_).report(out);
+ HANDLER(inject_).report(out);
HANDLER(invert).report(out);
HANDLER(limit_).report(out);
HANDLER(lot_dates).report(out);
@@ -376,9 +381,13 @@ public:
OPTION__(report_t, balance_format_, CTOR(report_t, balance_format_) {
on(none,
- "%(justify(scrub(display_total), 20, 20 + prepend_width, true, color))"
+ "%(ansify_if("
+ " justify(scrub(display_total), 20, 20 + prepend_width, true, color),"
+ " bold if should_bold))"
" %(!options.flat ? depth_spacer : \"\")"
- "%-(ansify_if(partial_account(options.flat), blue if color))\n%/"
+ "%-(ansify_if("
+ " ansify_if(partial_account(options.flat), blue if color),"
+ " bold if should_bold))\n%/"
"%$1\n%/"
"--------------------\n");
});
@@ -402,6 +411,18 @@ public:
parent->HANDLER(limit_).on(string("--begin"), predicate);
});
+ OPTION__
+ (report_t, bold_if_,
+ expr_t expr;
+ CTOR(report_t, bold_if_) {}
+ void set_expr(const optional<string>& whence, const string& str) {
+ expr = str;
+ on(whence, str);
+ }
+ DO_(args) {
+ set_expr(args.get<string>(0), args.get<string>(1));
+ });
+
OPTION_(report_t, budget, DO() {
parent->budget_flags |= BUDGET_BUDGETED;
});
@@ -616,6 +637,7 @@ public:
});
OPTION(report_t, head_);
+ OPTION(report_t, inject_);
OPTION_(report_t, invert, DO() {
parent->HANDLER(amount_).set_expr(string("--invert"), "-amount");
@@ -804,20 +826,37 @@ public:
OPTION__(report_t, register_format_, CTOR(report_t, register_format_) {
on(none,
- "%(ansify_if(justify(format_date(date), date_width), green "
- " if color & date > today))"
- " %(ansify_if(justify(truncated(payee, payee_width), payee_width), "
- " bold if color & !cleared & actual))"
- " %(ansify_if(justify(truncated(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 + prepend_width, true, color))"
- " %(justify(scrub(display_total), total_width, "
- " 4 + meta_width + date_width + payee_width + account_width"
- " + amount_width + total_width + prepend_width, true, color))\n%/"
- "%(justify(\" \", 2 + date_width + payee_width))"
- "%$3 %$4 %$5\n");
+ "%(ansify_if("
+ " ansify_if(justify(format_date(date), date_width),"
+ " green if color and date > today),"
+ " bold if should_bold))"
+ " %(ansify_if("
+ " ansify_if(justify(truncated(payee, payee_width), payee_width), "
+ " bold if color and !cleared and actual),"
+ " bold if should_bold))"
+ " %(ansify_if("
+ " ansify_if(justify(truncated(display_account, account_width, "
+ " abbrev_len), account_width),"
+ " blue if color),"
+ " bold if should_bold))"
+ " %(ansify_if("
+ " justify(scrub(display_amount), amount_width, "
+ " 3 + meta_width + date_width + payee_width"
+ " + account_width + amount_width + prepend_width,"
+ " true, color),"
+ " bold if should_bold))"
+ " %(ansify_if("
+ " justify(scrub(display_total), total_width, "
+ " 4 + meta_width + date_width + payee_width"
+ " + account_width + amount_width + total_width"
+ " + prepend_width, true, color),"
+ " bold if should_bold))\n%/"
+ "%(justify(\" \", date_width))"
+ " %(ansify_if("
+ " justify(truncated(has_tag(\"Payee\") ? payee : \" \", "
+ " payee_width), payee_width),"
+ " bold if should_bold))"
+ " %$3 %$4 %$5\n");
});
OPTION(report_t, related); // -r
diff --git a/src/scope.h b/src/scope.h
index 07b6bebe..dac6eba3 100644
--- a/src/scope.h
+++ b/src/scope.h
@@ -602,7 +602,7 @@ inline scope_t * call_scope_t::get<scope_t *>(std::size_t index, bool) {
template <>
inline expr_t::ptr_op_t
call_scope_t::get<expr_t::ptr_op_t>(std::size_t index, bool) {
- return resolve(index, value_t::ANY).as_any<expr_t::ptr_op_t>();
+ return args[index].as_any<expr_t::ptr_op_t>();
}
class value_scope_t : public scope_t
diff --git a/src/session.cc b/src/session.cc
index 85b5fab2..65ed0bc2 100644
--- a/src/session.cc
+++ b/src/session.cc
@@ -161,14 +161,16 @@ void session_t::read_journal_files()
if (HANDLED(master_account_))
master_account = HANDLER(master_account_).str();
- std::size_t count = read_data(master_account);
- if (count == 0)
- throw_(parse_error,
- _("Failed to locate any transactions; did you specify a valid file with -f?"));
+#if defined(DEBUG_ON)
+ std::size_t count =
+#endif
+ read_data(master_account);
INFO_FINISH(journal);
+#if defined(DEBUG_ON)
INFO("Found " << count << " transactions");
+#endif
}
void session_t::close_journal_files()
diff --git a/src/textual.cc b/src/textual.cc
index 828a093d..7ddb5251 100644
--- a/src/textual.cc
+++ b/src/textual.cc
@@ -534,9 +534,13 @@ void instance_t::automated_xact_directive(char * line)
bool reveal_context = true;
try {
- std::auto_ptr<auto_xact_t> ae
- (new auto_xact_t(query_t(string(skip_ws(line + 1)),
- keep_details_t(true, true, true), false)));
+ query_t query;
+ keep_details_t keeper(true, true, true);
+ expr_t::ptr_op_t expr =
+ query.parse_args(string_value(skip_ws(line + 1)).to_sequence(),
+ keeper, false, true);
+
+ std::auto_ptr<auto_xact_t> ae(new auto_xact_t(predicate_t(expr, keeper)));
ae->pos = position_t();
ae->pos->pathname = pathname;
ae->pos->beg_pos = line_beg_pos;
@@ -715,10 +719,10 @@ void instance_t::include_directive(char * line)
mask_t glob;
#if BOOST_VERSION >= 103700
path parent_path = filename.parent_path();
- glob.assign_glob(filename.filename());
+ glob.assign_glob('^' + filename.filename() + '$');
#else // BOOST_VERSION >= 103700
path parent_path = filename.branch_path();
- glob.assign_glob(filename.leaf());
+ glob.assign_glob('^' + filename.leaf() + '$');
#endif // BOOST_VERSION >= 103700
bool files_found = false;
diff --git a/src/times.cc b/src/times.cc
index 31367e34..b8dcf98f 100644
--- a/src/times.cc
+++ b/src/times.cc
@@ -310,7 +310,14 @@ string_to_month_of_year(const std::string& str)
datetime_t parse_datetime(const char * str)
{
- datetime_t when = input_datetime_io->parse(str);
+ char buf[128];
+ std::strcpy(buf, str);
+
+ for (char * p = buf; *p; p++)
+ if (*p == '.' || *p == '-')
+ *p = '/';
+
+ datetime_t when = input_datetime_io->parse(buf);
if (when.is_not_a_date_time())
throw_(date_error, _("Invalid date/time: %1") << str);
return when;
@@ -401,6 +408,8 @@ class date_parser_t
TOK_A_MONTH,
TOK_A_WDAY,
+ TOK_AGO,
+ TOK_HENCE,
TOK_SINCE,
TOK_UNTIL,
TOK_IN,
@@ -498,6 +507,8 @@ class date_parser_t
out << date_specifier_t::day_of_week_type
(boost::get<date_time::weekdays>(*value));
break;
+ case TOK_AGO: return "ago";
+ case TOK_HENCE: return "hence";
case TOK_SINCE: return "since";
case TOK_UNTIL: return "until";
case TOK_IN: return "in";
@@ -545,6 +556,8 @@ class date_parser_t
case TOK_A_YEAR: out << "TOK_A_YEAR"; break;
case TOK_A_MONTH: out << "TOK_A_MONTH"; break;
case TOK_A_WDAY: out << "TOK_A_WDAY"; break;
+ case TOK_AGO: out << "TOK_AGO"; break;
+ case TOK_HENCE: out << "TOK_HENCE"; break;
case TOK_SINCE: out << "TOK_SINCE"; break;
case TOK_UNTIL: out << "TOK_UNTIL"; break;
case TOK_IN: out << "TOK_IN"; break;
@@ -638,17 +651,182 @@ private:
void date_parser_t::determine_when(date_parser_t::lexer_t::token_t& tok,
date_specifier_t& specifier)
{
+ date_t today = CURRENT_DATE();
+
switch (tok.kind) {
case lexer_t::token_t::TOK_DATE:
specifier = boost::get<date_specifier_t>(*tok.value);
break;
- case lexer_t::token_t::TOK_INT:
- specifier.day =
- date_specifier_t::day_type(boost::get<unsigned short>(*tok.value));
+ case lexer_t::token_t::TOK_INT: {
+ unsigned short amount = boost::get<unsigned short>(*tok.value);
+ int8_t adjust = 0;
+
+ tok = lexer.peek_token();
+ lexer_t::token_t::kind_t kind = tok.kind;
+ switch (kind) {
+ case lexer_t::token_t::TOK_YEAR:
+ case lexer_t::token_t::TOK_YEARS:
+ case lexer_t::token_t::TOK_QUARTER:
+ case lexer_t::token_t::TOK_QUARTERS:
+ case lexer_t::token_t::TOK_MONTH:
+ case lexer_t::token_t::TOK_MONTHS:
+ case lexer_t::token_t::TOK_WEEK:
+ case lexer_t::token_t::TOK_WEEKS:
+ case lexer_t::token_t::TOK_DAY:
+ case lexer_t::token_t::TOK_DAYS:
+ lexer.next_token();
+ tok = lexer.next_token();
+ switch (tok.kind) {
+ case lexer_t::token_t::TOK_AGO:
+ adjust = -1;
+ break;
+ case lexer_t::token_t::TOK_HENCE:
+ adjust = 1;
+ break;
+ default:
+ tok.unexpected();
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ date_t when(today);
+
+ switch (kind) {
+ case lexer_t::token_t::TOK_YEAR:
+ case lexer_t::token_t::TOK_YEARS:
+ when += gregorian::years(amount * adjust);
+ break;
+ case lexer_t::token_t::TOK_QUARTER:
+ case lexer_t::token_t::TOK_QUARTERS: {
+ date_t temp =
+ date_duration_t::find_nearest(today, date_duration_t::QUARTERS);
+ when += gregorian::months(amount * 3 * adjust);
+ break;
+ }
+ case lexer_t::token_t::TOK_MONTH:
+ case lexer_t::token_t::TOK_MONTHS:
+ when += gregorian::months(amount * adjust);
+ break;
+ case lexer_t::token_t::TOK_WEEK:
+ case lexer_t::token_t::TOK_WEEKS:
+ when += gregorian::weeks(amount * adjust);
+ break;
+ case lexer_t::token_t::TOK_DAY:
+ case lexer_t::token_t::TOK_DAYS:
+ when += gregorian::days(amount * adjust);
+ break;
+ default:
+ specifier.day = date_specifier_t::day_type(amount);
+ break;
+ }
+
+ if (adjust)
+ specifier = date_specifier_t(when);
break;
+ }
+
+ case lexer_t::token_t::TOK_THIS:
+ case lexer_t::token_t::TOK_NEXT:
+ case lexer_t::token_t::TOK_LAST: {
+ int8_t adjust = 0;
+ if (tok.kind == lexer_t::token_t::TOK_NEXT)
+ adjust = 1;
+ else if (tok.kind == lexer_t::token_t::TOK_LAST)
+ adjust = -1;
+
+ tok = lexer.next_token();
+ switch (tok.kind) {
+ case lexer_t::token_t::TOK_A_MONTH: {
+ date_t temp(today.year(),
+ boost::get<date_time::months_of_year>(*tok.value), 1);
+ temp += gregorian::years(adjust);
+ specifier =
+ date_specifier_t(static_cast<date_specifier_t::year_type>(temp.year()),
+ temp.month());
+ break;
+ }
+
+ case lexer_t::token_t::TOK_A_WDAY: {
+ date_t temp =
+ date_duration_t::find_nearest(today, date_duration_t::WEEKS);
+ while (temp.day_of_week() !=
+ boost::get<date_time::months_of_year>(*tok.value))
+ temp += gregorian::days(1);
+ temp += gregorian::days(7 * adjust);
+ specifier = date_specifier_t(temp);
+ break;
+ }
+
+ case lexer_t::token_t::TOK_YEAR: {
+ date_t temp(today);
+ temp += gregorian::years(adjust);
+ specifier =
+ date_specifier_t(static_cast<date_specifier_t::year_type>(temp.year()));
+ break;
+ }
+
+ case lexer_t::token_t::TOK_QUARTER: {
+ date_t base =
+ date_duration_t::find_nearest(today, date_duration_t::QUARTERS);
+ date_t temp;
+ if (adjust < 0) {
+ temp = base + gregorian::months(3 * adjust);
+ }
+ else if (adjust == 0) {
+ temp = base + gregorian::months(3);
+ }
+ else if (adjust > 0) {
+ base += gregorian::months(3 * adjust);
+ temp = base + gregorian::months(3 * adjust);
+ }
+ specifier = date_specifier_t(adjust < 0 ? temp : base);
+ break;
+ }
+
+ case lexer_t::token_t::TOK_WEEK: {
+ date_t base =
+ date_duration_t::find_nearest(today, date_duration_t::WEEKS);
+ date_t temp;
+ if (adjust < 0) {
+ temp = base + gregorian::days(7 * adjust);
+ }
+ else if (adjust == 0) {
+ temp = base + gregorian::days(7);
+ }
+ else if (adjust > 0) {
+ base += gregorian::days(7 * adjust);
+ temp = base + gregorian::days(7 * adjust);
+ }
+ specifier = date_specifier_t(adjust < 0 ? temp : base);
+ break;
+ }
+
+ case lexer_t::token_t::TOK_DAY: {
+ date_t temp(today);
+ temp += gregorian::days(adjust);
+ specifier = date_specifier_t(temp);
+ break;
+ }
+
+ default:
+ case lexer_t::token_t::TOK_MONTH: {
+ date_t temp(today);
+ temp += gregorian::months(adjust);
+ specifier =
+ date_specifier_t(static_cast<date_specifier_t::year_type>(temp.year()),
+ temp.month());
+ break;
+ }
+ }
+ break;
+ }
+
case lexer_t::token_t::TOK_A_YEAR:
- specifier.year = boost::get<date_specifier_t::year_type>(*tok.value);
+ specifier.year = boost::get<date_specifier_t::year_type>(*tok.value);
break;
case lexer_t::token_t::TOK_A_MONTH:
specifier.month =
@@ -662,13 +840,13 @@ void date_parser_t::determine_when(date_parser_t::lexer_t::token_t& tok,
break;
case lexer_t::token_t::TOK_TODAY:
- specifier = date_specifier_t(CURRENT_DATE());
+ specifier = date_specifier_t(today);
break;
case lexer_t::token_t::TOK_TOMORROW:
- specifier = date_specifier_t(CURRENT_DATE() + gregorian::days(1));
+ specifier = date_specifier_t(today + gregorian::days(1));
break;
case lexer_t::token_t::TOK_YESTERDAY:
- specifier = date_specifier_t(CURRENT_DATE() - gregorian::days(1));
+ specifier = date_specifier_t(today - gregorian::days(1));
break;
default:
@@ -784,6 +962,9 @@ date_interval_t date_parser_t::parse()
date_t base(today);
date_t end(today);
+ if (! adjust)
+ adjust = 1;
+
tok = lexer.next_token();
switch (tok.kind) {
case lexer_t::token_t::TOK_YEARS:
@@ -1231,6 +1412,8 @@ bool date_interval_t::find_period(const date_t& date)
DEBUG("times.interval", "true: start = " << *start);
DEBUG("times.interval", "true: end_of_duration = " << *end_of_duration);
+ resolve_end();
+
return true;
}
@@ -1281,7 +1464,11 @@ void date_interval_t::dump(std::ostream& out)
if (duration)
out << _("duration: ") << duration->to_string() << std::endl;
- stabilize(begin());
+ optional<date_t> when(begin());
+ if (! when)
+ when = CURRENT_DATE();
+
+ stabilize(when);
out << std::endl
<< _("--- After stabilization ---") << std::endl;
@@ -1401,6 +1588,10 @@ date_parser_t::lexer_t::token_t date_parser_t::lexer_t::next_token()
string_to_day_of_week(term)) {
return token_t(token_t::TOK_A_WDAY, token_t::content_t(*wday));
}
+ else if (term == _("ago"))
+ return token_t(token_t::TOK_AGO);
+ else if (term == _("hence"))
+ return token_t(token_t::TOK_HENCE);
else if (term == _("from") || term == _("since"))
return token_t(token_t::TOK_SINCE);
else if (term == _("to") || term == _("until"))
diff --git a/src/times.h b/src/times.h
index ac96669d..1ff08739 100644
--- a/src/times.h
+++ b/src/times.h
@@ -68,14 +68,14 @@ inline bool is_valid(const date_t& moment) {
extern optional<datetime_t> epoch;
#ifdef BOOST_DATE_TIME_HAS_HIGH_PRECISION_CLOCK
-#define TRUE_CURRENT_TIME() (boost::posix_time::microsec_clock::universal_time())
+#define TRUE_CURRENT_TIME() (boost::posix_time::microsec_clock::local_time())
#define CURRENT_TIME() (epoch ? *epoch : TRUE_CURRENT_TIME())
#else
-#define TRUE_CURRENT_TIME() (boost::posix_time::second_clock::universal_time())
+#define TRUE_CURRENT_TIME() (boost::posix_time::second_clock::local_time())
#define CURRENT_TIME() (epoch ? *epoch : TRUE_CURRENT_TIME())
#endif
#define CURRENT_DATE() \
- (epoch ? epoch->date() : boost::gregorian::day_clock::universal_day())
+ (epoch ? epoch->date() : boost::gregorian::day_clock::local_day())
extern date_time::weekdays start_of_week;
diff --git a/src/value.cc b/src/value.cc
index 99837832..c34792b2 100644
--- a/src/value.cc
+++ b/src/value.cc
@@ -901,13 +901,10 @@ bool value_t::is_less_than(const value_t& val) const
switch (val.type()) {
case INTEGER:
case AMOUNT: {
- if (val.is_nonzero())
- break;
-
bool no_amounts = true;
foreach (const balance_t::amounts_map::value_type& pair,
as_balance().amounts) {
- if (pair.second >= 0L)
+ if (pair.second >= val)
return false;
no_amounts = false;
}
@@ -927,12 +924,9 @@ bool value_t::is_less_than(const value_t& val) const
switch (val.type()) {
case INTEGER:
case AMOUNT: {
- if (val.is_nonzero())
- break;
-
bool no_amounts = true;
foreach (const value_t& value, as_sequence()) {
- if (value >= 0L)
+ if (value >= val)
return false;
no_amounts = false;
}
@@ -1023,13 +1017,10 @@ bool value_t::is_greater_than(const value_t& val) const
switch (val.type()) {
case INTEGER:
case AMOUNT: {
- if (val.is_nonzero())
- break;
-
bool no_amounts = true;
foreach (const balance_t::amounts_map::value_type& pair,
as_balance().amounts) {
- if (pair.second <= 0L)
+ if (pair.second <= val)
return false;
no_amounts = false;
}
@@ -1049,12 +1040,9 @@ bool value_t::is_greater_than(const value_t& val) const
switch (val.type()) {
case INTEGER:
case AMOUNT: {
- if (val.is_nonzero())
- break;
-
bool no_amounts = true;
foreach (const value_t& value, as_sequence()) {
- if (value <= 0L)
+ if (value <= val)
return false;
no_amounts = false;
}
diff --git a/src/xact.cc b/src/xact.cc
index 4fe3e6e6..35d61edd 100644
--- a/src/xact.cc
+++ b/src/xact.cc
@@ -124,27 +124,13 @@ bool xact_base_t::finalize()
amount_t& p(post->cost ? *post->cost : post->amount);
if (! p.is_null()) {
DEBUG("xact.finalize", "post must balance = " << p.reduced());
- if (! post->cost && post->amount.has_annotation() &&
- post->amount.annotation().price) {
- // If the amount has no cost, but is annotated with a per-unit
- // price, use the price times the amount as the cost
- post->cost = (*post->amount.annotation().price *
- post->amount).unrounded();
- DEBUG("xact.finalize",
- "annotation price = " << *post->amount.annotation().price);
- DEBUG("xact.finalize", "amount = " << post->amount);
- DEBUG("xact.finalize", "priced cost = " << *post->cost);
- post->add_flags(POST_COST_CALCULATED);
- add_or_set_value(balance, post->cost->rounded().reduced());
- } else {
- // If the amount was a cost, it very likely has the "keep_precision"
- // flag set, meaning commodity display precision is ignored when
- // displaying the amount. We never want this set for the balance,
- // so we must clear the flag in a temporary to avoid it propagating
- // into the balance.
- add_or_set_value(balance, p.keep_precision() ?
- p.rounded().reduced() : p.reduced());
- }
+ // If the amount was a cost, it very likely has the
+ // "keep_precision" flag set, meaning commodity display precision
+ // is ignored when displaying the amount. We never want this set
+ // for the balance, so we must clear the flag in a temporary to
+ // avoid it propagating into the balance.
+ add_or_set_value(balance, p.keep_precision() ?
+ p.rounded().reduced() : p.reduced());
}
else if (null_post) {
throw_(std::logic_error,
@@ -173,14 +159,15 @@ bool xact_base_t::finalize()
add_post(null_post);
}
- if (balance.is_balance() &&
+ if (! null_post && balance.is_balance() &&
balance.as_balance().amounts.size() == 2) {
// When an xact involves two different commodities (regardless of how
// many posts there are) determine the conversion ratio by dividing the
// total value of one commodity by the total value of the other. This
// establishes the per-unit cost for this post for both commodities.
- DEBUG("xact.finalize", "there were exactly two commodities");
+ DEBUG("xact.finalize",
+ "there were exactly two commodities, and no null post");
bool saw_cost = false;
post_t * top_post = NULL;
@@ -268,55 +255,65 @@ bool xact_base_t::finalize()
posts_list copy(posts);
- foreach (post_t * post, copy) {
- if (! post->cost)
- continue;
+ if (has_date()) {
+ foreach (post_t * post, copy) {
+ if (! post->cost)
+ continue;
- if (post->amount.commodity() == post->cost->commodity())
- throw_(balance_error,
- _("A posting's cost must be of a different commodity than its amount"));
+ if (post->amount.commodity() == post->cost->commodity())
+ throw_(balance_error,
+ _("A posting's cost must be of a different commodity than its amount"));
- cost_breakdown_t breakdown =
- commodity_pool_t::current_pool->exchange
+ cost_breakdown_t breakdown =
+ commodity_pool_t::current_pool->exchange
(post->amount, *post->cost, false,
datetime_t(date(), time_duration(0, 0, 0, 0)));
- if (post->amount.has_annotation() &&
- breakdown.basis_cost.commodity() == breakdown.final_cost.commodity()) {
- if (amount_t gain_loss = breakdown.basis_cost - breakdown.final_cost) {
- DEBUG("xact.finalize", "gain_loss = " << gain_loss);
- gain_loss.in_place_round();
- DEBUG("xact.finalize", "gain_loss rounds to = " << gain_loss);
-
- if (post->must_balance())
- add_or_set_value(balance, gain_loss.reduced());
-
- account_t * account;
- if (gain_loss.sign() > 0)
- account = journal->find_account(_("Equity:Capital Gains"));
- else
- account = journal->find_account(_("Equity:Capital Losses"));
-
- post_t * p = new post_t(account, gain_loss, ITEM_GENERATED);
- p->set_state(post->state());
- if (post->has_flags(POST_VIRTUAL)) {
- DEBUG("xact.finalize", "gain_loss came from a virtual post");
- p->add_flags(post->flags() & (POST_VIRTUAL | POST_MUST_BALANCE));
+ if (post->amount.has_annotation() && post->amount.annotation().price) {
+ if (breakdown.basis_cost.commodity() == breakdown.final_cost.commodity()) {
+ if (amount_t gain_loss = breakdown.basis_cost - breakdown.final_cost) {
+ DEBUG("xact.finalize", "gain_loss = " << gain_loss);
+ gain_loss.in_place_round();
+ DEBUG("xact.finalize", "gain_loss rounds to = " << gain_loss);
+
+ if (post->must_balance())
+ add_or_set_value(balance, gain_loss.reduced());
+
+ account_t * account;
+ if (gain_loss.sign() > 0)
+ account = journal->find_account(_("Equity:Capital Gains"));
+ else
+ account = journal->find_account(_("Equity:Capital Losses"));
+
+ post_t * p = new post_t(account, gain_loss, ITEM_GENERATED);
+ p->set_state(post->state());
+ if (post->has_flags(POST_VIRTUAL)) {
+ DEBUG("xact.finalize", "gain_loss came from a virtual post");
+ p->add_flags(post->flags() & (POST_VIRTUAL | POST_MUST_BALANCE));
+ }
+ add_post(p);
+ DEBUG("xact.finalize", "added gain_loss, balance = " << balance);
+ } else {
+ DEBUG("xact.finalize", "gain_loss would have displayed as zero");
+ }
}
- add_post(p);
- DEBUG("xact.finalize", "added gain_loss, balance = " << balance);
} else {
- DEBUG("xact.finalize", "gain_loss would have display as zero");
+ if (post->amount.has_annotation()) {
+ if (breakdown.amount.has_annotation())
+ breakdown.amount.annotation().tag = post->amount.annotation().tag;
+ else
+ breakdown.amount.annotate
+ (annotation_t(none, none, post->amount.annotation().tag));
+ }
+ post->amount = breakdown.amount;
+ DEBUG("xact.finalize", "added breakdown, balance = " << balance);
}
- } else {
- post->amount = breakdown.amount;
- DEBUG("xact.finalize", "added breakdown, balance = " << balance);
- }
- if (post->has_flags(POST_COST_FIXATED) &&
- post->amount.has_annotation() && post->amount.annotation().price) {
- DEBUG("xact.finalize", "fixating annotation price");
- post->amount.annotation().add_flags(ANNOTATION_PRICE_FIXATED);
+ if (post->has_flags(POST_COST_FIXATED) &&
+ post->amount.has_annotation() && post->amount.annotation().price) {
+ DEBUG("xact.finalize", "fixating annotation price");
+ post->amount.annotation().add_flags(ANNOTATION_PRICE_FIXATED);
+ }
}
}