diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/amount.cc | 4 | ||||
-rw-r--r-- | src/derive.cc | 489 | ||||
-rw-r--r-- | src/item.cc | 3 | ||||
-rw-r--r-- | src/report.cc | 16 | ||||
-rw-r--r-- | src/report.h | 1 | ||||
-rw-r--r-- | src/times.cc | 18 | ||||
-rw-r--r-- | src/xact.cc | 2 |
7 files changed, 359 insertions, 174 deletions
diff --git a/src/amount.cc b/src/amount.cc index 2f28e2d4..3fb8ddaf 100644 --- a/src/amount.cc +++ b/src/amount.cc @@ -263,9 +263,9 @@ amount_t& amount_t::operator+=(const amount_t& amt) if (! quantity || ! amt.quantity) { if (quantity) - throw_(amount_error, "Cannot add an amount to an uninitialized amount"); - else if (amt.quantity) throw_(amount_error, "Cannot add an uninitialized amount to an amount"); + else if (amt.quantity) + throw_(amount_error, "Cannot add an amount to an uninitialized amount"); else throw_(amount_error, "Cannot add two uninitialized amounts"); } diff --git a/src/derive.cc b/src/derive.cc index ec9b1ed8..979c6033 100644 --- a/src/derive.cc +++ b/src/derive.cc @@ -34,204 +34,373 @@ namespace ledger { -value_t entry_command(call_scope_t& args) -{ - report_t& report(find_scope<report_t>(args)); - scoped_ptr<entry_t> entry(derive_new_entry(report, args.begin(), - args.end())); - xact_handler_ptr handler - (new format_xacts(report, report.HANDLER(print_format_).str())); - - report.entry_report(handler, *entry.get()); - - return true; -} - -entry_t * derive_new_entry(report_t& report, - value_t::sequence_t::const_iterator i, - value_t::sequence_t::const_iterator end) -{ - session_t& session(report.session); +namespace { + struct entry_template_t + { + optional<date_t> date; + optional<date_t> eff_date; + item_t::state_t state; + optional<string> code; + optional<string> note; + mask_t payee_mask; + + struct xact_template_t { + bool from; + optional<mask_t> account_mask; + optional<amount_t> amount; + + xact_template_t() : from(false) {} + }; + + std::list<xact_template_t> xacts; + + entry_template_t() : state(item_t::UNCLEARED) {} + + void dump(std::ostream& out) const + { + if (date) + out << "Date: " << *date << std::endl; + else + out << "Date: <today>" << std::endl; + + if (eff_date) + out << "Effective: " << *eff_date << std::endl; + + out << "State: "; + switch (state) { + case item_t::UNCLEARED: + out << "uncleared" << std::endl; + break; + case item_t::CLEARED: + out << "cleared" << std::endl; + break; + case item_t::PENDING: + out << "pending" << std::endl; + break; + } - std::auto_ptr<entry_t> added(new entry_t); + if (code) + out << "Code: " << *code << std::endl; + if (note) + out << "Note: " << *note << std::endl; - entry_t * matching = NULL; + if (payee_mask.empty()) + out << "Payee mask: INVALID (template expression will cause an error)" + << std::endl; + else + out << "Payee mask: " << payee_mask << std::endl; - added->_date = parse_date((*i++).to_string()); - if (i == end) - throw std::runtime_error("Too few arguments to 'entry'"); + if (xacts.empty()) { + out << std::endl + << "<Transaction copied from last related entry>" + << std::endl; + } else { + bool has_only_from = true; + bool has_only_to = true; + + foreach (const xact_template_t& xact, xacts) { + if (xact.from) + has_only_to = false; + else + has_only_from = false; + } - mask_t regexp((*i++).to_string()); + foreach (const xact_template_t& xact, xacts) { + out << std::endl + << "[Transaction \"" << (xact.from ? "from" : "to") + << "\"]" << std::endl; - entries_list::reverse_iterator j; + if (xact.account_mask) + out << " Account mask: " << *xact.account_mask << std::endl; + else if (xact.from) + out << " Account mask: <use last of last related accounts>" << std::endl; + else + out << " Account mask: <use first of last related accounts>" << std::endl; - for (j = report.session.journal->entries.rbegin(); - j != report.session.journal->entries.rend(); - j++) { - if (regexp.match((*j)->payee)) { - matching = *j; - break; + if (xact.amount) + out << " Amount: " << *xact.amount << std::endl; + } + } } - } + }; + + entry_template_t + args_to_entry_template(value_t::sequence_t::const_iterator begin, + value_t::sequence_t::const_iterator end) + { + regex date_mask("([0-9]+(?:[-/.][0-9]+)?(?:[-/.][0-9]+))?(?:=.*)?"); + smatch what; + + entry_template_t tmpl; + bool check_for_date = true; + + entry_template_t::xact_template_t * xact = NULL; + + for (; begin != end; begin++) { + if (check_for_date && + regex_match((*begin).to_string(), what, date_mask)) { + tmpl.date = parse_date(what[0]); + if (what.size() == 2) + tmpl.eff_date = parse_date(what[1]); + check_for_date = false; + } else { + string arg = (*begin).to_string(); - added->payee = matching ? matching->payee : regexp.expr.str(); + if (arg == "at") { + tmpl.payee_mask = (*++begin).to_string(); + } + else if (arg == "to" || arg == "from") { + if (! xact || xact->account_mask) { + tmpl.xacts.push_back(entry_template_t::xact_template_t()); + xact = &tmpl.xacts.back(); + } + xact->account_mask = mask_t((*++begin).to_string()); + xact->from = arg == "from"; + } + else if (arg == "on") { + tmpl.date = parse_date((*++begin).to_string()); + check_for_date = false; + } + else if (arg == "code") { + tmpl.code = (*++begin).to_string(); + } + else if (arg == "note") { + tmpl.note = (*++begin).to_string(); + } + else { + // Without a preposition, it is either: + // + // A payee, if we have not seen one + // An account or an amount, if we have + // An account if an amount has just been seen + // An amount if an account has just been seen + + if (tmpl.payee_mask.empty()) { + tmpl.payee_mask = arg; + } + else { + amount_t amt; + optional<mask_t> account; + + if (! amt.parse(arg, amount_t::PARSE_SOFT_FAIL | + amount_t::PARSE_NO_MIGRATE)) + account = mask_t(arg); + + if (! xact || + (account && xact->account_mask) || + (! account && xact->amount)) { + tmpl.xacts.push_back(entry_template_t::xact_template_t()); + xact = &tmpl.xacts.back(); + } - string arg = (*i).to_string(); - if (! matching) { - account_t * acct; - if (i == end || (arg[0] == '-' || std::isdigit(arg[0]))) { - acct = session.master->find_account("Expenses"); - } - else if (i != end) { - acct = session.master->find_account_re(arg); - if (! acct) - acct = session.master->find_account(arg); - assert(acct); - i++; + if (account) { + xact->from = false; + xact->account_mask = account; + } else { + xact->amount = amt; + } + } + } + } } - if (i == end) { - added->add_xact(new xact_t(acct)); - } else { - xact_t * xact = new xact_t(acct, amount_t((*i++).to_string())); - added->add_xact(xact); - - if (! xact->amount.commodity()) { - // If the amount has no commodity, we can determine it given - // the account by creating a final for the account and then - // checking if it contains only a single commodity. An - // account to which only dollars are applied would imply that - // dollars are wanted now too. + if (! tmpl.xacts.empty()) { + bool has_only_from = true; + bool has_only_to = true; - report.sum_all_accounts(); + foreach (entry_template_t::xact_template_t& xact, tmpl.xacts) { + if (xact.from) + has_only_to = false; + else + has_only_from = false; + } - value_t total = acct->xdata().total; - if (total.is_type(value_t::AMOUNT)) - xact->amount.set_commodity(total.as_amount().commodity()); + if (has_only_from) { + tmpl.xacts.push_front(entry_template_t::xact_template_t()); + } + else if (has_only_to) { + tmpl.xacts.push_back(entry_template_t::xact_template_t()); + tmpl.xacts.back().from = true; } } - acct = NULL; + return tmpl; + } - if (i != end) { - if (! acct) - acct = session.master->find_account_re((*i).to_string()); - if (! acct) - acct = session.master->find_account((*i).to_string()); - } + entry_t * derive_entry_from_template(entry_template_t& tmpl, + report_t& report) + { + if (tmpl.payee_mask.empty()) + throw std::runtime_error("'entry' command requires at least a payee"); - if (! acct) { - if (matching && matching->journal->basket) - acct = matching->journal->basket; - else - acct = session.master->find_account("Equity"); - } + entry_t * matching = NULL; + journal_t& journal(*report.session.journal.get()); - added->add_xact(new xact_t(acct)); - } - else if (i == end) { - // If no argument were given but the payee, assume the user wants - // to see the same xact as last time. - added->code = matching->code; + std::auto_ptr<entry_t> added(new entry_t); - foreach (xact_t * xact, matching->xacts) - added->add_xact(new xact_t(*xact)); - } - else if (arg[0] == '-' || std::isdigit(arg[0])) { - xact_t * m_xact, * xact, * first; - m_xact = matching->xacts.front(); + entries_list::reverse_iterator j; - first = xact = new xact_t(m_xact->account, amount_t((*i++).to_string())); - added->add_xact(xact); + for (j = journal.entries.rbegin(); + j != journal.entries.rend(); + j++) { + if (tmpl.payee_mask.match((*j)->payee)) { + matching = *j; + break; + } + } - if (! xact->amount.commodity()) - xact->amount.set_commodity(m_xact->amount.commodity()); + if (! tmpl.date) + added->_date = CURRENT_DATE(); + else + added->_date = tmpl.date; - m_xact = matching->xacts.back(); + if (tmpl.eff_date) + added->_date_eff = tmpl.eff_date; - xact = new xact_t(m_xact->account, - first->amount); - added->add_xact(xact); + added->set_state(tmpl.state); - if (i != end) { - account_t * acct = session.master->find_account_re((*i).to_string()); - if (! acct) - acct = session.master->find_account((*i).to_string()); - assert(acct); - added->xacts.back()->account = acct; + if (matching) { + added->payee = matching->payee; + added->code = matching->code; + added->note = matching->note; + } else { + added->payee = tmpl.payee_mask.expr.str(); } - } - else { - account_t * draw_acct = NULL; - - while (i != end) { - string re_pat((*i++).to_string()); - account_t * acct = NULL; - amount_t * amt = NULL; - - mask_t acct_regex(re_pat); - - for (; j != matching->journal->entries.rend(); j++) - if (regexp.match((*j)->payee)) { - entry_t * entry = *j; - foreach (xact_t * xact, entry->xacts) - if (acct_regex.match(xact->account->fullname())) { - acct = xact->account; - amt = &xact->amount; - matching = entry; - goto found; + + if (tmpl.code) + added->code = tmpl.code; + if (tmpl.note) + added->note = tmpl.note; + + if (tmpl.xacts.empty()) { + if (matching) { + foreach (xact_t * xact, matching->xacts) + added->add_xact(new xact_t(*xact)); + } else { + throw_(std::runtime_error, + "No accounts, and no past entry matching '" << tmpl.payee_mask <<"'"); + } + } else { + foreach (entry_template_t::xact_template_t& xact, tmpl.xacts) { + std::auto_ptr<xact_t> new_xact; + + commodity_t * found_commodity = NULL; + + if (matching) { + if (xact.account_mask) { + foreach (xact_t * x, matching->xacts) { + if (xact.account_mask->match(x->account->fullname())) { + new_xact.reset(new xact_t(*x)); + break; + } } + } else { + if (xact.from) + new_xact.reset(new xact_t(*matching->xacts.back())); + else + new_xact.reset(new xact_t(*matching->xacts.front())); + } + if (new_xact.get()) { + found_commodity = &new_xact->amount.commodity(); + // Ignore the past amount from these transactions + new_xact->amount = amount_t(); + } } - found: - xact_t * xact; - if (i == end) { - if (amt) - xact = new xact_t(acct, *amt); - else - xact = new xact_t(acct); - } else { - amount_t amount((*i++).to_string()); - - value_t::sequence_t::const_iterator x = i; - if (i != end && ++x == end) { - draw_acct = session.master->find_account_re((*i).to_string()); - if (! draw_acct) - draw_acct = session.master->find_account((*i).to_string()); - i++; + if (! new_xact.get()) + new_xact.reset(new xact_t); + + if (! new_xact->account) { + if (xact.account_mask) { + account_t * acct = NULL; + if (! acct) + acct = journal.find_account_re(xact.account_mask->expr.str()); + if (! acct) + acct = journal.find_account(xact.account_mask->expr.str()); + new_xact->account = acct; + + // Find out the default commodity to use by looking at the last + // commodity used in that account + entries_list::reverse_iterator j; + + for (j = journal.entries.rbegin(); + j != journal.entries.rend(); + j++) { + foreach (xact_t * x, (*j)->xacts) { + if (x->account == acct && ! x->amount.is_null()) { + found_commodity = &x->amount.commodity(); + break; + } + } + } + } else { + if (xact.from) + new_xact->account = journal.find_account("Liabilities:Unknown"); + else + new_xact->account = journal.find_account("Expenses:Unknown"); + } + } + + if (xact.amount) { + new_xact->amount = *xact.amount; + if (xact.from) + new_xact->amount.in_place_negate(); } - if (! acct) - acct = session.master->find_account_re(re_pat); - if (! acct) - acct = session.master->find_account(re_pat); - - xact = new xact_t(acct, amount); - if (! xact->amount.commodity()) { - if (amt) - xact->amount.set_commodity(amt->commodity()); - else if (amount_t::current_pool->default_commodity) - xact->amount.set_commodity(*amount_t::current_pool->default_commodity); + if (found_commodity && + ! new_xact->amount.is_null() && + ! new_xact->amount.has_commodity()) { + new_xact->amount.set_commodity(*found_commodity); + new_xact->amount = new_xact->amount.rounded(); } + + added->add_xact(new_xact.release()); } - added->add_xact(xact); } - if (! draw_acct) { - assert(matching->xacts.back()->account); - draw_acct = matching->xacts.back()->account; - } - if (draw_acct) - added->add_xact(new xact_t(draw_acct)); + if (! journal.entry_finalize_hooks.run_hooks(*added.get(), false) || + ! added->finalize() || + ! journal.entry_finalize_hooks.run_hooks(*added.get(), true)) + throw std::runtime_error("Failed to finalize derived entry (check commodities)"); + + return added.release(); } +} + +value_t template_command(call_scope_t& args) +{ + report_t& report(find_scope<report_t>(args)); + std::ostream& out(report.output_stream); + + value_t::sequence_t::const_iterator begin = args.value().begin(); + value_t::sequence_t::const_iterator end = args.value().end(); + + out << "--- Input arguments ---" << std::endl; + args.value().dump(out); + out << std::endl << std::endl; + + entry_template_t tmpl = args_to_entry_template(begin, end); - if ((matching && - ! matching->journal->entry_finalize_hooks.run_hooks(*added, false)) || - ! added->finalize() || - (matching && - ! matching->journal->entry_finalize_hooks.run_hooks(*added, true))) - throw std::runtime_error("Failed to finalize derived entry (check commodities)"); + out << "--- Entry template ---" << std::endl; + tmpl.dump(out); - return added.release(); + return true; +} + +value_t entry_command(call_scope_t& args) +{ + value_t::sequence_t::const_iterator begin = args.value().begin(); + value_t::sequence_t::const_iterator end = args.value().end(); + + report_t& report(find_scope<report_t>(args)); + entry_template_t tmpl = args_to_entry_template(begin, end); + std::auto_ptr<entry_t> new_entry(derive_entry_from_template(tmpl, report)); + + report.entry_report(xact_handler_ptr + (new format_xacts(report, + report.HANDLER(print_format_).str())), + *new_entry.get()); + return true; } } // namespace ledger diff --git a/src/item.cc b/src/item.cc index 22e24738..48694727 100644 --- a/src/item.cc +++ b/src/item.cc @@ -329,6 +329,9 @@ bool item_t::valid() const string item_context(const item_t& item, const string& desc) { std::size_t len = item.end_pos - item.beg_pos; + if (! len) + return "<no item context>"; + assert(len > 0); assert(len < 2048); diff --git a/src/report.cc b/src/report.cc index 78d91fdf..e319c8aa 100644 --- a/src/report.cc +++ b/src/report.cc @@ -586,12 +586,6 @@ expr_t::ptr_op_t report_t::lookup(const string& name) if (is_eq(p, "args")) return WRAP_FUNCTOR(args_command); break; - case 'p': - if (is_eq(p, "parse")) - return WRAP_FUNCTOR(parse_command); - else if (is_eq(p, "period")) - return WRAP_FUNCTOR(period_command); - break; case 'e': if (is_eq(p, "eval")) return WRAP_FUNCTOR(eval_command); @@ -600,6 +594,16 @@ expr_t::ptr_op_t report_t::lookup(const string& name) if (is_eq(p, "format")) return WRAP_FUNCTOR(format_command); break; + case 'p': + if (is_eq(p, "parse")) + return WRAP_FUNCTOR(parse_command); + else if (is_eq(p, "period")) + return WRAP_FUNCTOR(period_command); + break; + case 't': + if (is_eq(p, "template")) + return WRAP_FUNCTOR(template_command); + break; } } else if (is_eq(p, "print_balance")) diff --git a/src/report.h b/src/report.h index 4a99fe74..15ee82c6 100644 --- a/src/report.h +++ b/src/report.h @@ -394,6 +394,7 @@ public: // jww (2009-02-10): These should perhaps live elsewhere value_t entry_command(call_scope_t& args); +value_t template_command(call_scope_t& args); entry_t * derive_new_entry(report_t& report, value_t::sequence_t::const_iterator i, diff --git a/src/times.cc b/src/times.cc index fe4d2b53..53be925f 100644 --- a/src/times.cc +++ b/src/times.cc @@ -82,14 +82,22 @@ namespace { result.tm_min = 0; result.tm_sec = 0; - if (result.tm_year == -1) - result.tm_year = (year == -1 ? int(CURRENT_DATE().year()) : year) - 1900; + if (result.tm_mday == -1) + result.tm_mday = 1; - if (result.tm_mon == -1) + if (result.tm_mon == -1) { result.tm_mon = 0; - if (result.tm_mday == -1) - result.tm_mday = 1; + if (result.tm_mday > (CURRENT_DATE().day() - 1)) + result.tm_mon = 11; + } + + if (result.tm_year == -1) { + result.tm_year = (year == -1 ? int(CURRENT_DATE().year()) : year) - 1900; + + if (result.tm_mon > (CURRENT_DATE().month() - 1)) + result.tm_year--; + } return true; } diff --git a/src/xact.cc b/src/xact.cc index 5a0a14c8..4e5004a2 100644 --- a/src/xact.cc +++ b/src/xact.cc @@ -260,7 +260,7 @@ void xact_t::add_to_value(value_t& value, expr_t& expr) if (! xdata_ || ! xdata_->has_flags(XACT_EXT_NO_TOTAL)) { bind_scope_t bound_scope(*expr.get_context(), *this); if (value.is_null()) - value = amount_t(); + value = amount_t(0L); value += expr.calc(bound_scope); } |