summaryrefslogtreecommitdiff
path: root/src/textual.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/textual.cc')
-rw-r--r--src/textual.cc1124
1 files changed, 1124 insertions, 0 deletions
diff --git a/src/textual.cc b/src/textual.cc
new file mode 100644
index 00000000..f10125ea
--- /dev/null
+++ b/src/textual.cc
@@ -0,0 +1,1124 @@
+/*
+ * Copyright (c) 2003-2008, 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.
+ */
+
+#if defined(__GNUG__) && __GNUG__ < 3
+#define _XOPEN_SOURCE
+#endif
+
+#include "textual.h"
+#include "expr.h"
+#include "parser.h"
+#include "session.h"
+#include "option.h"
+#include "acconf.h"
+
+#define TIMELOG_SUPPORT 1
+
+namespace ledger {
+
+#define MAX_LINE 1024
+
+static path pathname;
+static unsigned int linenum;
+static unsigned int src_idx;
+static accounts_map account_aliases;
+
+static std::list<std::pair<path, int> > include_stack;
+
+#ifdef TIMELOG_SUPPORT
+struct time_entry_t
+{
+ datetime_t checkin;
+ account_t * account;
+ string desc;
+
+ time_entry_t() : account(NULL) {
+ TRACE_CTOR(time_entry_t, "");
+ }
+ time_entry_t(const datetime_t& _checkin,
+ account_t * _account = NULL,
+ const string& _desc = "")
+ : checkin(_checkin), account(_account), desc(_desc) {
+ TRACE_CTOR(time_entry_t, "const datetime_t&, account_t *, const string&");
+ }
+ time_entry_t(const time_entry_t& entry)
+ : checkin(entry.checkin), account(entry.account),
+ desc(entry.desc) {
+ TRACE_CTOR(time_entry_t, "copy");
+ }
+ ~time_entry_t() throw() {
+ TRACE_DTOR(time_entry_t);
+ }
+};
+#endif
+
+namespace {
+ optional<expr_t> parse_amount_expr(std::istream& in,
+ amount_t& amount,
+ xact_t * xact,
+ unsigned short flags = 0)
+ {
+ expr_t expr(in, flags | EXPR_PARSE_PARTIAL);
+
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed an amount expression");
+
+#ifdef DEBUG_ENABLED
+ DEBUG_IF("ledger.textual.parse") {
+ if (_debug_stream) {
+ ledger::dump_value_expr(*_debug_stream, expr);
+ *_debug_stream << std::endl;
+ }
+ }
+#endif
+
+ if (expr) {
+ amount = expr.calc(*xact).as_amount();
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "The transaction amount is " << amount);
+ return expr;
+ }
+ return none;
+ }
+}
+
+xact_t * parse_xact(char * line, account_t * account, entry_t * entry = NULL)
+{
+ std::istringstream in(line);
+
+ string err_desc;
+ try {
+
+ // The account will be determined later...
+ std::auto_ptr<xact_t> xact(new xact_t(NULL));
+ if (entry)
+ xact->entry = entry;
+
+ // Parse the state flag
+
+ char p = peek_next_nonws(in);
+ switch (p) {
+ case '*':
+ xact->state = xact_t::CLEARED;
+ in.get(p);
+ p = peek_next_nonws(in);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed the CLEARED flag");
+ break;
+ case '!':
+ xact->state = xact_t::PENDING;
+ in.get(p);
+ p = peek_next_nonws(in);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed the PENDING flag");
+ break;
+ }
+
+ // Parse the account name
+
+ unsigned long account_beg = static_cast<unsigned long>(in.tellg());
+ unsigned long account_end = account_beg;
+ while (! in.eof()) {
+ in.get(p);
+ if (in.eof() || (std::isspace(p) &&
+ (p == '\t' || in.peek() == EOF ||
+ std::isspace(in.peek()))))
+ break;
+ account_end++;
+ }
+
+ if (account_beg == account_end)
+ throw parse_error("No account was specified");
+
+ char * b = &line[account_beg];
+ char * e = &line[account_end];
+ if ((*b == '[' && *(e - 1) == ']') ||
+ (*b == '(' && *(e - 1) == ')')) {
+ xact->add_flags(XACT_VIRTUAL);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed a virtual account name");
+ if (*b == '[') {
+ xact->add_flags(XACT_BALANCE);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed a balanced virtual account name");
+ }
+ b++; e--;
+ }
+
+ string name(b, e - b);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed account name " << name);
+ if (account_aliases.size() > 0) {
+ accounts_map::const_iterator i = account_aliases.find(name);
+ if (i != account_aliases.end())
+ xact->account = (*i).second;
+ }
+ if (! xact->account)
+ xact->account = account->find_account(name);
+
+ // Parse the optional amount
+
+ bool saw_amount = false;
+
+ if (in.good() && ! in.eof()) {
+ p = peek_next_nonws(in);
+ if (in.eof())
+ goto finished;
+ if (p == ';')
+ goto parse_note;
+ if (p == '=' && entry)
+ goto parse_assign;
+
+ try {
+ unsigned long beg = static_cast<unsigned long>(in.tellg());
+
+ xact->amount_expr =
+ parse_amount_expr(in, xact->amount, xact.get(),
+ EXPR_PARSE_NO_REDUCE | EXPR_PARSE_NO_ASSIGN);
+ saw_amount = true;
+
+ if (! xact->amount.is_null()) {
+ xact->amount.reduce();
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Reduced amount is " << xact->amount);
+ }
+
+ // We don't need to store the actual expression that resulted in the
+ // amount if it's constant
+ if (xact->amount_expr) {
+ if (xact->amount_expr->is_constant())
+ xact->amount_expr = expr_t();
+
+ unsigned long end = static_cast<unsigned long>(in.tellg());
+ xact->amount_expr->set_text(string(line, beg, end - beg));
+ }
+ }
+ catch (const std::exception& err) {
+ add_error_context("While parsing transaction amount:\n");
+ throw err;
+ }
+ }
+
+ // Parse the optional cost (@ PER-UNIT-COST, @@ TOTAL-COST)
+
+ if (in.good() && ! in.eof()) {
+ p = peek_next_nonws(in);
+ if (p == '@') {
+ if (! saw_amount)
+ throw parse_error
+ ("Transaction cannot have a cost expression with an amount");
+
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Found a price indicator");
+ bool per_unit = true;
+ in.get(p);
+ if (in.peek() == '@') {
+ in.get(p);
+ per_unit = false;
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "And it's for a total price");
+ }
+
+ if (in.good() && ! in.eof()) {
+ xact->cost = amount_t();
+
+ try {
+ unsigned long beg = static_cast<unsigned long>(in.tellg());
+
+ xact->cost_expr =
+ parse_amount_expr(in, *xact->cost, xact.get(),
+ EXPR_PARSE_NO_MIGRATE |
+ EXPR_PARSE_NO_ASSIGN);
+
+ if (xact->cost_expr) {
+ unsigned long end = static_cast<unsigned long>(in.tellg());
+ if (per_unit)
+ xact->cost_expr->set_text(string("@") +
+ string(line, beg, end - beg));
+ else
+ xact->cost_expr->set_text(string("@@") +
+ string(line, beg, end - beg));
+ }
+ }
+ catch (const std::exception& err) {
+ add_error_context("While parsing transaction cost:\n");
+ throw err;
+ }
+
+ if (xact->cost->sign() < 0)
+ throw parse_error("A transaction's cost may not be negative");
+
+ amount_t per_unit_cost(*xact->cost);
+ if (per_unit)
+ *xact->cost *= xact->amount;
+ else
+ per_unit_cost /= xact->amount;
+
+ if (xact->amount.commodity() &&
+ ! xact->amount.commodity().annotated) {
+ if (xact->entry)
+ xact->amount.annotate(annotation_t(per_unit_cost,
+ xact->entry->actual_date(),
+ xact->entry->code));
+ else
+ xact->amount.annotate(annotation_t(per_unit_cost));
+ }
+
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Total cost is " << *xact->cost);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Per-unit cost is " << per_unit_cost);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Annotated amount is " << xact->amount);
+ }
+ }
+ }
+
+ parse_assign:
+ if (entry != NULL) {
+ // Parse the optional assigned (= AMOUNT)
+
+ if (in.good() && ! in.eof()) {
+ p = peek_next_nonws(in);
+ if (p == '=') {
+ in.get(p);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Found a balance assignment indicator");
+ if (in.good() && ! in.eof()) {
+ amount_t amt;
+
+ try {
+ unsigned long beg = static_cast<unsigned long>(in.tellg());
+
+ optional<expr_t> total_expr =
+ parse_amount_expr(in, amt, xact.get(), EXPR_PARSE_NO_MIGRATE);
+
+ if (amt.is_null())
+ throw parse_error
+ ("An assigned balance must evaluate to a constant value");
+
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "XACT assign: parsed amt = " << amt);
+
+ if (total_expr) {
+ unsigned long end = static_cast<unsigned long>(in.tellg());
+ total_expr->set_text(string("=") +
+ string(line, beg, end - beg));
+ }
+
+ // jww (2008-08-02): Save total_expr somewhere!
+
+ account_t::xdata_t& xdata(xact->account->xdata());
+
+ DEBUG("ledger.xact.assign", "account balance = " << xdata.value);
+ DEBUG("ledger.xact.assign", "xact amount = " << amt);
+
+ amount_t diff;
+ if (xdata.value.is_amount()) {
+ diff = amt - xdata.value.as_amount();
+ }
+ else if (xdata.value.is_balance()) {
+ optional<amount_t> comm_bal =
+ xdata.value.as_balance().commodity_amount(amt.commodity());
+ diff = amt - (comm_bal ? *comm_bal : amount_t(0L));
+ }
+ else if (xdata.value.is_balance_pair()) {
+ optional<amount_t> comm_bal =
+ xdata.value.as_balance_pair().commodity_amount(amt.commodity());
+ diff = amt - (comm_bal ? *comm_bal : amount_t(0L));
+ }
+ else {
+ diff = amt;
+ }
+
+ DEBUG("ledger.xact.assign", "diff = " << diff);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "XACT assign: diff = " << diff);
+
+ if (! diff.is_realzero()) {
+ if (! xact->amount.is_null()) {
+ xact_t * temp =
+ new xact_t(xact->account, diff,
+ XACT_GENERATED | XACT_CALCULATED);
+ entry->add_xact(temp);
+
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Created balancing transaction");
+ } else {
+ xact->amount = diff;
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Overwrite null transaction");
+ }
+ xdata.value = amt;
+ }
+ }
+ catch (const std::exception& err) {
+ add_error_context("While parsing assigned balance:\n");
+ throw err;
+ }
+ }
+ }
+ }
+ }
+
+ // Parse the optional note
+
+ parse_note:
+ if (in.good() && ! in.eof()) {
+ p = peek_next_nonws(in);
+ if (p == ';') {
+ in.get(p);
+ p = peek_next_nonws(in);
+ xact->note = &line[static_cast<unsigned long>(in.tellg())];
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed a note '" << *xact->note << "'");
+
+ if (char * b = std::strchr(xact->note->c_str(), '['))
+ if (char * e = std::strchr(xact->note->c_str(), ']')) {
+ char buf[256];
+ std::strncpy(buf, b + 1, e - b - 1);
+ buf[e - b - 1] = '\0';
+
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed a transaction date " << buf);
+
+ if (char * p = std::strchr(buf, '=')) {
+ *p++ = '\0';
+ xact->_date_eff = parse_date(p);
+ }
+ if (buf[0])
+ xact->_date = parse_date(buf);
+ }
+ }
+ }
+
+ finished:
+ return xact.release();
+
+ }
+ catch (const std::exception& err) {
+ add_error_context("While parsing transaction:\n");
+ add_error_context(line_context(line, static_cast<unsigned long>(in.tellg()) - 1));
+ throw err;
+ }
+}
+
+bool parse_xacts(std::istream& in,
+ account_t * account,
+ entry_base_t& entry,
+ const string& kind,
+ unsigned long beg_pos)
+{
+ TRACE_START(entry_xacts, 1, "Time spent parsing transactions:");
+
+ static char line[MAX_LINE + 1];
+ bool added = false;
+
+ while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) {
+ in.getline(line, MAX_LINE);
+ if (in.eof())
+ break;
+
+ int len = std::strlen(line);
+ if (line[len - 1] == '\r')
+ line[--len] = '\0';
+
+ beg_pos += len + 1;
+ linenum++;
+
+ if (line[0] == ' ' || line[0] == '\t') {
+ char * p = skip_ws(line);
+ if (! *p)
+ break;
+ }
+ if (xact_t * xact = parse_xact(line, account)) {
+ entry.add_xact(xact);
+ added = true;
+ }
+ }
+
+ TRACE_STOP(entry_xacts, 1);
+
+ return added;
+}
+
+entry_t * parse_entry(std::istream& in, char * line, account_t * master,
+ textual_parser_t& parser, unsigned long& pos)
+{
+ TRACE_START(entry_text, 1, "Time spent preparing entry text:");
+
+ std::auto_ptr<entry_t> curr(new entry_t);
+
+ // Parse the date
+
+ char * next = next_element(line);
+
+ if (char * p = std::strchr(line, '=')) {
+ *p++ = '\0';
+ curr->_date_eff = parse_date(p);
+ }
+ curr->_date = parse_date(line);
+
+ // Parse the optional cleared flag: *
+
+ xact_t::state_t state = xact_t::UNCLEARED;
+ if (next) {
+ switch (*next) {
+ case '*':
+ state = xact_t::CLEARED;
+ next = skip_ws(++next);
+ break;
+ case '!':
+ state = xact_t::PENDING;
+ next = skip_ws(++next);
+ break;
+ }
+ }
+
+ // Parse the optional code: (TEXT)
+
+ if (next && *next == '(') {
+ if (char * p = std::strchr(next++, ')')) {
+ *p++ = '\0';
+ curr->code = next;
+ next = skip_ws(p);
+ }
+ }
+
+ // Parse the description text
+
+ curr->payee = next ? next : "<Unspecified payee>";
+
+ TRACE_STOP(entry_text, 1);
+
+ // Parse all of the xacts associated with this entry
+
+ TRACE_START(entry_details, 1, "Time spent parsing entry details:");
+
+ unsigned long end_pos;
+ unsigned long beg_line = linenum;
+
+ while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) {
+ unsigned long beg_pos = static_cast<unsigned long>(in.tellg());
+
+ line[0] = '\0';
+ in.getline(line, MAX_LINE);
+ if (in.eof() && line[0] == '\0')
+ break;
+
+ int len = std::strlen(line);
+ if (line[len - 1] == '\r')
+ line[--len] = '\0';
+
+ end_pos = beg_pos + len + 1;
+ linenum++;
+
+ if (line[0] == ' ' || line[0] == '\t') {
+ char * p = skip_ws(line);
+ if (! *p)
+ break;
+ }
+
+ if (xact_t * xact = parse_xact(line, master, curr.get())) {
+ if (state != xact_t::UNCLEARED &&
+ xact->state == xact_t::UNCLEARED)
+ xact->state = state;
+
+ xact->beg_pos = beg_pos;
+ xact->beg_line = beg_line;
+ xact->end_pos = end_pos;
+ xact->end_line = linenum;
+ pos = end_pos;
+
+ curr->add_xact(xact);
+ }
+
+ if (in.eof())
+ break;
+ }
+
+ TRACE_STOP(entry_details, 1);
+
+ return curr.release();
+}
+
+static inline void parse_symbol(char *& p, string& symbol)
+{
+ if (*p == '"') {
+ char * q = std::strchr(p + 1, '"');
+ if (! q)
+ throw parse_error("Quoted commodity symbol lacks closing quote");
+ symbol = string(p + 1, 0, q - p - 1);
+ p = q + 2;
+ } else {
+ char * q = next_element(p);
+ symbol = p;
+ if (q)
+ p = q;
+ else
+ p += symbol.length();
+ }
+ if (symbol.empty())
+ throw parse_error("Failed to parse commodity");
+}
+
+bool textual_parser_t::test(std::istream& in) const
+{
+ char buf[5];
+
+ in.read(buf, 5);
+ if (std::strncmp(buf, "<?xml", 5) == 0) {
+#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
+ throw parse_error("Ledger file contains XML data, but format was not recognized");
+#else
+ throw parse_error("Ledger file contains XML data, but no XML support present");
+#endif
+ }
+
+ in.clear();
+ in.seekg(0, std::ios::beg);
+ assert(in.good());
+ return true;
+}
+
+static void clock_out_from_timelog(std::list<time_entry_t>& time_entries,
+ const datetime_t& when,
+ account_t * account,
+ const char * desc,
+ journal_t& journal)
+{
+ time_entry_t event;
+
+ if (time_entries.size() == 1) {
+ event = time_entries.back();
+ time_entries.clear();
+ }
+ else if (time_entries.empty()) {
+ throw parse_error("Timelog check-out event without a check-in");
+ }
+ else if (! account) {
+ throw parse_error
+ ("When multiple check-ins are active, checking out requires an account");
+ }
+ else {
+ bool found = false;
+
+ for (std::list<time_entry_t>::iterator i = time_entries.begin();
+ i != time_entries.end();
+ i++)
+ if (account == (*i).account) {
+ event = *i;
+ found = true;
+ time_entries.erase(i);
+ break;
+ }
+
+ if (! found)
+ throw parse_error
+ ("Timelog check-out event does not match any current check-ins");
+ }
+
+ if (desc && event.desc.empty()) {
+ event.desc = desc;
+ desc = NULL;
+ }
+
+ std::auto_ptr<entry_t> curr(new entry_t);
+ curr->_date = when.date();
+ curr->code = desc ? desc : "";
+ curr->payee = event.desc;
+
+ if (when < event.checkin)
+ throw parse_error
+ ("Timelog check-out date less than corresponding check-in");
+
+ char buf[32];
+ std::sprintf(buf, "%lds", long((when - event.checkin).seconds()));
+ amount_t amt;
+ amt.parse(buf);
+ assert(amt.valid());
+
+ xact_t * xact
+ = new xact_t(event.account, amt, XACT_VIRTUAL);
+ xact->state = xact_t::CLEARED;
+ curr->add_xact(xact);
+
+ if (! journal.add_entry(curr.get()))
+ throw parse_error("Failed to record 'out' timelog entry");
+ else
+ curr.release();
+}
+
+unsigned int textual_parser_t::parse(std::istream& in,
+ session_t& session,
+ journal_t& journal,
+ account_t * master,
+ const path * original_file)
+{
+ TRACE_START(parsing_total, 1, "Total time spent parsing text:");
+
+ static bool added_auto_entry_hook = false;
+ static char line[MAX_LINE + 1];
+ unsigned int count = 0;
+ unsigned int errors = 0;
+
+ std::list<account_t *> account_stack;
+ auto_entry_finalizer_t auto_entry_finalizer(&journal);
+ std::list<time_entry_t> time_entries;
+
+ if (! master)
+ master = journal.master;
+
+ account_stack.push_front(master);
+
+ pathname = journal.sources.back();
+ src_idx = journal.sources.size() - 1;
+ linenum = 1;
+
+ INFO("Parsing file '" << pathname.string() << "'");
+
+ unsigned long beg_pos = static_cast<unsigned long>(in.tellg());
+ unsigned long end_pos;
+ unsigned long beg_line = linenum;
+
+ while (in.good() && ! in.eof()) {
+ try {
+ in.getline(line, MAX_LINE);
+ if (in.eof())
+ break;
+
+ int len = std::strlen(line);
+ if (line[len - 1] == '\r')
+ line[--len] = '\0';
+
+ end_pos = beg_pos + len + 1;
+ linenum++;
+
+ switch (line[0]) {
+ case '\0':
+ break;
+
+ case ' ':
+ case '\t': {
+ char * p = skip_ws(line);
+ if (*p)
+ throw parse_error("Line begins with whitespace");
+ break;
+ }
+
+#ifdef TIMELOG_SUPPORT
+ case 'i':
+ case 'I': {
+ string date(line, 2, 19);
+
+ char * p = skip_ws(line + 22);
+ char * n = next_element(p, true);
+
+ time_entry_t event(parse_datetime(date),
+ account_stack.front()->find_account(p), n ? n : "");
+
+ if (! time_entries.empty())
+ foreach (time_entry_t& time_entry, time_entries)
+ if (event.account == time_entry.account)
+ throw parse_error("Cannot double check-in to the same account");
+
+ time_entries.push_back(event);
+ break;
+ }
+
+ case 'o':
+ case 'O':
+ if (time_entries.empty()) {
+ throw parse_error("Timelog check-out event without a check-in");
+ } else {
+ string date(line, 2, 19);
+
+ char * p = skip_ws(line + 22);
+ char * n = next_element(p, true);
+
+ clock_out_from_timelog
+ (time_entries, parse_datetime(date),
+ p ? account_stack.front()->find_account(p) : NULL, n, journal);
+ count++;
+ }
+ break;
+#endif // TIMELOG_SUPPORT
+
+ case 'D': { // a default commodity for "entry"
+ amount_t amt(skip_ws(line + 1));
+ assert(amt.valid());
+ amount_t::current_pool->default_commodity = &amt.commodity();
+ break;
+ }
+
+ case 'A': // a default account for unbalanced xacts
+ journal.basket =
+ account_stack.front()->find_account(skip_ws(line + 1));
+ break;
+
+ case 'C': // a set of conversions
+ if (char * p = std::strchr(line + 1, '=')) {
+ *p++ = '\0';
+ // jww (2008-04-22): NYI!
+#if 0
+ parse_conversion(line + 1, p);
+#endif
+ }
+ break;
+
+ case 'P': { // a pricing entry
+ char * date_field_ptr = skip_ws(line + 1);
+ char * time_field_ptr = next_element(date_field_ptr);
+ if (! time_field_ptr) break;
+ string date_field = date_field_ptr;
+
+ char * symbol_and_price;
+ datetime_t datetime;
+
+ if (std::isdigit(time_field_ptr[0])) {
+ symbol_and_price = next_element(time_field_ptr);
+ if (! symbol_and_price) break;
+ datetime = parse_datetime(date_field + " " + time_field_ptr);
+ } else {
+ symbol_and_price = time_field_ptr;
+ datetime = parse_datetime(date_field);
+ }
+
+ string symbol;
+ parse_symbol(symbol_and_price, symbol);
+ amount_t price(symbol_and_price);
+ assert(price.valid());
+
+ if (commodity_t * commodity =
+ amount_t::current_pool->find_or_create(symbol))
+ commodity->add_price(datetime, price);
+ break;
+ }
+
+ case 'N': { // don't download prices
+ char * p = skip_ws(line + 1);
+ string symbol;
+ parse_symbol(p, symbol);
+
+ if (commodity_t * commodity =
+ amount_t::current_pool->find_or_create(symbol))
+ commodity->add_flags(COMMODITY_STYLE_NOMARKET);
+ break;
+ }
+
+ case 'Y': // set the current year
+ current_year = std::atoi(skip_ws(line + 1));
+ break;
+
+#ifdef TIMELOG_SUPPORT
+ case 'h':
+ case 'b':
+#endif
+ case '*': // comment line
+ case ';': // comment line
+ break;
+
+ case '-': { // option setting
+ char * p = next_element(line);
+ if (! p) {
+ p = std::strchr(line, '=');
+ if (p)
+ *p++ = '\0';
+ }
+ process_option(line + 2, session, p);
+ break;
+ }
+
+ case '=': { // automated entry
+ if (! added_auto_entry_hook) {
+ journal.add_entry_finalizer(&auto_entry_finalizer);
+ added_auto_entry_hook = true;
+ }
+
+ auto_entry_t * ae = new auto_entry_t(skip_ws(line + 1));
+ if (parse_xacts(in, account_stack.front(), *ae,
+ "automated", end_pos)) {
+ journal.auto_entries.push_back(ae);
+ ae->src_idx = src_idx;
+ ae->beg_pos = beg_pos;
+ ae->beg_line = beg_line;
+ ae->end_pos = end_pos;
+ ae->end_line = linenum;
+ }
+ break;
+ }
+
+ case '~': { // period entry
+ period_entry_t * pe = new period_entry_t(skip_ws(line + 1));
+ if (! pe->period)
+ throw_(parse_error, "Parsing time period '" << line << "'");
+
+ if (parse_xacts(in, account_stack.front(), *pe,
+ "period", end_pos)) {
+ if (pe->finalize()) {
+ extend_entry_base(&journal, *pe, true);
+ journal.period_entries.push_back(pe);
+ pe->src_idx = src_idx;
+ pe->beg_pos = beg_pos;
+ pe->beg_line = beg_line;
+ pe->end_pos = end_pos;
+ pe->end_line = linenum;
+ } else {
+ throw new parse_error("Period entry failed to balance");
+ }
+ }
+ break;
+ }
+
+ case '@':
+ case '!': { // directive
+ char * p = next_element(line);
+ string word(line + 1);
+ if (word == "include") {
+ push_variable<path> save_pathname(pathname);
+ push_variable<unsigned int> save_src_idx(src_idx);
+ push_variable<unsigned long> save_beg_pos(beg_pos);
+ push_variable<unsigned long> save_end_pos(end_pos);
+ push_variable<unsigned int> save_linenum(linenum);
+
+ pathname = p;
+#if 0
+ if (pathname[0] != '/' && pathname[0] != '\\' && pathname[0] != '~') {
+ string::size_type pos = save_pathname.prev.rfind('/');
+ if (pos == string::npos)
+ pos = save_pathname.prev.rfind('\\');
+ if (pos != string::npos)
+ pathname = string(save_pathname.prev, 0, pos + 1) + pathname;
+ }
+ pathname = resolve_path(pathname);
+
+ DEBUG("ledger.textual.include", "line " << linenum << ": " <<
+ "Including path '" << pathname << "'");
+
+ include_stack.push_back(std::pair<path, int>
+ (journal.sources.back(), linenum - 1));
+ count += parse_journal_file(pathname, config, journal,
+ account_stack.front());
+ include_stack.pop_back();
+#endif
+ }
+ else if (word == "account") {
+ account_t * acct;
+ acct = account_stack.front()->find_account(p);
+ account_stack.push_front(acct);
+ }
+ else if (word == "end") {
+ account_stack.pop_front();
+ }
+ else if (word == "alias") {
+ char * b = p;
+ if (char * e = std::strchr(b, '=')) {
+ char * z = e - 1;
+ while (std::isspace(*z))
+ *z-- = '\0';
+ *e++ = '\0';
+ e = skip_ws(e);
+
+ // Once we have an alias name (b) and the target account
+ // name (e), add a reference to the account in the
+ // `account_aliases' map, which is used by the xact
+ // parser to resolve alias references.
+ account_t * acct = account_stack.front()->find_account(e);
+ std::pair<accounts_map::iterator, bool> result
+ = account_aliases.insert(accounts_map::value_type(b, acct));
+ assert(result.second);
+ }
+ }
+ else if (word == "def") {
+ expr_t def(p);
+ def.compile(session); // causes definitions to be established
+ }
+ break;
+ }
+
+ default: {
+ unsigned long pos = beg_pos;
+ TRACE_START(entries, 1, "Time spent handling entries:");
+ if (entry_t * entry =
+ parse_entry(in, line, account_stack.front(), *this, pos)) {
+ if (journal.add_entry(entry)) {
+ entry->src_idx = src_idx;
+ entry->beg_pos = beg_pos;
+ entry->beg_line = beg_line;
+ entry->end_pos = pos;
+ entry->end_line = linenum;
+ count++;
+ } else {
+ checked_delete(entry);
+ throw parse_error("Entry does not balance");
+ }
+ } else {
+ throw parse_error("Failed to parse entry");
+ }
+ end_pos = pos;
+ TRACE_STOP(entries, 1);
+ break;
+ }
+ }
+ }
+ catch (const std::exception& err) {
+ for (std::list<std::pair<path, int> >::reverse_iterator i =
+ include_stack.rbegin();
+ i != include_stack.rend();
+ i++) {
+ add_error_context("In file included from ");
+#if 0
+ add_error_context(include_context((*i).first, (*i).second));
+#endif
+ }
+ add_error_context(file_context(pathname, linenum - 1));
+
+ std::cout.flush();
+ std::cerr << "Error: " << error_context() << err.what()
+ << std::endl;
+ errors++;
+ }
+ beg_pos = end_pos;
+ }
+
+ if (! time_entries.empty()) {
+ std::list<account_t *> accounts;
+
+ foreach (time_entry_t& time_entry, time_entries)
+ accounts.push_back(time_entry.account);
+
+ foreach (account_t * account, accounts)
+ clock_out_from_timelog(time_entries, current_time, account, NULL,
+ journal);
+
+ assert(time_entries.empty());
+ }
+
+ if (added_auto_entry_hook)
+ journal.remove_entry_finalizer(&auto_entry_finalizer);
+
+ if (errors > 0)
+ throw static_cast<int>(errors);
+
+ TRACE_STOP(parsing_total, 1);
+
+ return count;
+}
+
+void write_textual_journal(journal_t& journal,
+ const path& pathname,
+ xact_handler_ptr formatter,
+ const string& write_hdr_format,
+ std::ostream& out)
+{
+ unsigned long index = 0;
+ path found;
+
+ if (pathname.empty()) {
+ if (! journal.sources.empty())
+ found = *journal.sources.begin();
+ } else {
+#ifdef HAVE_REALPATH
+ char buf1[PATH_MAX];
+ char buf2[PATH_MAX];
+
+ ::realpath(pathname.string().c_str(), buf1);
+
+ foreach (const path& path, journal.sources) {
+ ::realpath(path.string().c_str(), buf2);
+ if (std::strcmp(buf1, buf2) == 0) {
+ found = path;
+ break;
+ }
+ index++;
+ }
+#else
+ foreach (const path& path, journal.sources) {
+ if (pathname == path) {
+ found = path;
+ break;
+ }
+ index++;
+ }
+#endif
+ }
+
+ if (found.empty())
+ throw_(std::runtime_error,
+ "Journal does not refer to file '" << pathname << "'");
+
+ entries_list::iterator el = journal.entries.begin();
+ auto_entries_list::iterator al = journal.auto_entries.begin();
+ period_entries_list::iterator pl = journal.period_entries.begin();
+
+ unsigned long pos = 0;
+
+ format_t hdr_fmt(write_hdr_format);
+ boost::filesystem::ifstream in(found);
+
+ while (! in.eof()) {
+ entry_base_t * base = NULL;
+ if (el != journal.entries.end() && pos == (*el)->beg_pos) {
+ hdr_fmt.format(out, **el);
+ base = *el++;
+ }
+ else if (al != journal.auto_entries.end() && pos == (*al)->beg_pos) {
+ out << "= " << (*al)->predicate.predicate.text() << '\n';
+ base = *al++;
+ }
+ else if (pl != journal.period_entries.end() && pos == (*pl)->beg_pos) {
+ out << "~ " << (*pl)->period_string << '\n';
+ base = *pl++;
+ }
+
+ char c;
+ if (base) {
+ foreach (xact_t * xact, base->xacts) {
+ if (! xact->has_flags(XACT_AUTO)) {
+ xact->xdata().add_flags(XACT_EXT_TO_DISPLAY);
+ (*formatter)(*xact);
+ }
+ }
+ formatter->flush();
+
+ while (pos < base->end_pos) {
+ in.get(c);
+ pos = static_cast<unsigned long>(in.tellg()); // pos++;
+ }
+ } else {
+ in.get(c);
+ pos = static_cast<unsigned long>(in.tellg()); // pos++;
+ out.put(c);
+ }
+ }
+}
+
+} // namespace ledger