diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/account.cc | 2 | ||||
-rw-r--r-- | src/amount.cc | 13 | ||||
-rw-r--r-- | src/annotate.cc | 50 | ||||
-rw-r--r-- | src/chain.cc | 14 | ||||
-rw-r--r-- | src/commodity.h | 23 | ||||
-rw-r--r-- | src/convert.cc | 26 | ||||
-rw-r--r-- | src/expr.cc | 5 | ||||
-rw-r--r-- | src/filters.cc | 120 | ||||
-rw-r--r-- | src/filters.h | 41 | ||||
-rw-r--r-- | src/format.cc | 2 | ||||
-rw-r--r-- | src/item.cc | 45 | ||||
-rw-r--r-- | src/item.h | 22 | ||||
-rw-r--r-- | src/output.cc | 7 | ||||
-rw-r--r-- | src/pool.cc | 20 | ||||
-rw-r--r-- | src/post.cc | 28 | ||||
-rw-r--r-- | src/post.h | 20 | ||||
-rw-r--r-- | src/precmd.cc | 19 | ||||
-rw-r--r-- | src/predicate.cc | 6 | ||||
-rw-r--r-- | src/predicate.h | 20 | ||||
-rw-r--r-- | src/print.cc | 2 | ||||
-rw-r--r-- | src/query.cc | 207 | ||||
-rw-r--r-- | src/query.h | 108 | ||||
-rw-r--r-- | src/report.cc | 99 | ||||
-rw-r--r-- | src/report.h | 71 | ||||
-rw-r--r-- | src/scope.h | 2 | ||||
-rw-r--r-- | src/session.cc | 10 | ||||
-rw-r--r-- | src/textual.cc | 14 | ||||
-rw-r--r-- | src/times.cc | 209 | ||||
-rw-r--r-- | src/times.h | 6 | ||||
-rw-r--r-- | src/value.cc | 20 | ||||
-rw-r--r-- | src/xact.cc | 123 |
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; } } @@ -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) { @@ -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); + } } } |