diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/account.cc | 4 | ||||
-rw-r--r-- | src/amount.cc | 2 | ||||
-rw-r--r-- | src/chain.cc | 8 | ||||
-rw-r--r-- | src/commodity.cc | 2 | ||||
-rw-r--r-- | src/filters.cc | 97 | ||||
-rw-r--r-- | src/global.cc | 3 | ||||
-rw-r--r-- | src/iterators.cc | 11 | ||||
-rw-r--r-- | src/iterators.h | 7 | ||||
-rw-r--r-- | src/report.cc | 3 | ||||
-rw-r--r-- | src/report.h | 40 | ||||
-rw-r--r-- | src/session.cc | 12 | ||||
-rw-r--r-- | src/temps.cc | 3 | ||||
-rw-r--r-- | src/temps.h | 4 | ||||
-rw-r--r-- | src/textual.cc | 15 | ||||
-rw-r--r-- | src/times.h | 8 | ||||
-rw-r--r-- | src/utils.cc | 20 | ||||
-rw-r--r-- | src/xact.cc | 15 |
17 files changed, 167 insertions, 87 deletions
diff --git a/src/account.cc b/src/account.cc index 4bcb0c25..57b66d86 100644 --- a/src/account.cc +++ b/src/account.cc @@ -42,8 +42,10 @@ account_t::~account_t() { TRACE_DTOR(account_t); - foreach (accounts_map::value_type& pair, accounts) + foreach (accounts_map::value_type& pair, accounts) { + assert(! pair.second->has_flags(ACCOUNT_TEMP)); checked_delete(pair.second); + } } account_t * account_t::find_account(const string& name, diff --git a/src/amount.cc b/src/amount.cc index 5aa985c8..a6788af9 100644 --- a/src/amount.cc +++ b/src/amount.cc @@ -107,6 +107,7 @@ void amount_t::initialize(shared_ptr<commodity_pool_t> pool) mpq_init(tempq); mpfr_init(tempf); mpfr_init(tempfb); + is_initialized = true; } current_pool = pool; @@ -126,6 +127,7 @@ void amount_t::shutdown() mpq_clear(tempq); mpfr_clear(tempf); mpfr_clear(tempfb); + is_initialized = false; } } diff --git a/src/chain.cc b/src/chain.cc index 25f4833c..018b3812 100644 --- a/src/chain.cc +++ b/src/chain.cc @@ -114,6 +114,10 @@ post_handler_ptr chain_post_handlers(report_t& report, else handler.reset(new sort_posts(handler, report.HANDLER(sort_).str())); } + else if (! report.HANDLED(period_) && + ! report.HANDLED(unsorted)) { + handler.reset(new sort_posts(handler, "date")); + } // collapse_posts causes xacts with multiple posts to appear as xacts // with a subtotaled post for each commodity used. @@ -135,6 +139,10 @@ post_handler_ptr chain_post_handlers(report_t& report, else if (report.HANDLED(subtotal)) handler.reset(new subtotal_posts(handler, expr)); } + else if (! report.HANDLED(period_) && + ! report.HANDLED(unsorted)) { + handler.reset(new sort_posts(handler, "date")); + } if (report.HANDLED(dow)) handler.reset(new dow_posts(handler, expr)); diff --git a/src/commodity.cc b/src/commodity.cc index 4041946b..54cb02d6 100644 --- a/src/commodity.cc +++ b/src/commodity.cc @@ -393,7 +393,7 @@ commodity_t::check_for_updated_price(const optional<price_point_t>& point, DEBUG("commodity.download", "moment = " << *moment); DEBUG("commodity.download", "slip.moment = " << seconds_diff); } else { - seconds_diff = (CURRENT_TIME() - point->when).total_seconds(); + seconds_diff = (TRUE_CURRENT_TIME() - point->when).total_seconds(); DEBUG("commodity.download", "slip.now = " << seconds_diff); } diff --git a/src/filters.cc b/src/filters.cc index 25649ad0..814cf276 100644 --- a/src/filters.cc +++ b/src/filters.cc @@ -686,6 +686,7 @@ void transfer_details::operator()(post_t& post) xact.payee = expr.calc(bound_scope).to_string(); break; case SET_ACCOUNT: + temp.account->remove_post(&temp); temp.account = master->find_account(expr.calc(bound_scope).to_string()); temp.account->add_post(&temp); break; @@ -805,6 +806,7 @@ void forecast_posts::add_post(const date_interval_t& period, post_t& post) { generate_posts::add_post(period, post); + // Advance the period's interval until it is at or beyond the current date. date_interval_t& i = pending_posts.back().first; if (! i.start) { if (! i.find_period(CURRENT_DATE())) @@ -819,59 +821,92 @@ void forecast_posts::add_post(const date_interval_t& period, post_t& post) void forecast_posts::flush() { posts_list passed; - date_t last; + date_t last = CURRENT_DATE(); + + // If there are period transactions to apply in a continuing series until + // the forecast condition is met, generate those transactions now. Note + // that no matter what, we abandon forecasting beyond the next 5 years. + // + // It works like this: + // + // Earlier, in forecast_posts::add_period_xacts, we cut up all the periodic + // transactions into their components postings, so that we have N "periodic + // postings". For example, if the user had this: + // + // ~ daily + // Expenses:Food $10 + // Expenses:Auto:Gas $20 + // ~ monthly + // Expenses:Food $100 + // Expenses:Auto:Gas $200 + // + // We now have 4 periodic postings in `pending_posts'. + // + // Each periodic postings gets its own copy of its parent transaction's + // period, which is modified as we go. This is found in the second member + // of the pending_posts_list for each posting. + // + // The algorithm below works by iterating through the N periodic postings + // over and over, until each of them mets the termination critera for the + // forecast and is removed from the set. while (pending_posts.size() > 0) { + // At each step through the loop, we find the first periodic posting whose + // period contains the earliest starting date. pending_posts_list::iterator least = pending_posts.begin(); for (pending_posts_list::iterator i = ++pending_posts.begin(); i != pending_posts.end(); - i++) + i++) { if (*(*i).first.start < *(*least).first.start) least = i; + } date_t& begin = *(*least).first.start; + if ((*least).first.end) + assert(begin < *(*least).first.end); + + // If the next date in the series for this periodic posting is more than 5 + // years beyond the last valid post we generated, drop it from further + // consideration. + date_t next = *(*least).first.next; + assert(next > begin); - if ((*least).first.end && begin >= *(*least).first.end) { + if ((next - last).days() > 365 * 5) { + DEBUG("filters.forecast", + "Forecast transaction exceeds 5 years beyond today"); pending_posts.erase(least); - passed.remove((*least).second); continue; } - post_t& post = *(*least).second; + begin = next; + ++(*least).first; + // `post' refers to the posting defined in the period transaction. We + // make a copy of it within a temporary transaction with the payee + // "Forecast transaction". + post_t& post = *(*least).second; xact_t& xact = temps.create_xact(); - xact.payee = _("Forecast transaction"); - xact._date = begin; - + xact.payee = _("Forecast transaction"); + xact._date = begin; post_t& temp = temps.copy_post(post, xact); - date_t next = *(*least).first.next; - ++(*least).first; - - if (next < begin || (is_valid(last) && (next - last).days() > 365 * 5)) - break; - begin = next; - + // Submit the generated posting + DEBUG("filters.forecast", + "Forecast transaction: " << temp.date() + << " " << temp.account->fullname() + << " " << temp.amount); item_handler<post_t>::operator()(temp); + // If the generated posting matches the user's report query, check whether + // it also fails to match the continuation condition for the forecast. If + // it does, drop this periodic posting from consideration. if (temp.has_xdata() && temp.xdata().has_flags(POST_EXT_MATCHES)) { + DEBUG("filters.forecast", " matches report query"); bind_scope_t bound_scope(context, temp); - if (! pred(bound_scope)) - break; - last = temp.date(); - passed.clear(); - } else { - bool found = false; - foreach (post_t * x, passed) - if (x == &post) { - found = true; - break; - } - - if (! found) { - passed.push_back(&post); - if (passed.size() >= pending_posts.size()) - break; + if (! pred(bound_scope)) { + DEBUG("filters.forecast", " fails to match continuation criteria"); + pending_posts.erase(least); + continue; } } } diff --git a/src/global.cc b/src/global.cc index 2ce73bae..19cab1c8 100644 --- a/src/global.cc +++ b/src/global.cc @@ -418,6 +418,9 @@ void global_scope_t::normalize_report_options(const string& verb) report_t& rep(report()); + if (! rep.HANDLED(no_color)) + rep.HANDLER(color).on_only(string("?normalize")); + // jww (2009-02-09): These globals are a hack, but hard to avoid. item_t::use_effective_date = (rep.HANDLED(effective) && ! rep.HANDLED(actual_dates)); diff --git a/src/iterators.cc b/src/iterators.cc index 020f70b0..e9560cc0 100644 --- a/src/iterators.cc +++ b/src/iterators.cc @@ -108,24 +108,19 @@ void posts_commodities_iterator::reset(journal_t& journal) if (i != xacts_by_commodity.end()) { xact = (*i).second; } else { - xact_temps.push_back(new xact_t); - xact = xact_temps.back(); + xact = &temps.create_xact(); + xact_temps.push_back(xact); xact->payee = symbol; xact->_date = hpair.first.date(); xacts_by_commodity.insert (std::pair<string, xact_t *>(symbol, xact)); } - post_temps.push_back(post_t(account)); - post_t& temp = post_temps.back(); + post_t& temp = temps.create_post(*xact, account); temp._date = hpair.first.date(); - temp.xact = xact; temp.amount = hpair.second; - temp.set_flags(ITEM_GENERATED | ITEM_TEMP); temp.xdata().datetime = hpair.first; - - xact->add_post(&temp); } } } diff --git a/src/iterators.h b/src/iterators.h index a1563539..8aa1b451 100644 --- a/src/iterators.h +++ b/src/iterators.h @@ -49,6 +49,7 @@ #include "xact.h" #include "post.h" #include "account.h" +#include "temps.h" namespace ledger { @@ -172,9 +173,7 @@ protected: journal_posts_iterator journal_posts; xacts_iterator xacts; xact_posts_iterator posts; - - std::list<post_t> post_temps; - std::list<account_t> acct_temps; + temporaries_t temps; xacts_list xact_temps; public: @@ -187,8 +186,6 @@ public: } virtual ~posts_commodities_iterator() throw() { TRACE_DTOR(posts_commodities_iterator); - foreach (xact_t * xact, xact_temps) - checked_delete(xact); } void reset(journal_t& journal); diff --git a/src/report.cc b/src/report.cc index e6194541..62b54ad1 100644 --- a/src/report.cc +++ b/src/report.cc @@ -446,7 +446,7 @@ option_t<report_t> * report_t::lookup_option(const char * p) OPT_CH(cleared); break; case 'D': - OPT_CH(deviation); + OPT_CH(daily); break; case 'E': OPT_CH(empty); @@ -650,6 +650,7 @@ option_t<report_t> * report_t::lookup_option(const char * p) OPT(unbudgeted); else OPT(uncleared); else OPT(unround); + else OPT(unsorted); break; case 'w': OPT(weekly); diff --git a/src/report.h b/src/report.h index 31f65c4f..294af19a 100644 --- a/src/report.h +++ b/src/report.h @@ -290,6 +290,7 @@ public: HANDLER(unbudgeted).report(out); HANDLER(uncleared).report(out); HANDLER(unround).report(out); + HANDLER(unsorted).report(out); HANDLER(weekly).report(out); HANDLER(wide).report(out); HANDLER(yearly).report(out); @@ -450,7 +451,7 @@ public: parent->HANDLER(limit_).on(string("--current"), "date<=today"); }); - OPTION_(report_t, daily, DO() { + OPTION_(report_t, daily, DO() { // -D parent->HANDLER(period_).on(string("--daily"), "daily"); }); @@ -463,7 +464,7 @@ public: string("depth<=") + args.get<string>(1)); }); - OPTION_(report_t, deviation, DO() { // -D + OPTION_(report_t, deviation, DO() { parent->HANDLER(display_total_) .set_expr(string("--deviation"), "amount_expr-total_expr/count"); }); @@ -628,7 +629,32 @@ public: }); OPTION(report_t, output_); // -o - OPTION(report_t, pager_); + + OPTION__ + (report_t, pager_, + CTOR(report_t, pager_) { + if (! std::getenv("PAGER")) { + bool have_less = false; + if (exists(path("/opt/local/bin/less")) || + exists(path("/usr/local/bin/less")) || + exists(path("/usr/bin/less"))) + have_less = true; + + if (have_less) { + on(none, "less"); + setenv("LESS", "--quit-if-one-screen -R", 0); + } + } + } + virtual void on_with(const optional<string>& whence, const value_t& text) { + string cmd(text.to_string()); + if (cmd == "" || cmd == "false" || cmd == "off" || + cmd == "none" || cmd == "no" || cmd == "disable") + option_t<report_t>::off(); + else + option_t<report_t>::on_with(whence, text); + }); + OPTION(report_t, payee_as_account); OPTION_(report_t, pending, DO() { // -C @@ -690,12 +716,12 @@ public: " \"\") %(payee)%(xact.comment)\n" " %(xact.uncleared ?" " (cleared ? \"* \" : (pending ? \"! \" : \"\")) : \"\")" - "%-34(account)" - " %12(calculated ? \"\" : justify(scrub(amount), 12, -1, true))" + "%(calculated ? account : justify(account, 34, -1, false))" + "%(calculated ? \"\" : \" \" + justify(scrub(amount), 12, -1, true))" "%(has_cost & !cost_calculated ?" " \" @ \" + justify(scrub(abs(cost / amount)), 0) : \"\")" "%(comment)\n%/" - " %$7%$8 %$9%$A%$B\n%/\n"); + " %$7%$8%$9%$A%$B\n%/\n"); }); OPTION_(report_t, quantity, DO() { // -O @@ -821,6 +847,8 @@ public: .set_expr(string("--unround"), "unrounded(total_expr)"); }); + OPTION(report_t, unsorted); + OPTION_(report_t, weekly, DO() { // -W parent->HANDLER(period_).on(string("--weekly"), "weekly"); }); diff --git a/src/session.cc b/src/session.cc index b46d545e..2e728dd6 100644 --- a/src/session.cc +++ b/src/session.cc @@ -121,8 +121,16 @@ std::size_t session_t::read_journal(const path& pathname, std::size_t session_t::read_data(const string& master_account) { - if (HANDLER(file_).data_files.empty()) - throw_(parse_error, "No journal file was specified (please use -f)"); + if (HANDLER(file_).data_files.empty()) { + path file; + if (const char * home_var = std::getenv("HOME")) + file = path(home_var) / ".ledger"; + + if (! file.empty() && exists(file)) + HANDLER(file_).data_files.push_back(file); + else + throw_(parse_error, "No journal file was specified (please use -f)"); + } std::size_t xact_count = 0; diff --git a/src/temps.cc b/src/temps.cc index a9ecc2a1..f2dee159 100644 --- a/src/temps.cc +++ b/src/temps.cc @@ -31,6 +31,9 @@ #include <system.hh> +#include "xact.h" +#include "post.h" +#include "account.h" #include "temps.h" namespace ledger { diff --git a/src/temps.h b/src/temps.h index acd9cbd7..646f4dcd 100644 --- a/src/temps.h +++ b/src/temps.h @@ -46,10 +46,6 @@ #ifndef _TEMPS_H #define _TEMPS_H -#include "xact.h" -#include "post.h" -#include "account.h" - namespace ledger { class temporaries_t diff --git a/src/textual.cc b/src/textual.cc index 26500596..4daecc79 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -1014,21 +1014,8 @@ post_t * instance_t::parse_post(char * line, if (! diff.is_zero()) { if (! post->amount.is_null()) { diff -= post->amount; - if (! diff.is_zero()) { -#if 1 + if (! diff.is_zero()) throw_(parse_error, _("Balance assertion off by %1") << diff); -#else - // This code, rather than issuing an error if a balance assignment - // fails, creates a balancing transaction that causes the - // assertion to be true. - post_t * temp = new post_t(post->account, diff, - ITEM_GENERATED | POST_CALCULATED); - xact->add_post(temp); - - DEBUG("textual.parse", "line " << linenum << ": " - << "Created balancing posting"); -#endif - } } else { post->amount = diff; DEBUG("textual.parse", "line " << linenum << ": " diff --git a/src/times.h b/src/times.h index 035d86cd..69e3af51 100644 --- a/src/times.h +++ b/src/times.h @@ -69,11 +69,11 @@ inline bool is_valid(const date_t& moment) { extern optional<datetime_t> epoch; #ifdef BOOST_DATE_TIME_HAS_HIGH_PRECISION_CLOCK -#define CURRENT_TIME() \ - (epoch ? *epoch : boost::posix_time::microsec_clock::universal_time()) +#define TRUE_CURRENT_TIME() (boost::posix_time::microsec_clock::universal_time()) +#define CURRENT_TIME() (epoch ? *epoch : TRUE_CURRENT_TIME()) #else -#define CURRENT_TIME() \ - (epoch ? *epoch : boost::posix_time::second_clock::universal_time()) +#define TRUE_CURRENT_TIME() (boost::posix_time::second_clock::universal_time()) +#define CURRENT_TIME() (epoch ? *epoch : TRUE_CURRENT_TIME()) #endif #define CURRENT_DATE() \ (epoch ? epoch->date() : boost::gregorian::day_clock::universal_day()) diff --git a/src/utils.cc b/src/utils.cc index 85a7aa46..c68737dc 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -190,8 +190,15 @@ static void trace_delete_func(void * ptr, const char * which) i = freed_memory->find(ptr); if (i != freed_memory->end()) VERIFY(! "Freeing a block of memory twice"); +#if 0 + // There can be memory allocated by Boost or the standard library, which + // was allocated before memory tracing got turned on, that the system + // might free for some coincidental reason. As such, we can't rely on + // this check being valid. I've seen cases where processes ran to + // completion with it on, and then others where valid processes failed. else VERIFY(! "Freeing an unknown block of memory"); +#endif memory_tracing_active = true; return; } @@ -531,14 +538,15 @@ bool logger_func(log_level_t level) { if (! logger_has_run) { logger_has_run = true; - logger_start = CURRENT_TIME(); + logger_start = TRUE_CURRENT_TIME(); IF_VERIFY() *_log_stream << " TIME OBJSZ MEMSZ" << std::endl; } *_log_stream << std::right << std::setw(5) - << (CURRENT_TIME() - logger_start).total_milliseconds() << "ms"; + << (TRUE_CURRENT_TIME() - + logger_start).total_milliseconds() << "ms"; #if defined(VERIFY_ON) IF_VERIFY() { @@ -616,7 +624,7 @@ struct timer_t bool active; timer_t(log_level_t _level, std::string _description) - : level(_level), begin(CURRENT_TIME()), + : level(_level), begin(TRUE_CURRENT_TIME()), spent(time_duration(0, 0, 0, 0)), description(_description), active(true) {} }; @@ -637,7 +645,7 @@ void start_timer(const char * name, log_level_t lvl) timers.insert(timer_map::value_type(name, timer_t(lvl, _log_buffer.str()))); } else { assert((*i).second.description == _log_buffer.str()); - (*i).second.begin = CURRENT_TIME(); + (*i).second.begin = TRUE_CURRENT_TIME(); (*i).second.active = true; } _log_buffer.str(""); @@ -657,7 +665,7 @@ void stop_timer(const char * name) timer_map::iterator i = timers.find(name); assert(i != timers.end()); - (*i).second.spent += CURRENT_TIME() - (*i).second.begin; + (*i).second.spent += TRUE_CURRENT_TIME() - (*i).second.begin; (*i).second.active = false; #if defined(VERIFY_ON) @@ -682,7 +690,7 @@ void finish_timer(const char * name) time_duration spent = (*i).second.spent; if ((*i).second.active) { - spent = CURRENT_TIME() - (*i).second.begin; + spent = TRUE_CURRENT_TIME() - (*i).second.begin; (*i).second.active = false; } diff --git a/src/xact.cc b/src/xact.cc index 6ea8d8f9..9f118ec2 100644 --- a/src/xact.cc +++ b/src/xact.cc @@ -49,16 +49,23 @@ xact_base_t::~xact_base_t() { TRACE_DTOR(xact_base_t); - foreach (post_t * post, posts) { - // If the posting is a temporary, it will be destructed when the - // temporary is. - if (! post->has_flags(ITEM_TEMP)) + if (! has_flags(ITEM_TEMP)) { + foreach (post_t * post, posts) { + // If the posting is a temporary, it will be destructed when the + // temporary is. + assert(! post->has_flags(ITEM_TEMP)); checked_delete(post); + } } } void xact_base_t::add_post(post_t * post) { + // You can add temporary postings to transactions, but not real postings to + // temporary transactions. + if (! post->has_flags(ITEM_TEMP)) + assert(! has_flags(ITEM_TEMP)); + posts.push_back(post); } |