summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2009-11-09 01:36:26 -0500
committerJohn Wiegley <johnw@newartisans.com>2009-11-09 02:06:08 -0500
commit7411c74d6d5bea42cb9fa5b6b0ed90480c954a03 (patch)
tree2da84c70cadb965486f271a2c4b83b88f076cb30 /src
parentfb8be53edb9d9fdd91fd906e9a4ce6c3e8e3adb3 (diff)
downloadfork-ledger-7411c74d6d5bea42cb9fa5b6b0ed90480c954a03.tar.gz
fork-ledger-7411c74d6d5bea42cb9fa5b6b0ed90480c954a03.tar.bz2
fork-ledger-7411c74d6d5bea42cb9fa5b6b0ed90480c954a03.zip
Redesigned the draft_t class
Diffstat (limited to 'src')
-rw-r--r--src/derive.cc559
-rw-r--r--src/draft.cc525
-rw-r--r--src/draft.h (renamed from src/derive.h)65
-rw-r--r--src/format.h3
-rw-r--r--src/report.cc2
-rw-r--r--src/stats.cc2
6 files changed, 586 insertions, 570 deletions
diff --git a/src/derive.cc b/src/derive.cc
deleted file mode 100644
index 6f489003..00000000
--- a/src/derive.cc
+++ /dev/null
@@ -1,559 +0,0 @@
-/*
- * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * - Neither the name of New Artisans LLC nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include <system.hh>
-
-#include "derive.h"
-#include "xact.h"
-#include "post.h"
-#include "account.h"
-#include "journal.h"
-#include "session.h"
-#include "report.h"
-#include "output.h"
-
-namespace ledger {
-
-namespace {
- struct xact_template_t
- {
- optional<date_t> date;
- optional<string> code;
- optional<string> note;
- mask_t payee_mask;
-
- struct post_template_t {
- bool from;
- optional<mask_t> account_mask;
- optional<amount_t> amount;
- optional<string> cost_operator;
- optional<amount_t> cost;
-
- post_template_t() : from(false) {}
- };
-
- std::list<post_template_t> posts;
-
- xact_template_t() {}
-
- void dump(std::ostream& out) const
- {
- if (date)
- out << _("Date: ") << *date << std::endl;
- else
- out << _("Date: <today>") << std::endl;
-
- if (code)
- out << _("Code: ") << *code << std::endl;
- if (note)
- out << _("Note: ") << *note << std::endl;
-
- if (payee_mask.empty())
- out << _("Payee mask: INVALID (template expression will cause an error)")
- << std::endl;
- else
- out << _("Payee mask: ") << payee_mask << std::endl;
-
- if (posts.empty()) {
- out << std::endl
- << _("<Posting copied from last related transaction>")
- << std::endl;
- } else {
- bool has_only_from = true;
- bool has_only_to = true;
-
- foreach (const post_template_t& post, posts) {
- if (post.from)
- has_only_to = false;
- else
- has_only_from = false;
- }
-
- foreach (const post_template_t& post, posts) {
- straccstream accum;
- out << std::endl
- << ACCUM(accum << _("[Posting \"%1\"]")
- << (post.from ? _("from") : _("to")))
- << std::endl;
-
- if (post.account_mask)
- out << _(" Account mask: ") << *post.account_mask << std::endl;
- else if (post.from)
- out << _(" Account mask: <use last of last related accounts>") << std::endl;
- else
- out << _(" Account mask: <use first of last related accounts>") << std::endl;
-
- if (post.amount)
- out << _(" Amount: ") << *post.amount << std::endl;
-
- if (post.cost)
- out << _(" Cost: ") << *post.cost_operator
- << " " << *post.cost << std::endl;
- }
- }
- }
- };
-
- xact_template_t
- args_to_xact_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;
-
- xact_template_t tmpl;
- bool check_for_date = true;
-
- optional<date_time::weekdays> weekday;
- xact_template_t::post_template_t * post = NULL;
-
- for (; begin != end; begin++) {
- if (check_for_date &&
- regex_match((*begin).to_string(), what, date_mask)) {
- tmpl.date = parse_date(what[0]);
- check_for_date = false;
- }
- else if (check_for_date &&
- bool(weekday = string_to_day_of_week(what[0]))) {
- short dow = static_cast<short>(*weekday);
- date_t date = CURRENT_DATE() - date_duration(1);
- while (date.day_of_week() != dow)
- date -= date_duration(1);
- tmpl.date = date;
- check_for_date = false;
- }
- else {
- string arg = (*begin).to_string();
-
- if (arg == "at") {
- if (begin == end)
- throw std::runtime_error(_("Invalid xact command arguments"));
- tmpl.payee_mask = (*++begin).to_string();
- }
- else if (arg == "to" || arg == "from") {
- if (! post || post->account_mask) {
- tmpl.posts.push_back(xact_template_t::post_template_t());
- post = &tmpl.posts.back();
- }
- if (begin == end)
- throw std::runtime_error(_("Invalid xact command arguments"));
- post->account_mask = mask_t((*++begin).to_string());
- post->from = arg == "from";
- }
- else if (arg == "on") {
- if (begin == end)
- throw std::runtime_error(_("Invalid xact command arguments"));
- tmpl.date = parse_date((*++begin).to_string());
- check_for_date = false;
- }
- else if (arg == "code") {
- if (begin == end)
- throw std::runtime_error(_("Invalid xact command arguments"));
- tmpl.code = (*++begin).to_string();
- }
- else if (arg == "note") {
- if (begin == end)
- throw std::runtime_error(_("Invalid xact command arguments"));
- tmpl.note = (*++begin).to_string();
- }
- else if (arg == "rest") {
- ; // just ignore this argument
- }
- else if (arg == "@" || arg == "@@") {
- amount_t cost;
- post->cost_operator = arg;
- if (begin == end)
- throw std::runtime_error(_("Invalid xact command arguments"));
- arg = (*++begin).to_string();
- if (! cost.parse(arg, PARSE_SOFT_FAIL | PARSE_NO_MIGRATE))
- throw std::runtime_error(_("Invalid xact command arguments"));
- post->cost = cost;
- }
- 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, PARSE_SOFT_FAIL | PARSE_NO_MIGRATE))
- account = mask_t(arg);
-
- if (! post ||
- (account && post->account_mask) ||
- (! account && post->amount)) {
- tmpl.posts.push_back(xact_template_t::post_template_t());
- post = &tmpl.posts.back();
- }
-
- if (account) {
- post->from = false;
- post->account_mask = account;
- } else {
- post->amount = amt;
- }
- }
- }
- }
- }
-
- if (! tmpl.posts.empty()) {
- bool has_only_from = true;
- bool has_only_to = true;
-
- // A single account at the end of the line is the "from" account
- if (tmpl.posts.size() > 1 &&
- tmpl.posts.back().account_mask && ! tmpl.posts.back().amount)
- tmpl.posts.back().from = true;
-
- foreach (xact_template_t::post_template_t& post, tmpl.posts) {
- if (post.from)
- has_only_to = false;
- else
- has_only_from = false;
- }
-
- if (has_only_from) {
- tmpl.posts.push_front(xact_template_t::post_template_t());
- }
- else if (has_only_to) {
- tmpl.posts.push_back(xact_template_t::post_template_t());
- tmpl.posts.back().from = true;
- }
- }
-
- return tmpl;
- }
-
- xact_t * derive_xact_from_template(xact_template_t& tmpl,
- report_t& report)
- {
- if (tmpl.payee_mask.empty())
- throw std::runtime_error(_("xact' command requires at least a payee"));
-
- xact_t * matching = NULL;
- journal_t& journal(*report.session.journal.get());
- std::auto_ptr<xact_t> added(new xact_t);
-
- for (xacts_list::reverse_iterator j = journal.xacts.rbegin();
- j != journal.xacts.rend();
- j++) {
- if (tmpl.payee_mask.match((*j)->payee)) {
- matching = *j;
- DEBUG("derive.xact",
- "Found payee match: transaction on line " << (*j)->pos->beg_line);
- break;
- }
- }
-
- if (! tmpl.date) {
- added->_date = CURRENT_DATE();
- DEBUG("derive.xact", "Setting date to current date");
- } else {
- added->_date = tmpl.date;
- DEBUG("derive.xact", "Setting date to template date: " << *tmpl.date);
- }
-
- added->set_state(item_t::UNCLEARED);
-
- if (matching) {
- added->payee = matching->payee;
- added->code = matching->code;
- added->note = matching->note;
-
-#if defined(DEBUG_ON)
- DEBUG("derive.xact", "Setting payee from match: " << added->payee);
- if (added->code)
- DEBUG("derive.xact", "Setting code from match: " << *added->code);
- if (added->note)
- DEBUG("derive.xact", "Setting note from match: " << *added->note);
-#endif
- } else {
- added->payee = tmpl.payee_mask.str();
- DEBUG("derive.xact", "Setting payee from template: " << added->payee);
- }
-
- if (tmpl.code) {
- added->code = tmpl.code;
- DEBUG("derive.xact", "Now setting code from template: " << *added->code);
- }
- if (tmpl.note) {
- added->note = tmpl.note;
- DEBUG("derive.xact", "Now setting note from template: " << *added->note);
- }
-
- if (tmpl.posts.empty()) {
- if (matching) {
- DEBUG("derive.xact", "Template had no postings, copying from match");
-
- foreach (post_t * post, matching->posts) {
- added->add_post(new post_t(*post));
- added->posts.back()->set_state(item_t::UNCLEARED);
- }
- } else {
- throw_(std::runtime_error,
- _("No accounts, and no past transaction matching '%1'")
- << tmpl.payee_mask);
- }
- } else {
- DEBUG("derive.xact", "Template had postings");
-
- bool any_post_has_amount = false;
- foreach (xact_template_t::post_template_t& post, tmpl.posts) {
- if (post.amount) {
- DEBUG("derive.xact", " and at least one has an amount specified");
- any_post_has_amount = true;
- break;
- }
- }
-
- foreach (xact_template_t::post_template_t& post, tmpl.posts) {
- std::auto_ptr<post_t> new_post;
-
- commodity_t * found_commodity = NULL;
-
- if (matching) {
- if (post.account_mask) {
- DEBUG("derive.xact",
- "Looking for matching posting based on account mask");
-
- foreach (post_t * x, matching->posts) {
- if (post.account_mask->match(x->account->fullname())) {
- new_post.reset(new post_t(*x));
- DEBUG("derive.xact",
- "Founding posting from line " << x->pos->beg_line);
- break;
- }
- }
- } else {
- if (post.from) {
- for (posts_list::reverse_iterator j = matching->posts.rbegin();
- j != matching->posts.rend();
- j++) {
- if ((*j)->must_balance()) {
- new_post.reset(new post_t(**j));
- DEBUG("derive.xact",
- "Copied last real posting from matching");
- break;
- }
- }
- } else {
- for (posts_list::iterator j = matching->posts.begin();
- j != matching->posts.end();
- j++) {
- if ((*j)->must_balance()) {
- new_post.reset(new post_t(**j));
- DEBUG("derive.xact",
- "Copied first real posting from matching");
- break;
- }
- }
- }
- }
- }
-
- if (! new_post.get()) {
- new_post.reset(new post_t);
- DEBUG("derive.xact", "New posting was NULL, creating a blank one");
- }
-
- if (! new_post->account) {
- DEBUG("derive.xact", "New posting still needs an account");
-
- if (post.account_mask) {
- DEBUG("derive.xact", "The template has an account mask");
-
- account_t * acct = NULL;
- if (! acct) {
- acct = journal.find_account_re(post.account_mask->str());
-#if defined(DEBUG_ON)
- if (acct)
- DEBUG("derive.xact", "Found account as a regular expression");
-#endif
- }
- if (! acct) {
- acct = journal.find_account(post.account_mask->str());
-#if defined(DEBUG_ON)
- if (acct)
- DEBUG("derive.xact", "Found (or created) account by name");
-#endif
- }
-
- // Find out the default commodity to use by looking at the last
- // commodity used in that account
- for (xacts_list::reverse_iterator j = journal.xacts.rbegin();
- j != journal.xacts.rend();
- j++) {
- foreach (post_t * x, (*j)->posts) {
- if (x->account == acct && ! x->amount.is_null()) {
- new_post.reset(new post_t(*x));
- DEBUG("derive.xact",
- "Found account in journal postings, setting new posting");
- break;
- }
- }
- }
-
- new_post->account = acct;
- DEBUG("derive.xact",
- "Set new posting's account to: " << acct->fullname());
- } else {
- if (post.from) {
- new_post->account = journal.find_account(_("Liabilities:Unknown"));
- DEBUG("derive.xact",
- "Set new posting's account to: Liabilities:Unknown");
- } else {
- new_post->account = journal.find_account(_("Expenses:Unknown"));
- DEBUG("derive.xact",
- "Set new posting's account to: Expenses:Unknown");
- }
- }
- }
-
- if (new_post.get() && ! new_post->amount.is_null()) {
- found_commodity = &new_post->amount.commodity();
-
- if (any_post_has_amount) {
- new_post->amount = amount_t();
- DEBUG("derive.xact", "New posting has an amount, but we cleared it");
- } else {
- any_post_has_amount = true;
- DEBUG("derive.xact", "New posting has an amount, and we're using it");
- }
- }
-
- if (post.amount) {
- new_post->amount = *post.amount;
- DEBUG("derive.xact", "Copied over posting amount");
-
- if (post.from) {
- new_post->amount.in_place_negate();
- DEBUG("derive.xact", "Negated new posting amount");
- }
- }
-
- if (post.cost) {
- if (post.cost->sign() < 0)
- throw parse_error(_("A posting's cost may not be negative"));
-
- post.cost->in_place_unround();
-
- if (*post.cost_operator == "@") {
- // For the sole case where the cost might be uncommoditized,
- // guarantee that the commodity of the cost after multiplication
- // is the same as it was before.
- commodity_t& cost_commodity(post.cost->commodity());
- *post.cost *= new_post->amount;
- post.cost->set_commodity(cost_commodity);
- }
-
- new_post->cost = *post.cost;
- DEBUG("derive.xact", "Copied over posting cost");
- }
-
- if (found_commodity &&
- ! new_post->amount.is_null() &&
- ! new_post->amount.has_commodity()) {
- new_post->amount.set_commodity(*found_commodity);
- DEBUG("derive.xact", "Set posting amount commodity to: "
- << new_post->amount.commodity());
-
- new_post->amount = new_post->amount.rounded();
- DEBUG("derive.xact",
- "Rounded posting amount to: " << new_post->amount);
- }
-
- added->add_post(new_post.release());
- added->posts.back()->set_state(item_t::UNCLEARED);
-
- DEBUG("derive.xact", "Added new posting to derived entry");
- }
- }
-
- if (! journal.xact_finalize_hooks.run_hooks(*added.get(), false) ||
- ! added->finalize() ||
- ! journal.xact_finalize_hooks.run_hooks(*added.get(), true)) {
- throw_(std::runtime_error,
- _("Failed to finalize derived transaction (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;
-
- xact_template_t tmpl = args_to_xact_template(begin, end);
-
- out << _("--- Transaction template ---") << std::endl;
- tmpl.dump(out);
-
- return true;
-}
-
-value_t xact_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));
- xact_template_t tmpl = args_to_xact_template(begin, end);
- std::auto_ptr<xact_t> new_xact(derive_xact_from_template(tmpl, report));
-
- // Only consider actual postings for the "xact" command
- report.HANDLER(limit_).on(string("#xact"), "actual");
-
- report.xact_report(post_handler_ptr
- (new format_posts(report,
- report.HANDLER(print_format_).str())),
- *new_xact.get());
- return true;
-}
-
-} // namespace ledger
diff --git a/src/draft.cc b/src/draft.cc
new file mode 100644
index 00000000..b4e23322
--- /dev/null
+++ b/src/draft.cc
@@ -0,0 +1,525 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "draft.h"
+#include "xact.h"
+#include "post.h"
+#include "account.h"
+#include "journal.h"
+#include "session.h"
+#include "report.h"
+#include "output.h"
+
+namespace ledger {
+
+void draft_t::xact_template_t::dump(std::ostream& out) const
+{
+ if (date)
+ out << _("Date: ") << *date << std::endl;
+ else
+ out << _("Date: <today>") << std::endl;
+
+ if (code)
+ out << _("Code: ") << *code << std::endl;
+ if (note)
+ out << _("Note: ") << *note << std::endl;
+
+ if (payee_mask.empty())
+ out << _("Payee mask: INVALID (template expression will cause an error)")
+ << std::endl;
+ else
+ out << _("Payee mask: ") << payee_mask << std::endl;
+
+ if (posts.empty()) {
+ out << std::endl
+ << _("<Posting copied from last related transaction>")
+ << std::endl;
+ } else {
+ bool has_only_from = true;
+ bool has_only_to = true;
+
+ foreach (const post_template_t& post, posts) {
+ if (post.from)
+ has_only_to = false;
+ else
+ has_only_from = false;
+ }
+
+ foreach (const post_template_t& post, posts) {
+ straccstream accum;
+ out << std::endl
+ << ACCUM(accum << _("[Posting \"%1\"]")
+ << (post.from ? _("from") : _("to")))
+ << std::endl;
+
+ if (post.account_mask)
+ out << _(" Account mask: ") << *post.account_mask << std::endl;
+ else if (post.from)
+ out << _(" Account mask: <use last of last related accounts>") << std::endl;
+ else
+ out << _(" Account mask: <use first of last related accounts>") << std::endl;
+
+ if (post.amount)
+ out << _(" Amount: ") << *post.amount << std::endl;
+
+ if (post.cost)
+ out << _(" Cost: ") << *post.cost_operator
+ << " " << *post.cost << std::endl;
+ }
+ }
+}
+
+void draft_t::parse_args(const value_t& args)
+{
+ regex date_mask(_("([0-9]+(?:[-/.][0-9]+)?(?:[-/.][0-9]+))?"));
+ smatch what;
+ bool check_for_date = true;
+
+ tmpl = xact_template_t();
+
+ optional<date_time::weekdays> weekday;
+ xact_template_t::post_template_t * post = NULL;
+
+ value_t::sequence_t::const_iterator begin = args.begin();
+ value_t::sequence_t::const_iterator end = args.end();
+
+ for (; begin != end; begin++) {
+ if (check_for_date &&
+ regex_match((*begin).to_string(), what, date_mask)) {
+ tmpl->date = parse_date(what[0]);
+ check_for_date = false;
+ }
+ else if (check_for_date &&
+ bool(weekday = string_to_day_of_week(what[0]))) {
+ short dow = static_cast<short>(*weekday);
+ date_t date = CURRENT_DATE() - date_duration(1);
+ while (date.day_of_week() != dow)
+ date -= date_duration(1);
+ tmpl->date = date;
+ check_for_date = false;
+ }
+ else {
+ string arg = (*begin).to_string();
+
+ if (arg == "at") {
+ if (begin == end)
+ throw std::runtime_error(_("Invalid xact command arguments"));
+ tmpl->payee_mask = (*++begin).to_string();
+ }
+ else if (arg == "to" || arg == "from") {
+ if (! post || post->account_mask) {
+ tmpl->posts.push_back(xact_template_t::post_template_t());
+ post = &tmpl->posts.back();
+ }
+ if (begin == end)
+ throw std::runtime_error(_("Invalid xact command arguments"));
+ post->account_mask = mask_t((*++begin).to_string());
+ post->from = arg == "from";
+ }
+ else if (arg == "on") {
+ if (begin == end)
+ throw std::runtime_error(_("Invalid xact command arguments"));
+ tmpl->date = parse_date((*++begin).to_string());
+ check_for_date = false;
+ }
+ else if (arg == "code") {
+ if (begin == end)
+ throw std::runtime_error(_("Invalid xact command arguments"));
+ tmpl->code = (*++begin).to_string();
+ }
+ else if (arg == "note") {
+ if (begin == end)
+ throw std::runtime_error(_("Invalid xact command arguments"));
+ tmpl->note = (*++begin).to_string();
+ }
+ else if (arg == "rest") {
+ ; // just ignore this argument
+ }
+ else if (arg == "@" || arg == "@@") {
+ amount_t cost;
+ post->cost_operator = arg;
+ if (begin == end)
+ throw std::runtime_error(_("Invalid xact command arguments"));
+ arg = (*++begin).to_string();
+ if (! cost.parse(arg, PARSE_SOFT_FAIL | PARSE_NO_MIGRATE))
+ throw std::runtime_error(_("Invalid xact command arguments"));
+ post->cost = cost;
+ }
+ 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, PARSE_SOFT_FAIL | PARSE_NO_MIGRATE))
+ account = mask_t(arg);
+
+ if (! post ||
+ (account && post->account_mask) ||
+ (! account && post->amount)) {
+ tmpl->posts.push_back(xact_template_t::post_template_t());
+ post = &tmpl->posts.back();
+ }
+
+ if (account) {
+ post->from = false;
+ post->account_mask = account;
+ } else {
+ post->amount = amt;
+ }
+ }
+ }
+ }
+ }
+
+ if (! tmpl->posts.empty()) {
+ bool has_only_from = true;
+ bool has_only_to = true;
+
+ // A single account at the end of the line is the "from" account
+ if (tmpl->posts.size() > 1 &&
+ tmpl->posts.back().account_mask && ! tmpl->posts.back().amount)
+ tmpl->posts.back().from = true;
+
+ foreach (xact_template_t::post_template_t& post, tmpl->posts) {
+ if (post.from)
+ has_only_to = false;
+ else
+ has_only_from = false;
+ }
+
+ if (has_only_from) {
+ tmpl->posts.push_front(xact_template_t::post_template_t());
+ }
+ else if (has_only_to) {
+ tmpl->posts.push_back(xact_template_t::post_template_t());
+ tmpl->posts.back().from = true;
+ }
+ }
+}
+
+xact_t * draft_t::insert(journal_t& journal)
+{
+ if (tmpl->payee_mask.empty())
+ throw std::runtime_error(_("xact' command requires at least a payee"));
+
+ xact_t * matching = NULL;
+
+ std::auto_ptr<xact_t> added(new xact_t);
+
+ for (xacts_list::reverse_iterator j = journal.xacts.rbegin();
+ j != journal.xacts.rend();
+ j++) {
+ if (tmpl->payee_mask.match((*j)->payee)) {
+ matching = *j;
+ DEBUG("derive.xact",
+ "Found payee match: transaction on line " << (*j)->pos->beg_line);
+ break;
+ }
+ }
+
+ if (! tmpl->date) {
+ added->_date = CURRENT_DATE();
+ DEBUG("derive.xact", "Setting date to current date");
+ } else {
+ added->_date = tmpl->date;
+ DEBUG("derive.xact", "Setting date to template date: " << *tmpl->date);
+ }
+
+ added->set_state(item_t::UNCLEARED);
+
+ if (matching) {
+ added->payee = matching->payee;
+ added->code = matching->code;
+ added->note = matching->note;
+
+#if defined(DEBUG_ON)
+ DEBUG("derive.xact", "Setting payee from match: " << added->payee);
+ if (added->code)
+ DEBUG("derive.xact", "Setting code from match: " << *added->code);
+ if (added->note)
+ DEBUG("derive.xact", "Setting note from match: " << *added->note);
+#endif
+ } else {
+ added->payee = tmpl->payee_mask.str();
+ DEBUG("derive.xact", "Setting payee from template: " << added->payee);
+ }
+
+ if (tmpl->code) {
+ added->code = tmpl->code;
+ DEBUG("derive.xact", "Now setting code from template: " << *added->code);
+ }
+ if (tmpl->note) {
+ added->note = tmpl->note;
+ DEBUG("derive.xact", "Now setting note from template: " << *added->note);
+ }
+
+ if (tmpl->posts.empty()) {
+ if (matching) {
+ DEBUG("derive.xact", "Template had no postings, copying from match");
+
+ foreach (post_t * post, matching->posts) {
+ added->add_post(new post_t(*post));
+ added->posts.back()->set_state(item_t::UNCLEARED);
+ }
+ } else {
+ throw_(std::runtime_error,
+ _("No accounts, and no past transaction matching '%1'")
+ << tmpl->payee_mask);
+ }
+ } else {
+ DEBUG("derive.xact", "Template had postings");
+
+ bool any_post_has_amount = false;
+ foreach (xact_template_t::post_template_t& post, tmpl->posts) {
+ if (post.amount) {
+ DEBUG("derive.xact", " and at least one has an amount specified");
+ any_post_has_amount = true;
+ break;
+ }
+ }
+
+ foreach (xact_template_t::post_template_t& post, tmpl->posts) {
+ std::auto_ptr<post_t> new_post;
+
+ commodity_t * found_commodity = NULL;
+
+ if (matching) {
+ if (post.account_mask) {
+ DEBUG("derive.xact",
+ "Looking for matching posting based on account mask");
+
+ foreach (post_t * x, matching->posts) {
+ if (post.account_mask->match(x->account->fullname())) {
+ new_post.reset(new post_t(*x));
+ DEBUG("derive.xact",
+ "Founding posting from line " << x->pos->beg_line);
+ break;
+ }
+ }
+ } else {
+ if (post.from) {
+ for (posts_list::reverse_iterator j = matching->posts.rbegin();
+ j != matching->posts.rend();
+ j++) {
+ if ((*j)->must_balance()) {
+ new_post.reset(new post_t(**j));
+ DEBUG("derive.xact",
+ "Copied last real posting from matching");
+ break;
+ }
+ }
+ } else {
+ for (posts_list::iterator j = matching->posts.begin();
+ j != matching->posts.end();
+ j++) {
+ if ((*j)->must_balance()) {
+ new_post.reset(new post_t(**j));
+ DEBUG("derive.xact",
+ "Copied first real posting from matching");
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (! new_post.get()) {
+ new_post.reset(new post_t);
+ DEBUG("derive.xact", "New posting was NULL, creating a blank one");
+ }
+
+ if (! new_post->account) {
+ DEBUG("derive.xact", "New posting still needs an account");
+
+ if (post.account_mask) {
+ DEBUG("derive.xact", "The template has an account mask");
+
+ account_t * acct = NULL;
+ if (! acct) {
+ acct = journal.find_account_re(post.account_mask->str());
+#if defined(DEBUG_ON)
+ if (acct)
+ DEBUG("derive.xact", "Found account as a regular expression");
+#endif
+ }
+ if (! acct) {
+ acct = journal.find_account(post.account_mask->str());
+#if defined(DEBUG_ON)
+ if (acct)
+ DEBUG("derive.xact", "Found (or created) account by name");
+#endif
+ }
+
+ // Find out the default commodity to use by looking at the last
+ // commodity used in that account
+ for (xacts_list::reverse_iterator j = journal.xacts.rbegin();
+ j != journal.xacts.rend();
+ j++) {
+ foreach (post_t * x, (*j)->posts) {
+ if (x->account == acct && ! x->amount.is_null()) {
+ new_post.reset(new post_t(*x));
+ DEBUG("derive.xact",
+ "Found account in journal postings, setting new posting");
+ break;
+ }
+ }
+ }
+
+ new_post->account = acct;
+ DEBUG("derive.xact",
+ "Set new posting's account to: " << acct->fullname());
+ } else {
+ if (post.from) {
+ new_post->account = journal.find_account(_("Liabilities:Unknown"));
+ DEBUG("derive.xact",
+ "Set new posting's account to: Liabilities:Unknown");
+ } else {
+ new_post->account = journal.find_account(_("Expenses:Unknown"));
+ DEBUG("derive.xact",
+ "Set new posting's account to: Expenses:Unknown");
+ }
+ }
+ }
+
+ if (new_post.get() && ! new_post->amount.is_null()) {
+ found_commodity = &new_post->amount.commodity();
+
+ if (any_post_has_amount) {
+ new_post->amount = amount_t();
+ DEBUG("derive.xact", "New posting has an amount, but we cleared it");
+ } else {
+ any_post_has_amount = true;
+ DEBUG("derive.xact", "New posting has an amount, and we're using it");
+ }
+ }
+
+ if (post.amount) {
+ new_post->amount = *post.amount;
+ DEBUG("derive.xact", "Copied over posting amount");
+
+ if (post.from) {
+ new_post->amount.in_place_negate();
+ DEBUG("derive.xact", "Negated new posting amount");
+ }
+ }
+
+ if (post.cost) {
+ if (post.cost->sign() < 0)
+ throw parse_error(_("A posting's cost may not be negative"));
+
+ post.cost->in_place_unround();
+
+ if (*post.cost_operator == "@") {
+ // For the sole case where the cost might be uncommoditized,
+ // guarantee that the commodity of the cost after multiplication
+ // is the same as it was before.
+ commodity_t& cost_commodity(post.cost->commodity());
+ *post.cost *= new_post->amount;
+ post.cost->set_commodity(cost_commodity);
+ }
+
+ new_post->cost = *post.cost;
+ DEBUG("derive.xact", "Copied over posting cost");
+ }
+
+ if (found_commodity &&
+ ! new_post->amount.is_null() &&
+ ! new_post->amount.has_commodity()) {
+ new_post->amount.set_commodity(*found_commodity);
+ DEBUG("derive.xact", "Set posting amount commodity to: "
+ << new_post->amount.commodity());
+
+ new_post->amount = new_post->amount.rounded();
+ DEBUG("derive.xact",
+ "Rounded posting amount to: " << new_post->amount);
+ }
+
+ added->add_post(new_post.release());
+ added->posts.back()->set_state(item_t::UNCLEARED);
+
+ DEBUG("derive.xact", "Added new posting to derived entry");
+ }
+ }
+
+ if (! journal.add_xact(added.get()))
+ throw_(std::runtime_error,
+ _("Failed to finalize derived transaction (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);
+
+ out << _("--- Input arguments ---") << std::endl;
+ args.value().dump(out);
+ out << std::endl << std::endl;
+
+ draft_t draft(args.value());
+
+ out << _("--- Transaction template ---") << std::endl;
+ draft.dump(out);
+
+ return true;
+}
+
+value_t xact_command(call_scope_t& args)
+{
+ report_t& report(find_scope<report_t>(args));
+ draft_t draft(args.value());
+
+ xact_t * new_xact = draft.insert(*report.session.journal.get());
+
+ // Only consider actual postings for the "xact" command
+ report.HANDLER(limit_).on(string("#xact"), "actual");
+
+ report.xact_report(post_handler_ptr
+ (new format_posts(report,
+ report.HANDLER(print_format_).str())),
+ *new_xact);
+ return true;
+}
+
+} // namespace ledger
diff --git a/src/derive.h b/src/draft.h
index 0fb071a3..faefa67b 100644
--- a/src/derive.h
+++ b/src/draft.h
@@ -42,21 +42,72 @@
#ifndef _DERIVE_H
#define _DERIVE_H
+#include "exprbase.h"
#include "value.h"
namespace ledger {
-class call_scope_t;
+class journal_t;
+class xact_t;
+
+class draft_t : public expr_base_t<value_t>
+{
+ typedef expr_base_t<value_t> base_type;
+
+ struct xact_template_t
+ {
+ optional<date_t> date;
+ optional<string> code;
+ optional<string> note;
+ mask_t payee_mask;
+
+ struct post_template_t {
+ bool from;
+ optional<mask_t> account_mask;
+ optional<amount_t> amount;
+ optional<string> cost_operator;
+ optional<amount_t> cost;
+
+ post_template_t() : from(false) {}
+ };
+
+ std::list<post_template_t> posts;
+
+ xact_template_t() {}
+
+ void dump(std::ostream& out) const;
+ };
+
+ optional<xact_template_t> tmpl;
+
+public:
+ draft_t(const value_t& args) : base_type() {
+ TRACE_CTOR(draft_t, "value_t");
+ if (! args.empty())
+ parse_args(args);
+ }
+ ~draft_t() {
+ TRACE_DTOR(draft_t);
+ }
+
+ void parse_args(const value_t& args);
+
+ virtual result_type real_calc(scope_t&) {
+ assert(0);
+ return true;
+ }
+
+ xact_t * insert(journal_t& journal);
+
+ virtual void dump(std::ostream& out) const {
+ if (tmpl)
+ tmpl->dump(out);
+ }
+};
value_t xact_command(call_scope_t& args);
value_t template_command(call_scope_t& args);
-class xact_t;
-class report_t;
-xact_t * derive_new_xact(report_t& report,
- value_t::sequence_t::const_iterator i,
- value_t::sequence_t::const_iterator end);
-
} // namespace ledger
#endif // _DERIVE_H
diff --git a/src/format.h b/src/format.h
index 516b6d7e..b3ae464f 100644
--- a/src/format.h
+++ b/src/format.h
@@ -105,8 +105,7 @@ class format_t : public expr_base_t<string>
void dump(std::ostream& out) const;
};
- string format_string;
- scoped_ptr<element_t> elements;
+ scoped_ptr<element_t> elements;
public:
static enum elision_style_t {
diff --git a/src/report.cc b/src/report.cc
index c0b5d745..3da71616 100644
--- a/src/report.cc
+++ b/src/report.cc
@@ -42,7 +42,7 @@
#include "precmd.h"
#include "stats.h"
#include "generate.h"
-#include "derive.h"
+#include "draft.h"
#include "emacs.h"
namespace ledger {
diff --git a/src/stats.cc b/src/stats.cc
index e2db9d8b..b89a5949 100644
--- a/src/stats.cc
+++ b/src/stats.cc
@@ -31,7 +31,7 @@
#include <system.hh>
-#include "derive.h"
+#include "draft.h"
#include "xact.h"
#include "post.h"
#include "account.h"