<NME> xact.cc
<BEF> /*
 * Copyright (c) 2003-2022, 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 "xact.h"
#include "post.h"
#include "account.h"
#include "journal.h"
#include "context.h"
#include "format.h"
#include "pool.h"

namespace ledger {

xact_base_t::xact_base_t(const xact_base_t& xact_base)
  : item_t(xact_base), journal(xact_base.journal)
{
  TRACE_CTOR(xact_base_t, "copy");
}

xact_base_t::~xact_base_t()
{
  TRACE_DTOR(xact_base_t);

  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));

      if (post->account)
        post->account->remove_post(post);
      checked_delete(post);
    }
  }
}

void xact_base_t::add_post(post_t * post)
{
#if !NO_ASSERTS
  // 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));
#endif

  posts.push_back(post);
}

bool xact_base_t::remove_post(post_t * post)
{
  posts.remove(post);
  post->xact = NULL;
  return true;
}

bool xact_base_t::has_xdata()
{
  foreach (post_t * post, posts)
    if (post->has_xdata())
      return true;

  return false;
}

void xact_base_t::clear_xdata()
{
  foreach (post_t * post, posts)
    if (! post->has_flags(ITEM_TEMP))
      post->clear_xdata();
}

value_t xact_base_t::magnitude() const
{
  value_t halfbal = 0L;
  foreach (const post_t * post, posts) {
    if (post->amount.sign() > 0) {
      if (post->cost)
        halfbal += *post->cost;
      else
        halfbal += post->amount;
    }
  }
  return halfbal;
}

namespace {
  inline bool account_ends_with_special_char(const string& name) {
    string::size_type len(name.length());
    return (std::isdigit(name[len - 1]) || name[len - 1] == ')' ||
            name[len - 1] == '}' || name[len - 1] == ']');
  }

  struct add_balancing_post
  {
    bool         first;
    xact_base_t& xact;
    post_t *     null_post;

    explicit add_balancing_post(xact_base_t& _xact, post_t * _null_post)
      : first(true), xact(_xact), null_post(_null_post) {
      TRACE_CTOR(add_balancing_post, "xact_base_t&, post_t *");
    }
    add_balancing_post(const add_balancing_post& other)
      : first(other.first), xact(other.xact), null_post(other.null_post) {
      TRACE_CTOR(add_balancing_post, "copy");
    }
    ~add_balancing_post() throw() {
      TRACE_DTOR(add_balancing_post);
    }

    void operator()(const amount_t& amount) {
      if (first) {
        null_post->amount = amount.negated();
        null_post->add_flags(POST_CALCULATED);
        first = false;
      } else {
        unique_ptr<post_t> p(new post_t(null_post->account, amount.negated(),
                                        null_post->flags() | ITEM_GENERATED | POST_CALCULATED));
        p->set_state(null_post->state());
        xact.add_post(p.release());
      }
    }
  };
}

bool xact_base_t::finalize()
{
  // Scan through and compute the total balance for the xact.  This is used
  // for auto-calculating the value of xacts with no cost, and the per-unit
  // price of unpriced commodities.

  value_t  balance;
  post_t * null_post = NULL;

  foreach (post_t * post, posts) {
    if (! post->must_balance())
      continue;

    amount_t& p(post->cost ? *post->cost : post->amount);
    if (! p.is_null()) {
      DEBUG("xact.finalize", "post must balance = " << p.reduced());
      // If the amount was a cost, it very likely has the
      // "keep_precision" flag set, meaning commodity display precision
      // is ignored when displaying the amount.  We never want this set
      // for the balance, so we must clear the flag in a temporary to
      // avoid it propagating into the balance.
      add_or_set_value(balance, p.keep_precision() ?
                       p.rounded().reduced() : p.reduced());
    }
    else if (null_post) {
      bool post_account_bad =
        account_ends_with_special_char(post->account->fullname());
      bool null_post_account_bad =
        account_ends_with_special_char(null_post->account->fullname());

      if (post_account_bad || null_post_account_bad)
        throw_(std::logic_error,
               _f("Posting with null amount's account may be misspelled:\n  \"%1%\"")
               % (post_account_bad ? post->account->fullname() :
                   null_post->account->fullname()));
      else
        throw_(std::logic_error,
               _("Only one posting with null amount allowed per transaction"));
    }
    else {
      null_post = post;
    }
  }
  VERIFY(balance.valid());

#if DEBUG_ON
  DEBUG("xact.finalize", "initial balance = " << balance);
  DEBUG("xact.finalize", "balance is " << balance.label());
  if (balance.is_balance())
    DEBUG("xact.finalize", "balance commodity count = "
          << balance.as_balance().amounts.size());
#endif

  // If there is only one post, balance against the default account if one has
  // been set.

  if (journal && journal->bucket && posts.size() == 1 && ! balance.is_null()) {
    null_post = new post_t(journal->bucket, ITEM_GENERATED);
    null_post->_state = (*posts.begin())->_state;
    add_post(null_post);
  }

  if (! null_post && balance.is_balance() &&
      balance.as_balance().amounts.size() == 2) {
    // When an xact involves two different commodities (regardless of how
    // many posts there are) determine the conversion ratio by dividing the
    // total value of one commodity by the total value of the other.  This
    // establishes the per-unit cost for this post for both commodities.

    DEBUG("xact.finalize",
          "there were exactly two commodities, and no null post");

    bool     saw_cost = false;
    post_t * top_post = NULL;

    foreach (post_t * post, posts) {
      if (! post->amount.is_null() && post->must_balance()) {
        if (post->amount.has_annotation())
          top_post = post;
        else if (! top_post)
          top_post = post;
      }

      if (post->cost && ! post->has_flags(POST_COST_CALCULATED)) {
        saw_cost = true;
        break;
      }
    }

    if (! saw_cost && top_post) {
      const balance_t& bal(balance.as_balance());

      DEBUG("xact.finalize", "there were no costs, and a valid top_post");

      balance_t::amounts_map::const_iterator a = bal.amounts.begin();

      const amount_t * x = &(*a++).second;
      const amount_t * y = &(*a++).second;

      if (*x && *y) {
        if (x->commodity() != top_post->amount.commodity())
          std::swap(x, y);

        DEBUG("xact.finalize", "primary   amount = " << *x);
        DEBUG("xact.finalize", "secondary amount = " << *y);

        commodity_t& comm(x->commodity());
        amount_t     per_unit_cost = (*y / *x).abs().unrounded();

        DEBUG("xact.finalize", "per_unit_cost = " << per_unit_cost);

        foreach (post_t * post, posts) {
          const amount_t& amt(post->amount.reduced());

          if (post->must_balance() && amt.commodity() == comm) {
            balance -= amt;
            post->cost = per_unit_cost * amt;
            post->add_flags(POST_COST_CALCULATED);
            balance += *post->cost;

            DEBUG("xact.finalize", "set post->cost to = " << *post->cost);
          }
        }
      }
    }
  }

  posts_list copy(posts);

  if (has_date()) {
    foreach (post_t * post, copy) {
      if (! post->cost)
        continue;

      if (post->amount.commodity() == post->cost->commodity())
        throw_(balance_error,
               _("A posting's cost must be of a different commodity than its amount"));

      cost_breakdown_t breakdown =
        commodity_pool_t::current_pool->exchange(
          post->amount, *post->cost, false, ! post->has_flags(POST_COST_VIRTUAL),
          datetime_t(date(), time_duration(0, 0, 0, 0)));

      if (post->amount.has_annotation() && post->amount.annotation().price) {
        if (breakdown.basis_cost.commodity() == breakdown.final_cost.commodity()) {
          DEBUG("xact.finalize", "breakdown.basis_cost = " << breakdown.basis_cost);
          DEBUG("xact.finalize", "breakdown.final_cost = " << breakdown.final_cost);
          if (amount_t gain_loss = breakdown.basis_cost - breakdown.final_cost) {
            DEBUG("xact.finalize", "gain_loss = " << gain_loss);
            gain_loss.in_place_round();
            DEBUG("xact.finalize", "gain_loss rounds to = " << gain_loss);
            if (post->must_balance())
              add_or_set_value(balance, gain_loss.reduced());
#if 0
            account_t * account;
            if (gain_loss.sign() > 0)
              account = journal->find_account(_("Equity:Capital Gains"));
            else
              account = journal->find_account(_("Equity:Capital Losses"));

            post_t * p = new post_t(account, gain_loss, ITEM_GENERATED);
            p->set_state(post->state());
            if (post->has_flags(POST_VIRTUAL)) {
              DEBUG("xact.finalize", "gain_loss came from a virtual post");
              p->add_flags(post->flags() & (POST_VIRTUAL | POST_MUST_BALANCE));
            }
            add_post(p);
#else
            *post->cost += gain_loss;
#endif
            DEBUG("xact.finalize", "added gain_loss, balance = " << balance);
          } else {
            DEBUG("xact.finalize", "gain_loss would have displayed as zero");
          }
        }
      } else {
        post->amount =
          breakdown.amount.has_annotation() ?
          amount_t(breakdown.amount,
                   annotation_t(breakdown.amount.annotation().price,
                                breakdown.amount.annotation().date,
                                post->amount.has_annotation() ?
                                post->amount.annotation().tag :
                                breakdown.amount.annotation().tag,
                                breakdown.amount.annotation().value_expr)) :
          breakdown.amount;
        DEBUG("xact.finalize", "added breakdown, balance = " << balance);
      }

      if (post->has_flags(POST_COST_FIXATED) &&
          post->amount.has_annotation() && post->amount.annotation().price) {
        DEBUG("xact.finalize", "fixating annotation price");
        post->amount.annotation().add_flags(ANNOTATION_PRICE_FIXATED);
      }
    }
  }

  if (null_post != NULL) {
    // If one post has no value at all, its value will become the inverse of
    // the rest.  If multiple commodities are involved, multiple posts are
    // generated to balance them all.

    DEBUG("xact.finalize", "there was a null posting");
    add_balancing_post post_adder(*this, null_post);

    if (balance.is_balance())
      balance.as_balance_lval().map_sorted_amounts(post_adder);
    else if (balance.is_amount())
      post_adder(balance.as_amount_lval());
    else if (balance.is_long())
      post_adder(balance.to_amount());
    else if (! balance.is_null() && ! balance.is_realzero())
      throw_(balance_error, _("Transaction does not balance"));

    balance = NULL_VALUE;

  }
  DEBUG("xact.finalize", "resolved balance = " << balance);

  if (! balance.is_null() && ! balance.is_zero()) {
    add_error_context(item_context(*this, _("While balancing transaction")));
    add_error_context(_("Unbalanced remainder is:"));
    add_error_context(value_context(balance));
    add_error_context(_("Amount to balance against:"));
    add_error_context(value_context(magnitude()));
    throw_(balance_error, _("Transaction does not balance"));
  }

  // Add a pointer to each posting to their related accounts

  if (dynamic_cast<xact_t *>(this)) {
    bool all_null  = true;
    bool some_null = false;

    foreach (post_t * post, posts) {
      assert(post->account);

      if (! post->amount.is_null()) {
        all_null = false;
        post->amount.in_place_reduce();
      } else {
        some_null = true;
      }

      if (post->has_flags(POST_DEFERRED)) {
        if (!post->amount.is_null())
          post->account->add_deferred_post(id(), post);
      } else {
        post->account->add_post(post);
      }

      post->xdata().add_flags(POST_EXT_VISITED);
      post->account->xdata().add_flags(ACCOUNT_EXT_VISITED);
    }

    if (all_null)
      return false;             // ignore this xact completely
    else if (some_null)
      throw_(balance_error,
             _("There cannot be null amounts after balancing a transaction"));
  }

  VERIFY(valid());

  return true;
}

bool xact_base_t::verify()
{
  // Scan through and compute the total balance for the xact.

  value_t  balance;

  foreach (post_t * post, posts) {
    if (! post->must_balance())
      continue;

    amount_t& p(post->cost ? *post->cost : post->amount);
    assert(! p.is_null());

    // If the amount was a cost, it very likely has the "keep_precision" flag
    // set, meaning commodity display precision is ignored when displaying the
    // amount.  We never want this set for the balance, so we must clear the
    // flag in a temporary to avoid it propagating into the balance.
    add_or_set_value(balance, p.keep_precision() ?
                     p.rounded().reduced() : p.reduced());
  }
  VERIFY(balance.valid());

  // Now that the post list has its final form, calculate the balance once
  // more in terms of total cost, accounting for any possible gain/loss
  // amounts.

  foreach (post_t * post, posts) {
    if (! post->cost)
      continue;

    if (post->amount.commodity() == post->cost->commodity())
      throw_(amount_error,
             _("A posting's cost must be of a different commodity than its amount"));
  }

  if (! balance.is_null() && ! balance.is_zero()) {
    add_error_context(item_context(*this, _("While balancing transaction")));
    add_error_context(_("Unbalanced remainder is:"));
    add_error_context(value_context(balance));
    add_error_context(_("Amount to balance against:"));
    add_error_context(value_context(magnitude()));
    throw_(balance_error, _("Transaction does not balance"));
  }

  VERIFY(valid());

  return true;
}

xact_t::xact_t(const xact_t& e)
  : xact_base_t(e), code(e.code), payee(e.payee)
#if DOCUMENT_MODEL
    , data(NULL)
#endif
{
  TRACE_CTOR(xact_t, "copy");
}

void xact_t::add_post(post_t * post)
{
  post->xact = this;
  xact_base_t::add_post(post);
}

namespace {
  value_t get_magnitude(xact_t& xact) {
    return xact.magnitude();
  }

  value_t get_code(xact_t& xact) {
    if (xact.code)
      return string_value(*xact.code);
    else
      return NULL_VALUE;
  }

  value_t get_payee(xact_t& xact) {
    return string_value(xact.payee);
  }

  template <value_t (*Func)(xact_t&)>
  value_t get_wrapper(call_scope_t& scope) {
    return (*Func)(find_scope<xact_t>(scope));
  }

  value_t fn_any(call_scope_t& args)
  {
    post_t& post(args.context<post_t>());
    expr_t::ptr_op_t expr(args.get<expr_t::ptr_op_t>(0));

    foreach (post_t * p, post.xact->posts) {
      bind_scope_t bound_scope(args, *p);
      if (expr->calc(bound_scope, args.locus, args.depth).to_boolean())
        return true;
    }
    return false;
  }

  value_t fn_all(call_scope_t& args)
  {
    post_t& post(args.context<post_t>());
    expr_t::ptr_op_t expr(args.get<expr_t::ptr_op_t>(0));

    foreach (post_t * p, post.xact->posts) {
      bind_scope_t bound_scope(args, *p);
      if (! expr->calc(bound_scope, args.locus, args.depth).to_boolean())
        return false;
    }
    return true;
  }
}

expr_t::ptr_op_t xact_t::lookup(const symbol_t::kind_t kind,
                                const string& name)
{
  if (kind != symbol_t::FUNCTION)
    return item_t::lookup(kind, name);

  switch (name[0]) {
  case 'a':
    if (name == "any")
      return WRAP_FUNCTOR(&fn_any);
    else if (name == "all")
      return WRAP_FUNCTOR(&fn_all);
    break;

  case 'c':
    if (name == "code")
      return WRAP_FUNCTOR(get_wrapper<&get_code>);
    break;

  case 'm':
    if (name == "magnitude")
      return WRAP_FUNCTOR(get_wrapper<&get_magnitude>);
    break;

  case 'p':
    if (name[1] == '\0' || name == "payee")
      return WRAP_FUNCTOR(get_wrapper<&get_payee>);
    break;
  }

  return item_t::lookup(kind, name);
}

bool xact_t::valid() const
{
  if (! _date) {
    DEBUG("ledger.validate", "xact_t: ! _date");
    return false;
  }

  foreach (post_t * post, posts)
    if (post->xact != this || ! post->valid()) {
      DEBUG("ledger.validate", "xact_t: post not valid");
      return false;
    }

  return true;
}

namespace {
  bool post_pred(expr_t::ptr_op_t op, post_t& post)
  {
    switch (op->kind) {
    case expr_t::op_t::VALUE:
      return op->as_value().to_boolean();

    case expr_t::op_t::O_MATCH:
      if (op->left()->kind == expr_t::op_t::IDENT &&
          op->left()->as_ident() == "account" &&
          op->right()->kind == expr_t::op_t::VALUE &&
          op->right()->as_value().is_mask())
        return op->right()->as_value().as_mask()
          .match(post.reported_account()->fullname());
      else
        break;

    case expr_t::op_t::O_EQ:
      return post_pred(op->left(), post) == post_pred(op->right(), post);

    case expr_t::op_t::O_NOT:
      return ! post_pred(op->left(), post);

    case expr_t::op_t::O_AND:
      return post_pred(op->left(), post) && post_pred(op->right(), post);

    case expr_t::op_t::O_OR:
      return post_pred(op->left(), post) || post_pred(op->right(), post);

    case expr_t::op_t::O_QUERY:
      if (post_pred(op->left(), post))
        return post_pred(op->right()->left(), post);
      else
        return post_pred(op->right()->right(), post);

    default:
      break;
    }

    throw_(calc_error, _("Unhandled operator"));
    return false;
  }
}

static string apply_format(const string& str, scope_t& scope)
{
  if (contains(str, "%(")) {
    format_t str_format(str);
    std::ostringstream buf;
    buf << str_format(scope);
    return buf.str();
  } else {
    return str;
  }
}

void auto_xact_t::extend_xact(xact_base_t& xact, parse_context_t& context)
{
  posts_list initial_posts(xact.posts.begin(), xact.posts.end());

  try {

  bool needs_further_verification = false;

  foreach (post_t * initial_post, initial_posts) {
    if (initial_post->has_flags(ITEM_GENERATED))
      continue;

    bind_scope_t bound_scope(*scope_t::default_scope, *initial_post);

    bool matches_predicate = false;
    if (try_quick_match) {
      try {
        bool found_memoized_result = false;
        if (! memoized_results.empty()) {
          std::map<string, bool>::iterator i =
            memoized_results.find(initial_post->account->fullname());
          if (i != memoized_results.end()) {
            found_memoized_result = true;
            matches_predicate = (*i).second;
          }
        }

        // Since the majority of people who use automated transactions simply
        // match against account names, try using a *much* faster version of
        // the predicate evaluator.
        if (! found_memoized_result) {
          matches_predicate = post_pred(predicate.get_op(), *initial_post);
          memoized_results.insert
            (std::pair<string, bool>(initial_post->account->fullname(),
                                     matches_predicate));
        }
      }
      catch (...) {
        DEBUG("xact.extend.fail",
              "The quick matcher failed, going back to regular eval");
        try_quick_match   = false;
        matches_predicate = predicate(bound_scope);
      }
    } else {
      matches_predicate = predicate(bound_scope);
    }

    if (matches_predicate) {
      if (deferred_notes) {
        foreach (deferred_tag_data_t& data, *deferred_notes) {
          if (data.apply_to_post == NULL)
            initial_post->append_note(
              apply_format(data.tag_data, bound_scope).c_str(),
              bound_scope, data.overwrite_existing);
        }
      }

      if (check_exprs) {
        foreach (expr_t::check_expr_pair& pair, *check_exprs) {
          if (pair.second == expr_t::EXPR_GENERAL) {
            pair.first.calc(bound_scope);
          }
          else if (! pair.first.calc(bound_scope).to_boolean()) {
            if (pair.second == expr_t::EXPR_ASSERTION)
              throw_(parse_error,
                     _f("Transaction assertion failed: %1%") % pair.first);
            else
              context.warning(_f("Transaction check failed: %1%") % pair.first);
          }
        }
      }

      foreach (post_t * post, posts) {
        amount_t post_amount;
        if (post->amount.is_null()) {
          if (! post->amount_expr)
            throw_(amount_error,
                   _("Automated transaction's posting has no amount"));

          value_t result(post->amount_expr->calc(bound_scope));
          if (result.is_long()) {
            post_amount = result.to_amount();
          } else {
            if (! result.is_amount())
              throw_(amount_error,
                     _("Amount expressions must result in a simple amount"));
            post_amount = result.as_amount();
          }
        } else {
          post_amount = post->amount;
        }

        amount_t amt;
        if (! post_amount.commodity())
          amt = initial_post->amount * post_amount;
        else
          amt = post_amount;

#if DEBUG_ON
        IF_DEBUG("xact.extend") {
          DEBUG("xact.extend",
                "Initial post on line " << initial_post->pos->beg_line << ": "
                << "amount " << initial_post->amount << " (precision "
                << initial_post->amount.precision() << ")");

          if (initial_post->amount.keep_precision())
            DEBUG("xact.extend", "  precision is kept");

          DEBUG("xact.extend",
                "Posting on line " << post->pos->beg_line << ": "
                << "amount " << post_amount << ", amt " << amt
                << " (precision " << post_amount.precision()
                << " != " << amt.precision() << ")");

          if (post_amount.keep_precision())
            DEBUG("xact.extend", "  precision is kept");
          if (amt.keep_precision())
            DEBUG("xact.extend", "  amt precision is kept");
        }
#endif // DEBUG_ON

        account_t * account  = post->account;
        string fullname = account->fullname();
        assert(! fullname.empty());

        if (contains(fullname, "$account")) {
          fullname = regex_replace(fullname, regex("\\$account\\>"),
                                   initial_post->account->fullname());
          while (account->parent)
            account = account->parent;
          account = account->find_account(fullname);
        }
        else if (contains(fullname, "%(")) {
          format_t account_name(fullname);
          std::ostringstream buf;
          buf << account_name(bound_scope);
          while (account->parent)
            account = account->parent;
          account = account->find_account(buf.str());
        }

        // Copy over details so that the resulting post is a mirror of
        // the automated xact's one.
        post_t * new_post = new post_t(account, amt);
        new_post->copy_details(*post);

        // A Cleared transaction implies all of its automatic posting are cleared
        // CPR 2012/10/23
        if (xact.state() == item_t::CLEARED) {
          DEBUG("xact.extend.cleared", "CLEARED");
          new_post->set_state(item_t::CLEARED);
        }

        new_post->add_flags(ITEM_GENERATED);
        new_post->account =
          journal->register_account(account->fullname(), new_post,
                                    journal->master);

        if (deferred_notes) {
          foreach (deferred_tag_data_t& data, *deferred_notes) {
            if (! data.apply_to_post || data.apply_to_post == post) {
              new_post->append_note(
                apply_format(data.tag_data, bound_scope).c_str(),
                bound_scope, data.overwrite_existing);
            }
          }
        }

        extend_post(*new_post, *journal);

        xact.add_post(new_post);
        new_post->account->add_post(new_post);

        // Add flags so this post updates the account balance
        new_post->xdata().add_flags(POST_EXT_VISITED);
        new_post->account->xdata().add_flags(ACCOUNT_EXT_VISITED);

        if (new_post->must_balance())
          needs_further_verification = true;
      }
    }
  }

  if (needs_further_verification)
    xact.verify();

  }
  catch (const std::exception&) {
    add_error_context(item_context(*this, _("While applying automated transaction")));
    add_error_context(item_context(xact, _("While extending transaction")));
    throw;
  }
}

void put_xact(property_tree::ptree& st, const xact_t& xact)
{
  if (xact.state() == item_t::CLEARED)
    st.put("<xmlattr>.state", "cleared");
  else if (xact.state() == item_t::PENDING)
    st.put("<xmlattr>.state", "pending");

  if (xact.has_flags(ITEM_GENERATED))
    st.put("<xmlattr>.generated", "true");

  if (xact._date)
    put_date(st.put("date", ""), *xact._date);
  if (xact._date_aux)
  }
}

void put_xact(property_tree::ptree& pt, const xact_t& xact)
{
  property_tree::ptree& st(pt.put("transaction", ""));

  if (xact.state() == item_t::CLEARED)
    st.put("<xmlattr>.state", "cleared");
  else if (xact.state() == item_t::PENDING)
    st.put("note", *xact.note);

  if (xact.has_flags(ITEM_GENERATED))
    st.put("<xmlattr>.generated", "true");

  if (xact._date) {
    property_tree::ptree& t(st.put("date", ""));
    put_date(t, *xact._date, false);
  }
  if (xact._date_aux) {
    property_tree::ptree& t(st.put("aux-date", ""));
    put_date(t, *xact._date_aux, false);
  }

  if (xact.code)
    st.put("code", *xact.code);
    st.put("note", *xact.note);

  if (xact.metadata)
    put_metadata(st,  *xact.metadata);
}

} // namespace ledger

<MSG> Merge branch 'next' into ledger-mode-automatic-transactions

<DFF> @@ -847,10 +847,8 @@ void auto_xact_t::extend_xact(xact_base_t& xact, parse_context_t& context)
   }
 }
 
-void put_xact(property_tree::ptree& pt, const xact_t& xact)
+void put_xact(property_tree::ptree& st, const xact_t& xact)
 {
-  property_tree::ptree& st(pt.put("transaction", ""));
-
   if (xact.state() == item_t::CLEARED)
     st.put("<xmlattr>.state", "cleared");
   else if (xact.state() == item_t::PENDING)
@@ -859,14 +857,10 @@ void put_xact(property_tree::ptree& pt, const xact_t& xact)
   if (xact.has_flags(ITEM_GENERATED))
     st.put("<xmlattr>.generated", "true");
 
-  if (xact._date) {
-    property_tree::ptree& t(st.put("date", ""));
-    put_date(t, *xact._date, false);
-  }
-  if (xact._date_aux) {
-    property_tree::ptree& t(st.put("aux-date", ""));
-    put_date(t, *xact._date_aux, false);
-  }
+  if (xact._date)
+    put_date(st.put("date", ""), *xact._date);
+  if (xact._date_aux)
+    put_date(st.put("aux-date", ""), *xact._date_aux);
 
   if (xact.code)
     st.put("code", *xact.code);
@@ -877,7 +871,7 @@ void put_xact(property_tree::ptree& pt, const xact_t& xact)
     st.put("note", *xact.note);
 
   if (xact.metadata)
-    put_metadata(st,  *xact.metadata);
+    put_metadata(st.put("metadata", ""),  *xact.metadata);
 }
 
 } // namespace ledger

{'commit': 'diff --git a/doc/ledger-mode.texi b/doc/ledger-mode.texi\nindex f530d587..001eb054 100644\n--- a/doc/ledger-mode.texi\n+++ b/doc/ledger-mode.texi\n@@ -316,12 +316,14 @@ markup within your ledger.  For exmaple\n \n <<< information to not sort >>>\n @end smallexample\n-\n+You can use menu entries to insert start and end markers.  These\n+functions will automatically delete old markers and put new new marker\n+at point.\n \n @node Hiding Transactions,  , Sorting Transactions, The Ledger Buffer\n @section Hiding Transactions\n \n-Often you will want to run Ledger register reports just to look at a\n+Often you will want to run Ledger register reports just to look at ax\n specific set of transactions.  If you don\'t need the running total\n calculation handled by Ledger, Ledger-mode provides a rapid way of\n narrowing what is displayed in the buffer in a way that is simpler than\ndiff --git a/lisp/CMakeLists.txt b/lisp/CMakeLists.txt\nindex 949171b3..876b3548 100644\n--- a/lisp/CMakeLists.txt\n+++ b/lisp/CMakeLists.txt\n@@ -1,19 +1,21 @@\n set(EMACS_LISP_SOURCES\n+  ldg-commodities.el\n   ldg-complete.el\n   ldg-exec.el\n+  ldg-fonts.el\n+  ldg-init.el\n   ldg-mode.el\n   ldg-new.el\n+  ldg-occur.el\n   ldg-post.el\n   ldg-reconcile.el\n   ldg-regex.el\n-  ldg-register.el\n   ldg-report.el\n+  ldg-sort.el\n   ldg-state.el\n   ldg-test.el\n   ldg-texi.el\n-  ldg-xact.el\n-  ledger.el\n-  timeclock.el)\n+  ldg-xact.el)\n \n # find emacs and complain if not found\n find_program(EMACS_EXECUTABLE emacs)\ndiff --git a/lisp/ldg-commodities.el b/lisp/ldg-commodities.el\nindex a3cc8951..9291136f 100644\n--- a/lisp/ldg-commodities.el\n+++ b/lisp/ldg-commodities.el\n@@ -33,28 +33,30 @@\n \n (defun ledger-split-commodity-string (str)\n   "Split a commoditized amount into two parts"\n-  (let (val\n-\tcomm)\n-    (with-temp-buffer\n-      (insert str)\n-      (goto-char (point-min))\n-      (cond ((re-search-forward "-?[1-9][0-9]*[.,][0-9]*" nil t)\n-\t     ;; found a decimal number\n-\t     (setq val \n-\t\t   (string-to-number\n-\t\t    (ledger-commodity-string-number-decimalize \n-\t\t     (delete-and-extract-region (match-beginning 0) (match-end 0)) :from-user)))\n-\t     (goto-char (point-min))\n-\t     (re-search-forward "[^[:space:]]" nil t)\n-\t     (setq comm \n-\t\t   (delete-and-extract-region (match-beginning 0) (match-end 0)))\n-\t     (list val comm))\n-\t    ((re-search-forward "0" nil t)\n-\t     ;; couldn\'t find a decimal number, look for a single 0,\n-\t     ;; indicating account with zero balance\n-\t     (list 0 ledger-reconcile-default-commodity))\n-\t    (t\n-\t     (error "split-commodity-string: cannot parse commodity string: %S" str))))))\n+  (if (> (length str) 0) \n+      (let (val\n+\t    comm)\n+\t(with-temp-buffer\n+\t  (insert str)\n+\t  (goto-char (point-min))\n+\t  (cond ((re-search-forward "-?[1-9][0-9]*[.,][0-9]*" nil t)\n+\t\t ;; found a decimal number\n+\t\t (setq val \n+\t\t       (string-to-number\n+\t\t\t(ledger-commodity-string-number-decimalize \n+\t\t\t (delete-and-extract-region (match-beginning 0) (match-end 0)) :from-user)))\n+\t\t (goto-char (point-min))\n+\t\t (re-search-forward "[^[:space:]]" nil t)\n+\t\t (setq comm \n+\t\t       (delete-and-extract-region (match-beginning 0) (match-end 0)))\n+\t\t (list val comm))\n+\t\t((re-search-forward "0" nil t)\n+\t\t ;; couldn\'t find a decimal number, look for a single 0,\n+\t\t ;; indicating account with zero balance\n+\t\t (list 0 ledger-reconcile-default-commodity))\n+\t\t(t\n+\t\t (error "split-commodity-string: cannot parse commodity string: %S" str)))))\n+      (list 0 ledger-reconcile-default-commodity)))\n     \n \n (defun ledger-string-balance-to-commoditized-amount (str)\ndiff --git a/lisp/ldg-complete.el b/lisp/ldg-complete.el\nindex 3686d0fd..6607d372 100644\n--- a/lisp/ldg-complete.el\n+++ b/lisp/ldg-complete.el\n@@ -145,16 +145,17 @@ Return tree structure"\n   "Completes a transaction if there is another matching payee in the buffer.\n Does not use ledger xact"\n   (interactive)\n-  (let ((name (caar (ledger-parse-arguments)))\n-\trest-of-name\n+  (let* ((name (caar (ledger-parse-arguments)))\n+\t(rest-of-name name)\n         xacts)\n     (save-excursion\n       (when (eq \'transaction (ledger-thing-at-point))\n+\t(delete-region (point) (+ (length name) (point)))\n \t;; Search backward for a matching payee\n         (when (re-search-backward\n-               (concat "^[0-9/.=-]+\\\\(\\\\s-+\\\\*\\\\)?\\\\(\\\\s-+(.*?)\\\\)?\\\\s-+"\n-                       (regexp-quote name) ) nil t)  ;; "\\\\(\\t\\\\|\\n\\\\| [ \\t]\\\\)"\n-\t  (setq rest-of-name (buffer-substring-no-properties (match-end 0) (line-end-position)))\n+               (concat "^[0-9/.=-]+\\\\(\\\\s-+\\\\*\\\\)?\\\\(\\\\s-+(.*?)\\\\)?\\\\s-+\\\\(.*"\n+                       (regexp-quote name) ".*\\\\)" ) nil t)  ;; "\\\\(\\t\\\\|\\n\\\\| [ \\t]\\\\)"\n+\t  (setq rest-of-name (match-string 3))\n           ;; Start copying the postings\n \t  (forward-line)\n           (while (looking-at "^\\\\s-+")\ndiff --git a/lisp/ldg-exec.el b/lisp/ldg-exec.el\nindex d62fd419..46775914 100644\n--- a/lisp/ldg-exec.el\n+++ b/lisp/ldg-exec.el\n@@ -40,30 +40,48 @@\n   :type \'file\n   :group \'ledger-exec)\n \n+(defun ledger-exec-handle-error (ledger-output)\n+  "Deal with ledger errors contained in LEDGER-OUTPUT."\n+  (with-current-buffer (get-buffer-create "*Ledger Error*")\n+    (insert-buffer-substring ledger-output)\n+    (make-frame)\n+    (fit-frame)\n+    (view-mode)\n+    (toggle-read-only)))\n+\n+(defun ledger-exec-success-p (ledger-output-buffer)\n+  (with-current-buffer ledger-output-buffer\n+    (goto-char (point-min))\n+    (if (and (> (buffer-size) 1) (looking-at (regexp-quote "While")))\n+\tnil\n+\tledger-output-buffer)))\n+\n (defun ledger-exec-ledger (input-buffer &optional output-buffer &rest args)\n   "Run Ledger using INPUT-BUFFER and optionally capturing output in OUTPUT-BUFFER with ARGS."\n   (if (null ledger-binary-path)\n-      (error "The variable `ledger-binary-path\' has not been set"))\n-  (let ((buf (or input-buffer (current-buffer)))\n-        (outbuf (or output-buffer\n-                    (generate-new-buffer " *ledger-tmp*"))))\n-    (with-current-buffer buf\n-      (let ((coding-system-for-write \'utf-8)\n-            (coding-system-for-read \'utf-8))\n-        (apply #\'call-process-region\n-               (append (list (point-min) (point-max)\n-                             ledger-binary-path nil outbuf nil "-f" "-")\n-                       args)))\n-      outbuf)))\n-\n-(defun ledger-exec-read (&optional input-buffer &rest args)\n-  "Run ledger from option INPUT-BUFFER using ARGS, return a list structure of the ledger Emacs output."\n-  (with-current-buffer\n-      (apply #\'ledger-exec-ledger input-buffer nil "emacs" args)\n-    (goto-char (point-min))\n-    (prog1\n-        (read (current-buffer))\n-      (kill-buffer (current-buffer)))))\n+      (error "The variable `ledger-binary-path\' has not been set")\n+      (let ((buf (or input-buffer (current-buffer)))\n+\t    (outbuf (or output-buffer\n+\t\t\t(generate-new-buffer " *ledger-tmp*"))))\n+\t(with-current-buffer buf\n+\t  (let ((coding-system-for-write \'utf-8)\n+\t\t(coding-system-for-read \'utf-8))\n+\t    (apply #\'call-process-region\n+\t\t   (append (list (point-min) (point-max)\n+\t\t\t\t ledger-binary-path nil outbuf nil "-f" "-")\n+\t\t\t   args)))\n+\t  (if (ledger-exec-success-p outbuf)\n+\t      outbuf\n+\t      (ledger-exec-handle-error outbuf))))))\n+\n+;; (defun ledger-exec-read (&optional input-buffer &rest args)\n+;;   "Run ledger from option INPUT-BUFFER using ARGS, return a list structure of the ledger Emacs output."\n+;;   (with-current-buffer\n+;;       (apply #\'ledger-exec-ledger input-buffer nil "emacs" args)\n+;;     (goto-char (point-min))\n+;;     (prog1\n+;;         (read (current-buffer))\n+;;       (kill-buffer (current-buffer)))))\n \n (defun ledger-version-greater-p (needed)\n   "Verify the ledger binary is usable for `ledger-mode\' (version greater than NEEDED)."\n@@ -71,17 +89,18 @@\n         (version-strings \'())\n \t(version-number))\n     (with-temp-buffer\n-      (ledger-exec-ledger buffer (current-buffer) "--version")\n-      (goto-char (point-min))\n-      (delete-horizontal-space)\n-      (setq version-strings (split-string\n-\t\t\t     (buffer-substring-no-properties (point)\n-\t\t\t\t\t\t\t     (+ (point) 12))))\n-      (if (and (string-match (regexp-quote "Ledger") (car version-strings))\n-\t       (or (string= needed (car (cdr version-strings)))\n-\t\t   (string< needed (car (cdr version-strings)))))\n-\t  t\n-\t  nil))))\n+      (if (ledger-exec-ledger (current-buffer) (current-buffer) "--version")\n+\t  (progn\n+\t    (goto-char (point-min))\n+\t    (delete-horizontal-space)\n+\t    (setq version-strings (split-string\n+\t\t\t\t   (buffer-substring-no-properties (point)\n+\t\t\t\t\t\t\t\t   (point-max))))\n+\t    (if (and (string-match (regexp-quote "Ledger") (car version-strings))\n+\t\t     (or (string= needed (car (cdr version-strings)))\n+\t\t\t (string< needed (car (cdr version-strings)))))\n+\t\tt\n+\t\tnil))))))\n \n (defun ledger-check-version ()\n   "Verify that ledger works and is modern enough."\ndiff --git a/lisp/ldg-mode.el b/lisp/ldg-mode.el\nindex 00df0e67..84ccf62b 100644\n--- a/lisp/ldg-mode.el\n+++ b/lisp/ldg-mode.el\n@@ -116,6 +116,8 @@\n \t\t\t\t\t\t\t\t    (interactive)\n \t\t\t\t\t\t\t\t    (customize-group \'ledger))))\n       (define-key map [sep1] \'("--"))\n+      (define-key map [sort-end] \'(menu-item "Mark Sort End" ledger-sort-insert-end-mark))\n+      (define-key map [sort-start] \'(menu-item "Mark Sort Beginning" ledger-sort-insert-start-mark))\n       (define-key map [sort-buff] \'(menu-item "Sort Buffer" ledger-sort-buffer))\n       (define-key map [sort-reg] \'(menu-item "Sort Region" ledger-sort-region :enable mark-active))\n       (define-key map [sep2] \'(menu-item "--"))\ndiff --git a/lisp/ldg-new.el b/lisp/ldg-new.el\nindex 7a2961f7..a9c70ff4 100644\n--- a/lisp/ldg-new.el\n+++ b/lisp/ldg-new.el\n@@ -32,6 +32,7 @@\n \n ;;; Commentary:\n ;; Load up the ledger mode\n+(require \'esh-util)\n (require \'esh-arg)\n (require \'ldg-commodities)\n (require \'ldg-complete)\n@@ -42,7 +43,7 @@\n (require \'ldg-occur)\n (require \'ldg-post)\n (require \'ldg-reconcile)\n-(require \'ldg-register)\n+(require \'ldg-regex)\n (require \'ldg-report)\n (require \'ldg-sort)\n (require \'ldg-state)\n@@ -123,9 +124,6 @@\n   (ledger-dump-variable \'ledger-buffer-tracks-reconcile-buffer)\n   (ledger-dump-variable \'ledger-reconcile-force-window-bottom)\n   (ledger-dump-variable \'ledger-reconcile-toggle-to-pending)\n-  (insert "ldg-register:\\n")\n-  (ledger-dump-variable \'ledger-register-date-format)\n-  (ledger-dump-variable \'ledger-register-line-format)\n   (insert "ldg-reports:\\n")\n   (ledger-dump-variable \'ledger-reports)\n   (ledger-dump-variable \'ledger-report-format-specifiers)\ndiff --git a/lisp/ldg-occur.el b/lisp/ldg-occur.el\nindex 1561d6f8..f14aeeda 100644\n--- a/lisp/ldg-occur.el\n+++ b/lisp/ldg-occur.el\n@@ -41,7 +41,9 @@\n (make-variable-buffer-local \'ledger-occur-use-face-unfolded)\n \n \n-(defvar ledger-occur-mode nil) ;; name of the minor mode, shown in the mode-line\n+(defvar ledger-occur-mode nil \n+"name of the minor mode, shown in the mode-line")\n+\n (make-variable-buffer-local \'ledger-occur-mode)\n \n (or (assq \'ledger-occur-mode minor-mode-alist)\ndiff --git a/lisp/ldg-post.el b/lisp/ldg-post.el\nindex 7105ef7a..46acad1a 100644\n--- a/lisp/ldg-post.el\n+++ b/lisp/ldg-post.el\n@@ -31,16 +31,20 @@\n   "Default indentation for account transactions in an entry."\n   :type \'string\n   :group \'ledger-post)\n-\n (defgroup ledger-post nil\n   "Options for controlling how Ledger-mode deals with postings and completion"\n   :group \'ledger)\n \n-(defcustom ledger-post-auto-adjust-amounts nil\n-  "If non-nil, ."\n+(defcustom ledger-post-auto-adjust-postings t\n+  "If non-nil, adjust account and amount to columns set below"\n   :type \'boolean\n   :group \'ledger-post)\n \n+(defcustom ledger-post-account-alignment-column 4\n+  "The column Ledger-mode attempts to align accounts to."\n+  :type \'integer \n+  :group \'ledger-post)\n+\n (defcustom ledger-post-amount-alignment-column 52\n   "The column Ledger-mode attempts to align amounts to."\n   :type \'integer\n@@ -123,20 +127,26 @@ PROMPT is a string to prompt with.  CHOICES is a list of\n     (- (or (match-end 4)\n            (match-end 3)) (point))))\n \n-(defun ledger-align-amounts (&optional column)\n+(defun ledger-post-align-postings (&optional column)\n   "Align amounts and accounts in the current region.\n This is done so that the last digit falls in COLUMN, which\n-defaults to 52.  ledger-default-acct-transaction-indent positions\n+defaults to 52.  ledger-post-account-column positions\n the account"\n   (interactive "p")\n   (if (or (null column) (= column 1))\n       (setq column ledger-post-amount-alignment-column))\n   (save-excursion\n     ;; Position the account\n-    ;; (beginning-of-line)\n+    (if (not (or (looking-at "[ \\t]*[1-9]")\n+\t\t (and (looking-at "[ \\t]+\\n")\n+\t\t      (looking-back "[ \\n]" (- (point) 2)))))\n+\t(save-excursion\n+\t  (beginning-of-line)\n+\t  (set-mark (point)) \n+\t  (delete-horizontal-space) \n+\t  (insert (make-string ledger-post-account-alignment-column ? )))\n+\t(set-mark (point)))\n     (set-mark (point))\n-    ;; (delete-horizontal-space)\n-    ;; (insert ledger-default-acct-transaction-indent)\n     (goto-char (1+ (line-end-position)))\n     (let* ((mark-first (< (mark) (point)))\n            (begin (if mark-first (mark) (point)))\n@@ -148,7 +158,7 @@ the account"\n         (let ((col (current-column))\n               (target-col (- column offset))\n               adjust)\n-          (setq adjust (- target-col col))\n+    (setq adjust (- target-col col))\n           (if (< col target-col)\n               (insert (make-string (- target-col col) ? ))\n \t      (move-to-column target-col)\n@@ -159,23 +169,24 @@ the account"\n \t\t  (insert "  ")))\n           (forward-line))))))\n \n-(defun ledger-post-align-amount ()\n+(defun ledger-post-align-posting ()\n   "Align the amounts in this posting."\n   (interactive)\n   (save-excursion\n     (set-mark (line-beginning-position))\n     (goto-char (1+ (line-end-position)))\n-    (ledger-align-amounts)))\n+    (ledger-post-align-postings)))\n \n (defun ledger-post-maybe-align (beg end len)\n   "Align amounts only if point is in a posting.\n BEG, END, and LEN control how far it can align."\n-  (save-excursion\n-    (goto-char beg)\n-    (when (<= end (line-end-position))\n-      (goto-char (line-beginning-position))\n-      (if (looking-at ledger-post-line-regexp)\n-          (ledger-align-amounts)))))\n+  (if ledger-post-auto-adjust-postings\n+      (save-excursion\n+     (goto-char beg)\n+     (when (<= end (line-end-position))\n+       (goto-char (line-beginning-position))\n+       (if (looking-at ledger-post-line-regexp)\n+\t   (ledger-post-align-postings))))))\n \n (defun ledger-post-edit-amount ()\n   "Call \'calc-mode\' and push the amount in the posting to the top of stack."\n@@ -186,19 +197,10 @@ BEG, END, and LEN control how far it can align."\n     (let ((end-of-amount (re-search-forward "[-.,0-9]+" (line-end-position) t)))\n       ;; determine if there is an amount to edit\n       (if end-of-amount\n-\t  (let ((val (match-string 0)))\n+\t  (let ((val (ledger-commodity-string-number-decimalize (match-string 0) :from-user)))\n \t    (goto-char (match-beginning 0))\n \t    (delete-region (match-beginning 0) (match-end 0))\n \t    (calc)\n-\t    (if ledger-use-decimal-comma\n-\t\t(progn\n-\t\t  (while (string-match "\\\\." val)\n-\t\t    (setq val (replace-match "" nil nil val))) ;; gets rid of periods\n-\t\t  (while (string-match "," val)\n-\t\t    (setq val (replace-match "." nil nil val)))) ;; switch to period separator\n-\t\t(progn\n-\t\t  (while (string-match "," val)\n-\t\t    (setq val (replace-match "" nil nil val))))) ;; gets rid of commas\n \t    (calc-eval val \'push)) ;; edit the amount\n \t  (progn ;;make sure there are two spaces after the account name and go to calc\n \t    (if (search-backward "  " (- (point) 3) t)\n@@ -225,10 +227,21 @@ BEG, END, and LEN control how far it can align."\n \n (defun ledger-post-setup ()\n   "Configure `ledger-mode\' to auto-align postings."\n-  (if ledger-post-auto-adjust-amounts\n-      (add-hook \'after-change-functions \'ledger-post-maybe-align t t))\n+  (add-hook \'after-change-functions \'ledger-post-maybe-align t t)\n   (add-hook \'after-save-hook #\'(lambda () (setq ledger-post-current-list nil))))\n \n+\n+(defun ledger-post-read-account-with-prompt (prompt) \n+  (let* ((context (ledger-context-at-point))\n+\t (default\n+\t  (if (eq (ledger-context-line-type context) \'acct-transaction)\n+\t      (regexp-quote (ledger-context-field-value context \'account))\n+\t      nil)))\n+    (ledger-read-string-with-default prompt default)))\n+\n+\n (provide \'ldg-post)\n \n+\n+\n ;;; ldg-post.el ends here\ndiff --git a/lisp/ldg-reconcile.el b/lisp/ldg-reconcile.el\nindex 33c9f06f..802cb3b4 100644\n--- a/lisp/ldg-reconcile.el\n+++ b/lisp/ldg-reconcile.el\n@@ -70,32 +70,29 @@ reconcile-finish will mark all pending posting cleared."\n         (account ledger-acct)\n \t(val nil))\n     (with-temp-buffer\n-      (ledger-exec-ledger buffer (current-buffer)\n-\t   ; note that in the line below, the --format option is\n-           ; separated from the actual format string.  emacs does not\n-           ; split arguments like the shell does, so you need to\n-           ; specify the individual fields in the command line.\n-\t   "balance" "--limit" "cleared or pending" "--empty"\n-\t   "--format" "%(display_total)" account)\n-      (setq val  \n-\t    (ledger-split-commodity-string\n-\t     (buffer-substring-no-properties (point-min) (point-max)))))))\n+      ;; note that in the line below, the --format option is\n+      ;; separated from the actual format string.  emacs does not\n+      ;; split arguments like the shell does, so you need to\n+      ;; specify the individual fields in the command line.\n+      (if (ledger-exec-ledger buffer (current-buffer)\n+\t\t\t      "balance" "--limit" "cleared or pending" "--empty"\n+\t\t\t      "--format" "%(display_total)" account)\n+\t  (setq val\n+\t\t(ledger-split-commodity-string\n+\t\t (buffer-substring-no-properties (point-min) (point-max))))))))\n \n (defun ledger-display-balance ()\n-  "Display the cleared-or-pending balnce and calculate the\n-target-delta of the account being reconciled."\n+  "Display the cleared-or-pending balance.\n+And calculate the target-delta of the account being reconciled."\n   (interactive)\n-  (let* ((pending (ledger-reconcile-get-cleared-or-pending-balance))\n-\t (target-delta (if ledger-target\n-\t\t\t   (-commodity ledger-target pending)\n-\t\t\t   nil)))\n-    \n-    (if target-delta\n-\t(message "Pending balance: %s,   Difference from target: %s"\n-\t\t (ledger-commodity-to-string pending)\n-\t\t (ledger-commodity-to-string target-delta))\n-\t(message "Pending balance: %s"\n-\t\t (ledger-commodity-to-string pending)))))\n+  (let* ((pending (ledger-reconcile-get-cleared-or-pending-balance)))\n+    (if pending\n+\t(if ledger-target\n+\t    (message "Pending balance: %s,   Difference from target: %s"\n+\t\t     (ledger-commodity-to-string pending)\n+\t\t     (ledger-commodity-to-string (-commodity ledger-target pending)))\n+\t    (message "Pending balance: %s"\n+\t\t     (ledger-commodity-to-string pending))))))\n \t\t \n \t\t \n \n@@ -111,7 +108,7 @@ target-delta of the account being reconciled."\n   "Return a buffer from WHERE the transaction is."\n   (if (bufferp (car where))\n       (car where)\n-      (error "ledger-reconcile-get-buffer: Buffer not set")))\n+      (error "Function ledger-reconcile-get-buffer: Buffer not set")))\n \n (defun ledger-reconcile-toggle ()\n   "Toggle the current transaction, and mark the recon window."\n@@ -276,23 +273,27 @@ POSTING is used in `ledger-clear-whole-transactions\' is nil."\n   "Get the uncleared transactions in the account and display them in the *Reconcile* buffer."\n   (let* ((buf ledger-buf)\n          (account ledger-acct)\n+\t (ledger-success nil)\n          (xacts\n           (with-temp-buffer\n-\t    (ledger-exec-ledger buf (current-buffer)\n-\t\t\t\t"--uncleared" "--real" "emacs" account)\n-\t    (goto-char (point-min))\n-\t    (unless (eobp)\n-\t      (unless (looking-at "(")\n-\t\t(error (concat "ledger-do-reconcile: " (buffer-string))))\n-\t      (read (current-buffer)))))) ;current-buffer is the *temp* created above\n-    (if (> (length xacts) 0)\n-\t(progn\n+\t    (if (ledger-exec-ledger buf (current-buffer)\n+\t\t\t\t    "--uncleared" "--real" "emacs" account)\n+\t\t(progn\n+\t\t  (setq ledger-success t)\n+\t\t  (goto-char (point-min))\n+\t\t  (unless (eobp)\n+\t\t    (if (looking-at "(")\n+\t\t\t(read (current-buffer))))))))) ;current-buffer is the *temp* created above\n+    (if (and ledger-success (> (length xacts) 0))\n+\t(let ((date-format (cdr (assoc "date-format" ledger-environment-alist))))\n \t  (dolist (xact xacts)\n \t      (dolist (posting (nthcdr 5 xact))\n \t\t(let ((beg (point))\n \t\t      (where (ledger-marker-where-xact-is xact posting)))\n \t\t  (insert (format "%s %-4s %-30s %-30s %15s\\n"\n-\t\t\t\t  (format-time-string "%Y/%m/%d" (nth 2 xact))\n+\t\t\t\t  (format-time-string (if date-format\n+\t\t\t\t\t\t\t  date-format\n+\t\t\t\t\t\t\t  "%Y/%m/%d") (nth 2 xact))\n \t\t\t\t  (if (nth 3 xact)\n \t\t\t\t      (nth 3 xact)\n \t\t\t\t      "")\n@@ -310,7 +311,9 @@ POSTING is used in `ledger-clear-whole-transactions\' is nil."\n \t\t\t\t\t\t \'where where))))  ))\n \t  (goto-char (point-max))\n \t  (delete-char -1)) ;gets rid of the extra line feed at the bottom of the list\n-\t(insert (concat "There are no uncleared entries for " account)))\n+\t(if ledger-success \n+\t    (insert (concat "There are no uncleared entries for " account))\n+\t    (insert "Ledger has reported a problem.  Check *Ledger Error* buffer.")))\n     (goto-char (point-min))\n     (set-buffer-modified-p nil)\n     (toggle-read-only t)\n@@ -351,10 +354,11 @@ POSTING is used in `ledger-clear-whole-transactions\' is nil."\n       (set-window-buffer (split-window (get-buffer-window buf) nil nil) rbuf)\n       (pop-to-buffer rbuf)))\n \n-(defun ledger-reconcile (account)\n-  "Start reconciling ACCOUNT."\n-  (interactive "sAccount to reconcile: ")\n-  (let ((buf (current-buffer))\n+(defun ledger-reconcile ()\n+  "Start reconciling, prompt for account."\n+  (interactive)\n+  (let ((account (ledger-post-read-account-with-prompt "Account to reconcile"))\n+\t(buf (current-buffer))\n         (rbuf (get-buffer ledger-recon-buffer-name)))  ;; this means\n \t\t\t\t\t\t       ;; only one\n \t\t\t\t\t\t       ;; *Reconcile*\n@@ -396,7 +400,7 @@ POSTING is used in `ledger-clear-whole-transactions\' is nil."\n (defvar ledger-reconcile-mode-abbrev-table)\n \n (defun ledger-reconcile-change-target ()\n-  "Change the traget amount for the reconciliation process."\n+  "Change the target amount for the reconciliation process."\n   (interactive)\n   (setq ledger-target (ledger-read-commodity-string "Set reconciliation target")))\n \n@@ -442,6 +446,5 @@ POSTING is used in `ledger-clear-whole-transactions\' is nil."\n      (use-local-map map)))\n \n (provide \'ldg-reconcile)\n-(provide \'ldg-reconcile)\n \n ;;; ldg-reconcile.el ends here\ndiff --git a/lisp/ldg-regex.el b/lisp/ldg-regex.el\nindex e81394ef..97fd6e2c 100644\n--- a/lisp/ldg-regex.el\n+++ b/lisp/ldg-regex.el\n@@ -24,7 +24,8 @@\n (eval-when-compile\n   (require \'cl))\n \n-(defvar ledger-date-regex "\\\\([0-9]+\\\\)[/-]\\\\([0-9]+\\\\)[/-]\\\\([0-9]+\\\\)")\n+(defvar ledger-date-regex \n+  "\\\\([0-9]+\\\\)[/-]\\\\([0-9]+\\\\)[/-]\\\\([0-9]+\\\\)")\n \n (defmacro ledger-define-regexp (name regex docs &rest args)\n   "Simplify the creation of a Ledger regex and helper functions."\ndiff --git a/lisp/ldg-register.el b/lisp/ldg-register.el\ndeleted file mode 100644\nindex bfd8d360..00000000\n--- a/lisp/ldg-register.el\n+++ /dev/null\n@@ -1,86 +0,0 @@\n-;;; ldg-register.el --- Helper code for use with the "ledger" command-line tool\n-\n-;; Copyright (C) 2003-2013 John Wiegley (johnw AT gnu DOT org)\n-\n-;; This file is not part of GNU Emacs.\n-\n-;; This is free software; you can redistribute it and/or modify it under\n-;; the terms of the GNU General Public License as published by the Free\n-;; Software Foundation; either version 2, or (at your option) any later\n-;; version.\n-;;\n-;; This is distributed in the hope that it will be useful, but WITHOUT\n-;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\n-;; FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\n-;; for more details.\n-;;\n-;; You should have received a copy of the GNU General Public License\n-;; along with GNU Emacs; see the file COPYING.  If not, write to the\n-;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,\n-;; MA 02111-1307, USA.\n-\n-(require \'ldg-post)\n-(require \'ldg-state)\n-\n-(defgroup ledger-register nil\n-  ""\n-  :group \'ledger)\n-\n-(defcustom ledger-register-date-format "%m/%d/%y"\n-  "*The date format used for ledger register reports."\n-  :type \'string\n-  :group \'ledger-register)\n-\n-(defcustom ledger-register-line-format "%s %-30.30s %-25.25s %15s\\n"\n-  "*The date format used for ledger register reports."\n-  :type \'string\n-  :group \'ledger-register)\n-\n-(defface ledger-register-pending-face\n-    \'((((background light)) (:weight bold))\n-      (((background dark)) (:weight bold)))\n-  "Face used to highlight pending entries in a register report."\n-  :group \'ledger-register)\n-\n-(defun ledger-register-render (data-buffer posts)\n-  (dolist (post posts)\n-    (let ((index 1))\n-      (dolist (xact (nthcdr 5 post))\n-        (let ((beg (point))\n-              (where\n-               (with-current-buffer data-buffer\n-                 (cons\n-                  (nth 0 post)\n-                  (if ledger-clear-whole-transactions\n-                      (save-excursion\n-                        (goto-line (nth 1 post))\n-                        (point-marker))\n-\t\t      (save-excursion\n-\t\t\t(goto-line (nth 0 xact))\n-\t\t\t(point-marker)))))))\n-          (insert (format ledger-register-line-format\n-                          (format-time-string ledger-register-date-format\n-                                              (nth 2 post))\n-                          (nth 4 post) (nth 1 xact) (nth 2 xact)))\n-          (if (nth 3 xact)\n-              (set-text-properties beg (1- (point))\n-                                   (list \'face \'ledger-register-pending-face\n-                                         \'where where))\n-\t      (set-text-properties beg (1- (point))\n-\t\t\t\t   (list \'where where))))\n-        (setq index (1+ index)))))\n-  (goto-char (point-min)))\n-\n-(defun ledger-register-generate (&optional data-buffer &rest args)\n-  (let ((buf (or data-buffer (current-buffer))))\n-    (with-current-buffer (get-buffer-create "*ledger-register*")\n-      (let ((pos (point))\n-            (inhibit-read-only t))\n-        (erase-buffer)\n-        (ledger-register-render buf (apply #\'ledger-exec-read buf args))\n-        (goto-char pos))\n-      (set-buffer-modified-p nil)\n-      (toggle-read-only t)\n-      (display-buffer (current-buffer) t))))\n-\n-(provide \'ldg-register)\ndiff --git a/lisp/ldg-report.el b/lisp/ldg-report.el\nindex 0728495e..8d91d9d4 100644\n--- a/lisp/ldg-report.el\n+++ b/lisp/ldg-report.el\n@@ -258,12 +258,7 @@ used to generate the buffer, navigating the buffer, etc."\n    the default."\n   ;; It is intended completion should be available on existing account\n   ;; names, but it remains to be implemented.\n-  (let* ((context (ledger-context-at-point))\n-         (default\n-          (if (eq (ledger-context-line-type context) \'acct-transaction)\n-              (regexp-quote (ledger-context-field-value context \'account))\n-\t      nil)))\n-    (ledger-read-string-with-default "Account" default)))\n+  (ledger-post-read-account-with-prompt "Account"))\n \n (defun ledger-report-expand-format-specifiers (report-cmd)\n   "Expand %(account) and %(payee) appearing in REPORT-CMD with thing under point."\n@@ -437,11 +432,13 @@ Optional EDIT the command."\n       ("^\\\\(\\\\([0-9][0-9][0-9][0-9]/\\\\)?[01]?[0-9]/[0123]?[0-9]\\\\)[ \\t]+\\\\(\\\\([!*]\\\\)[ \\t]\\\\)?[ \\t]*\\\\((\\\\(.*\\\\))\\\\)?[ \\t]*\\\\(.*\\\\)[ \\t]*$"\n        (date nil status nil nil code payee))))\n     (acct-transaction\n-     (("\\\\(^[ \\t]+\\\\)\\\\(.*?\\\\)[ \\t]+\\\\([$]\\\\)\\\\(-?[0-9]*\\\\(\\\\.[0-9]*\\\\)?\\\\)[ \\t]*;[ \\t]*\\\\(.*?\\\\)[ \\t]*$"\n+     (("^\\\\([ \\t]+;\\\\|;\\\\)\\\\s-?\\\\(.*\\\\)"\n+       (indent comment))\n+      ("\\\\(^[ \\t]+\\\\)\\\\([:A-Za-z0-9]+?\\\\)\\\\s-\\\\s-+\\\\([$€£]\\\\s-?\\\\)\\\\(-?[0-9]*\\\\(\\\\.[0-9]*\\\\)?\\\\)$"\n+       (indent account commodity amount))\n+      ("\\\\(^[ \\t]+\\\\)\\\\(.*?\\\\)[ \\t]+\\\\([$€£]\\\\s-?\\\\)\\\\(-?[0-9]*\\\\(\\\\.[0-9]*\\\\)?\\\\)[ \\t]*;[ \\t]*\\\\(.*?\\\\)[ \\t]*$"\n        (indent account commodity amount nil comment))\n-      ("\\\\(^[ \\t]+\\\\)\\\\(.*?\\\\)[ \\t]+\\\\([$]\\\\)\\\\(-?[0-9]*\\\\(\\\\.[0-9]*\\\\)?\\\\)[ \\t]*$"\n-       (indent account commodity amount nil))\n-      ("\\\\(^[ \\t]+\\\\)\\\\(.*?\\\\)[ \\t]+\\\\(-?[0-9]+\\\\(\\\\.[0-9]*\\\\)?\\\\)[ \\t]+\\\\(.*?\\\\)[ \\t]*;[ \\t]*\\\\(.*?\\\\)[ \\t]*$"\n+      ("\\\\(^[ \\t]+\\\\)\\\\(.*?\\\\)[ \\t]+\\\\(-?[0-9]+\\\\(\\\\.[0-9]*\\\\)?\\\\)[ \\t]+\\\\(.*?\\\\)[ \\t]*\\\\(;[ \\t]*\\\\(.*?\\\\)[ \\t]*$\\\\|@+\\\\)"\n        (indent account amount nil commodity comment))\n       ("\\\\(^[ \\t]+\\\\)\\\\(.*?\\\\)[ \\t]+\\\\(-?[0-9]+\\\\(\\\\.[0-9]*\\\\)?\\\\)[ \\t]+\\\\(.*?\\\\)[ \\t]*$"\n        (indent account amount nil commodity))\n@@ -452,7 +449,13 @@ Optional EDIT the command."\n       ("\\\\(^[ \\t]+\\\\)\\\\(.*?\\\\)[ \\t]*;[ \\t]*\\\\(.*?\\\\)[ \\t]*$"\n        (indent account comment))\n       ("\\\\(^[ \\t]+\\\\)\\\\(.*?\\\\)[ \\t]*$"\n-       (indent account))))))\n+       (indent account))\n+\n+;; Bad regexes\n+      ("\\\\(^[ \\t]+\\\\)\\\\(.*?\\\\)[ \\t]+\\\\([$€£]\\\\s-?\\\\)\\\\(-?[0-9]*\\\\(\\\\.[0-9]*\\\\)?\\\\)[ \\t]*$"\n+       (indent account commodity amount nil))\n+\n+      ))))\n \n (defun ledger-extract-context-info (line-type pos)\n   "Get context info for current line with LINE-TYPE.\ndiff --git a/lisp/ldg-sort.el b/lisp/ldg-sort.el\nindex cc036492..33ae2a98 100644\n--- a/lisp/ldg-sort.el\n+++ b/lisp/ldg-sort.el\n@@ -38,6 +38,36 @@\n   "Move point to end of transaction."\n   (forward-paragraph))\n \n+(defun ledger-sort-find-start ()\n+  (if (re-search-forward ";.*Ledger-mode:.*Start sort" nil t)\n+      (match-end 0)))\n+\n+(defun ledger-sort-find-end ()\n+  (if (re-search-forward ";.*Ledger-mode:.*End sort" nil t)\n+      (match-end 0)))\n+\n+(defun ledger-sort-insert-start-mark ()\n+  (interactive)\n+  (let (has-old-marker) \n+    (save-excursion\n+      (goto-char (point-min))\n+      (setq has-old-marker (ledger-sort-find-start))\n+      (if has-old-marker \n+\t  (delete-region (match-beginning 0) (match-end 0))))\n+    (beginning-of-line)\n+    (insert "\\n; Ledger-mode: Start sort\\n\\n")))\n+\n+(defun ledger-sort-insert-end-mark ()\n+  (interactive)\n+  (let (has-old-marker) \n+    (save-excursion\n+      (goto-char (point-min))\n+      (setq has-old-marker (ledger-sort-find-end))\n+      (if has-old-marker \n+\t  (delete-region (match-beginning 0) (match-end 0))))\n+    (beginning-of-line)\n+    (insert "\\n; Ledger-mode: End sort\\n\\n")))\n+\n (defun ledger-sort-region (beg end)\n   "Sort the region from BEG to END in chronological order."\n   (interactive "r") ;; load beg and end from point and mark\n@@ -66,14 +96,15 @@\n (defun ledger-sort-buffer ()\n   "Sort the entire buffer."\n   (interactive)\n-  (let ((sort-start (point-min))\n-\t(sort-end (point-max)))\n-    (goto-char (point-min))\n-    (if (re-search-forward ";.*Ledger-mode:.*Start sort" nil t)\n-\t(set \'sort-start (match-end 0)))\n-    (if (re-search-forward ";.*Ledger-mode:.*End sort" nil t)\n-\t(set \'sort-end (match-end 0)))    \n-    (ledger-sort-region sort-start sort-end)))\n+  (goto-char (point-min))\n+  (let ((sort-start (ledger-sort-find-start))\n+\t(sort-end (ledger-sort-find-end)))\n+    (ledger-sort-region (if sort-start\n+\t\t\t    sort-start\n+\t\t\t    (point-min)) \n+\t\t\t(if sort-end\n+\t\t\t    sort-end\n+\t\t\t    (point-max)))))\n \n (provide \'ldg-sort)\n \ndiff --git a/lisp/ldg-state.el b/lisp/ldg-state.el\nindex b2247afe..dd5e42ad 100644\n--- a/lisp/ldg-state.el\n+++ b/lisp/ldg-state.el\n@@ -122,42 +122,48 @@ dropped."\n \n     ;;this excursion toggles the posting status\n     (save-excursion\n-      (goto-char (line-beginning-position))\n-      (when (looking-at "[ \\t]")\n-        (skip-chars-forward " \\t")\n-        (let ((here (point))\n-              (cur-status (ledger-state-from-char (char-after))))\n-          (skip-chars-forward "*! ")\n-          (let ((width (- (point) here)))\n-            (when (> width 0)\n-              (delete-region here (point))\n-              (save-excursion\n-                (if (search-forward "  " (line-end-position) t)\n-                    (insert (make-string width ? ))))))\n-          (let (inserted)\n-            (if cur-status\n-                (if (and style (eq style \'cleared))\n-                    (progn\n-                      (insert "* ")\n-                      (setq inserted \'cleared)))\n-\t\t(if (and style (eq style \'pending))\n-\t\t    (progn\n-\t\t      (insert "! ")\n-\t\t      (setq inserted \'pending))\n-\t\t    (progn\n-\t\t      (insert "* ")\n-\t\t      (setq inserted \'cleared))))\n-            (if (and inserted\n-                     (re-search-forward "\\\\(\\t\\\\| [ \\t]\\\\)"\n-                                        (line-end-position) t))\n-                (cond\n-\t\t  ((looking-at "\\t")\n-\t\t   (delete-char 1))\n-\t\t  ((looking-at " [ \\t]")\n-\t\t   (delete-char 2))\n-\t\t  ((looking-at " ")\n-\t\t   (delete-char 1))))\n-            (setq new-status inserted)))))\n+      (let ((has-align-hook (remove-hook \n+\t\t\t \'after-change-functions\n+\t\t\t \'ledger-post-maybe-align t)))\n+      \n+       (goto-char (line-beginning-position))\n+       (when (looking-at "[ \\t]")\n+\t (skip-chars-forward " \\t")\n+\t (let ((here (point))\n+\t       (cur-status (ledger-state-from-char (char-after))))\n+\t   (skip-chars-forward "*! ")\n+\t   (let ((width (- (point) here)))\n+\t     (when (> width 0)\n+\t       (delete-region here (point))\n+\t       (save-excursion\n+\t\t (if (search-forward "  " (line-end-position) t)\n+\t\t     (insert (make-string width ? ))))))\n+\t   (let (inserted)\n+\t     (if cur-status\n+\t\t (if (and style (eq style \'cleared))\n+\t\t     (progn\n+\t\t       (insert  "* ")\n+\t\t       (setq inserted \'cleared)))\n+\t\t (if (and style (eq style \'pending))\n+\t\t     (progn\n+\t\t       (insert  "! ")\n+\t\t       (setq inserted \'pending))\n+\t\t     (progn\n+\t\t       (insert  "* ")\n+\t\t       (setq inserted \'cleared))))\n+\t     (if (and inserted\n+\t\t      (re-search-forward "\\\\(\\t\\\\| [ \\t]\\\\)"\n+\t\t\t\t\t (line-end-position) t))\n+\t\t (cond\n+\t\t   ((looking-at "\\t")\n+\t\t    (delete-char 1))\n+\t\t   ((looking-at " [ \\t]")\n+\t\t    (delete-char 2))\n+\t\t   ((looking-at " ")\n+\t\t    (delete-char 1))))\n+\t     (setq new-status inserted))))\n+\t   (if has-align-hook\n+\t   (add-hook \'after-change-functions \'ledger-post-maybe-align t t))))\n \n     ;; This excursion cleans up the entry so that it displays\n     ;; minimally.  This means that if all posts are cleared, remove\n@@ -254,6 +260,4 @@ dropped."\n \n (provide \'ldg-state)\n \n-(provide \'ldg-state)\n-\n ;;; ldg-state.el ends here\ndiff --git a/lisp/ldg-xact.el b/lisp/ldg-xact.el\nindex 8db50df2..ecd87127 100644\n--- a/lisp/ldg-xact.el\n+++ b/lisp/ldg-xact.el\n@@ -118,9 +118,6 @@ within the transaction."\n     (replace-match date)\n     (re-search-forward "[1-9][0-9]+\\.[0-9]+")))\n \n-\n-    \n-(provide \'ldg-xact)\n (provide \'ldg-xact)\n \n ;;; ldg-xact.el ends here\ndiff --git a/lisp/ledger.el b/lisp/ledger.el\ndeleted file mode 100644\nindex 4fc21d6a..00000000\n--- a/lisp/ledger.el\n+++ /dev/null\n@@ -1,1340 +0,0 @@\n-;;; ledger.el --- Helper code for use with the "ledger" command-line tool\n-\n-;; Copyright (C) 2003-2009 John Wiegley (johnw AT gnu DOT org)\n-\n-;; Emacs Lisp Archive Entry\n-;; Filename: ledger.el\n-;; Version: 2.6.3\n-;; Date: Fri 18-Jul-2008\n-;; Keywords: data\n-;; Author: John Wiegley (johnw AT gnu DOT org)\n-;; Maintainer: John Wiegley (johnw AT gnu DOT org)\n-;; Description: Helper code for using my "ledger" command-line tool\n-;; URL: http://www.newartisans.com/johnw/emacs.html\n-;; Compatibility: Emacs22\n-\n-;; This file is not part of GNU Emacs.\n-\n-;; This is free software; you can redistribute it and/or modify it under\n-;; the terms of the GNU General Public License as published by the Free\n-;; Software Foundation; either version 2, or (at your option) any later\n-;; version.\n-;;\n-;; This is distributed in the hope that it will be useful, but WITHOUT\n-;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\n-;; FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\n-;; for more details.\n-;;\n-;; You should have received a copy of the GNU General Public License\n-;; along with GNU Emacs; see the file COPYING.  If not, write to the\n-;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,\n-;; MA 02111-1307, USA.\n-\n-;;; Commentary:\n-\n-;; To use this module: Load this file, open a ledger data file, and\n-;; type M-x ledger-mode.  Once this is done, you can type:\n-;;\n-;;   C-c C-a       add a new entry, based on previous entries\n-;;   C-c C-e       toggle cleared status of an entry\n-;;   C-c C-y       set default year for entry mode\n-;;   C-c C-m       set default month for entry mode\n-;;   C-c C-r       reconcile uncleared entries related to an account\n-;;   C-c C-o C-r   run a ledger report\n-;;   C-C C-o C-g   goto the ledger report buffer\n-;;   C-c C-o C-e   edit the defined ledger reports\n-;;   C-c C-o C-s   save a report definition based on the current report\n-;;   C-c C-o C-a   rerun a ledger report\n-;;   C-c C-o C-k   kill the ledger report buffer\n-;;\n-;; In the reconcile buffer, use SPACE to toggle the cleared status of\n-;; a transaction, C-x C-s to save changes (to the ledger file as\n-;; well).\n-;;\n-;; The ledger reports command asks the user to select a report to run\n-;; then creates a report buffer containing the results of running the\n-;; associated command line.  Its\' behavior is modified by a prefix\n-;; argument which, when given, causes the generated command line that\n-;; will be used to create the report to be presented for editing\n-;; before the report is actually run.  Arbitrary unnamed command lines\n-;; can be run by specifying an empty name for the report.  The command\n-;; line used can later be named and saved for future use as a named\n-;; report from the generated reports buffer.\n-;;\n-;; In a report buffer, the following keys are available:\n-;;   (space)  scroll up\n-;;   e        edit the defined ledger reports\n-;;   s        save a report definition based on the current report\n-;;   q        quit the report (return to ledger buffer)\n-;;   r        redo the report\n-;;   k        kill the report buffer\n-\n-(require \'esh-util)\n-(require \'esh-arg)\n-(require \'pcomplete)\n-\n-(defvar ledger-version "1.3"\n-  "The version of ledger.el currently loaded")\n-\n-(defgroup ledger nil\n-  "Interface to the Ledger command-line accounting program."\n-  :group \'data)\n-\n-(defcustom ledger-binary-path "ledger"\n-  "Path to the ledger executable."\n-  :type \'file\n-  :group \'ledger)\n-\n-(defcustom ledger-clear-whole-entries nil\n-  "If non-nil, clear whole entries, not individual transactions."\n-  :type \'boolean\n-  :group \'ledger)\n-\n-(defcustom ledger-reports\n-  \'(("bal" "ledger -f %(ledger-file) bal")\n-    ("reg" "ledger -f %(ledger-file) reg")\n-    ("payee" "ledger -f %(ledger-file) reg -- %(payee)")\n-    ("account" "ledger -f %(ledger-file) reg %(account)"))\n-  "Definition of reports to run.\n-\n-Each element has the form (NAME CMDLINE).  The command line can\n-contain format specifiers that are replaced with context sensitive\n-information.  Format specifiers have the format \'%(<name>)\' where\n-<name> is an identifier for the information to be replaced.  The\n-`ledger-report-format-specifiers\' alist variable contains a mapping\n-from format specifier identifier to a lisp function that implements\n-the substitution.  See the documentation of the individual functions\n-in that variable for more information on the behavior of each\n-specifier."\n-  :type \'(repeat (list (string :tag "Report Name")\n-                       (string :tag "Command Line")))\n-  :group \'ledger)\n-\n-(defcustom ledger-report-format-specifiers\n-  \'(("ledger-file" . ledger-report-ledger-file-format-specifier)\n-    ("payee" . ledger-report-payee-format-specifier)\n-    ("account" . ledger-report-account-format-specifier))\n-  "Alist mapping ledger report format specifiers to implementing functions\n-\n-The function is called with no parameters and expected to return the\n-text that should replace the format specifier."\n-  :type \'alist\n-  :group \'ledger)\n-\n-(defcustom ledger-default-acct-transaction-indent "    "\n-  "Default indentation for account transactions in an entry."\n-  :type \'string\n-  :group \'ledger)\n-\n-(defvar bold \'bold)\n-(defvar ledger-font-lock-keywords\n-  \'(("\\\\(\t\\\\|  \\\\|^\\\\)\\\\(;.*\\\\)" 2 font-lock-comment-face)\n-    ("^[0-9]+[-/.=][-/.=0-9]+\\\\s-+\\\\(([^)]+)\\\\s-+\\\\)?\\\\([^*].+?\\\\)\\\\(\\\\(        ;\\\\|  ;\\\\|$\\\\)\\\\)" 2 bold)\n-    ;;("^[0-9]+[-/.=][-/.=0-9]+\\\\s-+\\\\(([^)]+)\\\\s-+\\\\)?\\\\([*].+?\\\\)\\\\(\\\\(       ;\\\\|  ;\\\\|$\\\\)\\\\)"\n-    ;; 2 font-lock-type-face)\n-    ("^\\\\s-+\\\\([*]\\\\s-*\\\\)?\\\\(\\\\([[(]\\\\)?\\\\([^*;]\\\\)+?\\\\(:\\\\|\\\\s-\\\\)[^]);\n-\t]+?\\\\([])]\\\\)?\\\\)\\\\(\t\\\\|  \\\\|$\\\\)"\n-     2 font-lock-keyword-face)\n-    ("^\\\\([~=].+\\\\)" 1 font-lock-function-name-face)\n-    ("^\\\\([A-Za-z]+ .+\\\\)" 1 font-lock-function-name-face))\n-  "Expressions to highlight in Ledger mode.")\n-\n-(defsubst ledger-current-year ()\n-  (format-time-string "%Y"))\n-(defsubst ledger-current-month ()\n-  (format-time-string "%m"))\n-\n-(defvar ledger-year (ledger-current-year)\n-  "Start a ledger session with the current year, but make it\n-customizable to ease retro-entry.")\n-(defvar ledger-month (ledger-current-month)\n-  "Start a ledger session with the current month, but make it\n-customizable to ease retro-entry.")\n-\n-(defun ledger-iterate-entries (callback)\n-  (goto-char (point-min))\n-  (let* ((now (current-time))\n-         (current-year (nth 5 (decode-time now))))\n-    (while (not (eobp))\n-      (when (looking-at\n-             (concat "\\\\(Y\\\\s-+\\\\([0-9]+\\\\)\\\\|"\n-                     "\\\\([0-9]\\\\{4\\\\}+\\\\)?[./]?"\n-                     "\\\\([0-9]+\\\\)[./]\\\\([0-9]+\\\\)\\\\s-+"\n-                     "\\\\(\\\\*\\\\s-+\\\\)?\\\\(.+\\\\)\\\\)"))\n-        (let ((found (match-string 2)))\n-          (if found\n-              (setq current-year (string-to-number found))\n-            (let ((start (match-beginning 0))\n-                  (year (match-string 3))\n-                  (month (string-to-number (match-string 4)))\n-                  (day (string-to-number (match-string 5)))\n-                  (mark (match-string 6))\n-                  (desc (match-string 7)))\n-              (if (and year (> (length year) 0))\n-                  (setq year (string-to-number year)))\n-              (funcall callback start\n-                       (encode-time 0 0 0 day month\n-                                    (or year current-year))\n-                       mark desc)))))\n-      (forward-line))))\n-\n-(defun ledger-time-less-p (t1 t2)\n-  "Say whether time value T1 is less than time value T2."\n-  (or (< (car t1) (car t2))\n-      (and (= (car t1) (car t2))\n-           (< (nth 1 t1) (nth 1 t2)))))\n-\n-(defun ledger-time-subtract (t1 t2)\n-  "Subtract two time values.\n-Return the difference in the format of a time value."\n-  (let ((borrow (< (cadr t1) (cadr t2))))\n-    (list (- (car t1) (car t2) (if borrow 1 0))\n-          (- (+ (if borrow 65536 0) (cadr t1)) (cadr t2)))))\n-\n-(defun ledger-find-slot (moment)\n-  (catch \'found\n-    (ledger-iterate-entries\n-     (function\n-      (lambda (start date mark desc)\n-        (if (ledger-time-less-p moment date)\n-            (throw \'found t)))))))\n-\n-(defun ledger-add-entry (entry-text &optional insert-at-point)\n-  (interactive\n-   (list\n-    (read-string "Entry: " (concat ledger-year "/" ledger-month "/"))))\n-  (let* ((args (with-temp-buffer\n-                 (insert entry-text)\n-                 (eshell-parse-arguments (point-min) (point-max))))\n-         (ledger-buf (current-buffer))\n-         exit-code)\n-    (unless insert-at-point\n-      (let ((date (car args)))\n-        (if (string-match "\\\\([0-9]+\\\\)/\\\\([0-9]+\\\\)/\\\\([0-9]+\\\\)" date)\n-            (setq date\n-                  (encode-time 0 0 0 (string-to-number (match-string 3 date))\n-                               (string-to-number (match-string 2 date))\n-                               (string-to-number (match-string 1 date)))))\n-        (ledger-find-slot date)))\n-    (save-excursion\n-      (insert\n-       (with-temp-buffer\n-         (setq exit-code\n-               (apply #\'ledger-run-ledger ledger-buf "entry"\n-                      (mapcar \'eval args)))\n-         (goto-char (point-min))\n-         (if (looking-at "Error: ")\n-             (error (buffer-string))\n-           (buffer-string)))\n-       "\\n"))))\n-\n-(defun ledger-current-entry-bounds ()\n-  (save-excursion\n-    (when (or (looking-at "^[0-9]")\n-              (re-search-backward "^[0-9]" nil t))\n-      (let ((beg (point)))\n-        (while (not (eolp))\n-          (forward-line))\n-        (cons (copy-marker beg) (point-marker))))))\n-\n-(defun ledger-delete-current-entry ()\n-  (interactive)\n-  (let ((bounds (ledger-current-entry-bounds)))\n-    (delete-region (car bounds) (cdr bounds))))\n-\n-(defun ledger-toggle-current-entry (&optional style)\n-  (interactive)\n-  (let (clear)\n-    (save-excursion\n-      (when (or (looking-at "^[0-9]")\n-                (re-search-backward "^[0-9]" nil t))\n-        (skip-chars-forward "0-9./=")\n-        (delete-horizontal-space)\n-        (if (member (char-after) \'(?\\* ?\\!))\n-            (progn\n-              (delete-char 1)\n-              (if (and style (eq style \'cleared))\n-                  (insert " *")))\n-          (if (and style (eq style \'pending))\n-              (insert " ! ")\n-            (insert " * "))\n-          (setq clear t))))\n-    clear))\n-\n-(defun ledger-move-to-next-field ()\n-  (re-search-forward "\\\\(  \\\\|\\t\\\\)" (line-end-position) t))\n-\n-(defun ledger-toggle-state (state &optional style)\n-  (if (not (null state))\n-      (if (and style (eq style \'cleared))\n-          \'cleared)\n-    (if (and style (eq style \'pending))\n-        \'pending\n-      \'cleared)))\n-\n-(defun ledger-entry-state ()\n-  (save-excursion\n-    (when (or (looking-at "^[0-9]")\n-              (re-search-backward "^[0-9]" nil t))\n-      (skip-chars-forward "0-9./=")\n-      (skip-syntax-forward " ")\n-      (cond ((looking-at "!\\\\s-*") \'pending)\n-            ((looking-at "\\\\*\\\\s-*") \'cleared)\n-            (t nil)))))\n-\n-(defun ledger-transaction-state ()\n-  (save-excursion\n-    (goto-char (line-beginning-position))\n-    (skip-syntax-forward " ")\n-    (cond ((looking-at "!\\\\s-*") \'pending)\n-          ((looking-at "\\\\*\\\\s-*") \'cleared)\n-          (t (ledger-entry-state)))))\n-\n-(defun ledger-toggle-current-transaction (&optional style)\n-  "Toggle the cleared status of the transaction under point.\n-Optional argument STYLE may be `pending\' or `cleared\', depending\n-on which type of status the caller wishes to indicate (default is\n-`cleared\').\n-This function is rather complicated because it must preserve both\n-the overall formatting of the ledger entry, as well as ensuring\n-that the most minimal display format is used.  This could be\n-achieved more certainly by passing the entry to ledger for\n-formatting, but doing so causes inline math expressions to be\n-dropped."\n-  (interactive)\n-  (let ((bounds (ledger-current-entry-bounds))\n-        clear cleared)\n-    ;; Uncompact the entry, to make it easier to toggle the\n-    ;; transaction\n-    (save-excursion\n-      (goto-char (car bounds))\n-      (skip-chars-forward "0-9./= \\t")\n-      (setq cleared (and (member (char-after) \'(?\\* ?\\!))\n-                         (char-after)))\n-      (when cleared\n-        (let ((here (point)))\n-          (skip-chars-forward "*! ")\n-          (let ((width (- (point) here)))\n-            (when (> width 0)\n-              (delete-region here (point))\n-              (if (search-forward "  " (line-end-position) t)\n-                  (insert (make-string width ? ))))))\n-        (forward-line)\n-        (while (looking-at "[ \\t]")\n-          (skip-chars-forward " \\t")\n-          (insert cleared " ")\n-          (if (search-forward "  " (line-end-position) t)\n-              (delete-char 2))\n-          (forward-line))))\n-    ;; Toggle the individual transaction\n-    (save-excursion\n-      (goto-char (line-beginning-position))\n-      (when (looking-at "[ \\t]")\n-        (skip-chars-forward " \\t")\n-        (let ((here (point))\n-              (cleared (member (char-after) \'(?\\* ?\\!))))\n-          (skip-chars-forward "*! ")\n-          (let ((width (- (point) here)))\n-            (when (> width 0)\n-              (delete-region here (point))\n-              (save-excursion\n-                (if (search-forward "  " (line-end-position) t)\n-                    (insert (make-string width ? ))))))\n-          (let (inserted)\n-            (if cleared\n-                (if (and style (eq style \'cleared))\n-                    (progn\n-                      (insert "* ")\n-                      (setq inserted t)))\n-              (if (and style (eq style \'pending))\n-                  (progn\n-                    (insert "! ")\n-                    (setq inserted t))\n-                (progn\n-                  (insert "* ")\n-                  (setq inserted t))))\n-            (if (and inserted\n-                     (re-search-forward "\\\\(\\t\\\\| [ \\t]\\\\)"\n-                                        (line-end-position) t))\n-                (cond\n-                 ((looking-at "\\t")\n-                  (delete-char 1))\n-                 ((looking-at " [ \\t]")\n-                  (delete-char 2))\n-                 ((looking-at " ")\n-                  (delete-char 1))))\n-            (setq clear inserted)))))\n-    ;; Clean up the entry so that it displays minimally\n-    (save-excursion\n-      (goto-char (car bounds))\n-      (forward-line)\n-      (let ((first t)\n-            (state ? )\n-            (hetero nil))\n-        (while (and (not hetero) (looking-at "[ \\t]"))\n-          (skip-chars-forward " \\t")\n-          (let ((cleared (if (member (char-after) \'(?\\* ?\\!))\n-                             (char-after)\n-                           ? )))\n-            (if first\n-                (setq state cleared\n-                      first nil)\n-              (if (/= state cleared)\n-                  (setq hetero t))))\n-          (forward-line))\n-        (when (and (not hetero) (/= state ? ))\n-          (goto-char (car bounds))\n-          (forward-line)\n-          (while (looking-at "[ \\t]")\n-            (skip-chars-forward " \\t")\n-            (let ((here (point)))\n-              (skip-chars-forward "*! ")\n-              (let ((width (- (point) here)))\n-                (when (> width 0)\n-                  (delete-region here (point))\n-                  (if (re-search-forward "\\\\(\\t\\\\| [ \\t]\\\\)"\n-                                         (line-end-position) t)\n-                      (insert (make-string width ? ))))))\n-            (forward-line))\n-          (goto-char (car bounds))\n-          (skip-chars-forward "0-9./= \\t")\n-          (insert state " ")\n-          (if (re-search-forward "\\\\(\\t\\\\| [ \\t]\\\\)"\n-                                 (line-end-position) t)\n-              (cond\n-               ((looking-at "\\t")\n-                (delete-char 1))\n-               ((looking-at " [ \\t]")\n-                (delete-char 2))\n-               ((looking-at " ")\n-                (delete-char 1)))))))\n-    clear))\n-\n-(defun ledger-toggle-current (&optional style)\n-  (interactive)\n-  (if (or ledger-clear-whole-entries\n-          (eq \'entry (ledger-thing-at-point)))\n-      (progn\n-        (save-excursion\n-          (forward-line)\n-          (goto-char (line-beginning-position))\n-          (while (and (not (eolp))\n-                      (save-excursion\n-                        (not (eq \'entry (ledger-thing-at-point)))))\n-            (if (looking-at "\\\\s-+[*!]")\n-                (ledger-toggle-current-transaction nil))\n-            (forward-line)\n-            (goto-char (line-beginning-position))))\n-        (ledger-toggle-current-entry style))\n-    (ledger-toggle-current-transaction style)))\n-\n-(defvar ledger-mode-abbrev-table)\n-\n-;;;###autoload\n-(define-derived-mode ledger-mode text-mode "Ledger"\n-  "A mode for editing ledger data files.\n-\n-\\\\{ledger-mode-map}"\n-  (set (make-local-variable \'comment-start) " ; ")\n-  (set (make-local-variable \'comment-end) "")\n-  (set (make-local-variable \'indent-tabs-mode) nil)\n-\n-  (if (boundp \'font-lock-defaults)\n-      (set (make-local-variable \'font-lock-defaults)\n-           \'(ledger-font-lock-keywords nil t)))\n-\n-  (set (make-local-variable \'pcomplete-parse-arguments-function)\n-       \'ledger-parse-arguments)\n-  (set (make-local-variable \'pcomplete-command-completion-function)\n-       \'ledger-complete-at-point)\n-  (set (make-local-variable \'pcomplete-termination-string) "")\n-\n-  (let ((map (current-local-map)))\n-    (define-key map [(control ?c) (control ?a)] \'ledger-add-entry)\n-    (define-key map [(control ?c) (control ?d)] \'ledger-delete-current-entry)\n-    (define-key map [(control ?c) (control ?y)] \'ledger-set-year)\n-    (define-key map [(control ?c) (control ?m)] \'ledger-set-month)\n-    (define-key map [(control ?c) (control ?c)] \'ledger-toggle-current)\n-    (define-key map [(control ?c) (control ?e)] \'ledger-toggle-current-entry)\n-    (define-key map [(control ?c) (control ?r)] \'ledger-reconcile)\n-    (define-key map [(control ?c) (control ?s)] \'ledger-sort)\n-    (define-key map [tab] \'pcomplete)\n-    (define-key map [(control ?i)] \'pcomplete)\n-    (define-key map [(control ?c) tab] \'ledger-fully-complete-entry)\n-    (define-key map [(control ?c) (control ?i)] \'ledger-fully-complete-entry)\n-    (define-key map [(control ?c) (control ?o) (control ?r)] \'ledger-report)\n-    (define-key map [(control ?c) (control ?o) (control ?g)] \'ledger-report-goto)\n-    (define-key map [(control ?c) (control ?o) (control ?a)] \'ledger-report-redo)\n-    (define-key map [(control ?c) (control ?o) (control ?s)] \'ledger-report-save)\n-    (define-key map [(control ?c) (control ?o) (control ?e)] \'ledger-report-edit)\n-    (define-key map [(control ?c) (control ?o) (control ?k)] \'ledger-report-kill)))\n-\n-;; Reconcile mode\n-\n-(defvar ledger-buf nil)\n-(defvar ledger-acct nil)\n-\n-(defun ledger-display-balance ()\n-  (let ((buffer ledger-buf)\n-        (account ledger-acct))\n-    (with-temp-buffer\n-      (let ((exit-code (ledger-run-ledger buffer "-C" "balance" account)))\n-        (if (/= 0 exit-code)\n-            (message "Error determining cleared balance")\n-          (goto-char (1- (point-max)))\n-          (goto-char (line-beginning-position))\n-          (delete-horizontal-space)\n-          (message "Cleared balance = %s"\n-                   (buffer-substring-no-properties (point)\n-                                                   (line-end-position))))))))\n-\n-(defun ledger-reconcile-toggle ()\n-  (interactive)\n-  (let ((where (get-text-property (point) \'where))\n-        (account ledger-acct)\n-        (inhibit-read-only t)\n-        cleared)\n-    (when (or (equal (car where) "<stdin>") (equal (car where) "/dev/stdin"))\n-      (with-current-buffer ledger-buf\n-          (goto-char (cdr where))\n-        (setq cleared (ledger-toggle-current \'pending)))\n-      (if cleared\n-          (add-text-properties (line-beginning-position)\n-                               (line-end-position)\n-                               (list \'face \'bold))\n-        (remove-text-properties (line-beginning-position)\n-                                (line-end-position)\n-                                (list \'face))))\n-    (forward-line)))\n-\n-(defun ledger-reconcile-refresh ()\n-  (interactive)\n-  (let ((inhibit-read-only t)\n-        (line (count-lines (point-min) (point))))\n-    (erase-buffer)\n-    (ledger-do-reconcile)\n-    (set-buffer-modified-p t)\n-    (goto-char (point-min))\n-    (forward-line line)))\n-\n-(defun ledger-reconcile-refresh-after-save ()\n-  (let ((buf (get-buffer "*Reconcile*")))\n-    (if buf\n-        (with-current-buffer buf\n-          (ledger-reconcile-refresh)\n-          (set-buffer-modified-p nil)))))\n-\n-(defun ledger-reconcile-add ()\n-  (interactive)\n-  (with-current-buffer ledger-buf\n-    (call-interactively #\'ledger-add-entry))\n-  (ledger-reconcile-refresh))\n-\n-(defun ledger-reconcile-delete ()\n-  (interactive)\n-  (let ((where (get-text-property (point) \'where)))\n-    (when (or (equal (car where) "<stdin>") (equal (car where) "/dev/stdin"))\n-      (with-current-buffer ledger-buf\n-        (goto-char (cdr where))\n-        (ledger-delete-current-entry))\n-      (let ((inhibit-read-only t))\n-        (goto-char (line-beginning-position))\n-        (delete-region (point) (1+ (line-end-position)))\n-        (set-buffer-modified-p t)))))\n-\n-(defun ledger-reconcile-visit ()\n-  (interactive)\n-  (let ((where (get-text-property (point) \'where)))\n-    (when (markerp (cdr where))\n-      (switch-to-buffer-other-window ledger-buf)\n-      (goto-char (cdr where)))))\n-\n-(defun ledger-reconcile-save ()\n-  (interactive)\n-  (with-current-buffer ledger-buf\n-    (save-buffer))\n-  (set-buffer-modified-p nil)\n-  (ledger-display-balance))\n-\n-(defun ledger-reconcile-quit ()\n-  (interactive)\n-  (kill-buffer (current-buffer)))\n-\n-(defun ledger-reconcile-finish ()\n-  (interactive)\n-  (save-excursion\n-    (goto-char (point-min))\n-    (while (not (eobp))\n-      (let ((where (get-text-property (point) \'where))\n-            (face  (get-text-property (point) \'face)))\n-        (if (and (eq face \'bold)\n-                 (or (equal (car where) "<stdin>")\n-                     (equal (car where) "/dev/stdin")))\n-            (with-current-buffer ledger-buf\n-              (goto-char (cdr where))\n-              (ledger-toggle-current \'cleared))))\n-      (forward-line 1)))\n-  (ledger-reconcile-save))\n-\n-(defun ledger-do-reconcile ()\n-  (let* ((buf ledger-buf)\n-         (account ledger-acct)\n-         (items\n-          (with-temp-buffer\n-            (let ((exit-code\n-                   (ledger-run-ledger buf "--uncleared" "--real"\n-                                      "emacs" account)))\n-              (when (= 0 exit-code)\n-                (goto-char (point-min))\n-                (unless (eobp)\n-                  (unless (looking-at "(")\n-                    (error (buffer-string)))\n-                  (read (current-buffer))))))))\n-    (dolist (item items)\n-      (let ((index 1))\n-        (dolist (xact (nthcdr 5 item))\n-          (let ((beg (point))\n-                (where\n-                 (with-current-buffer buf\n-                   (cons\n-                    (nth 0 item)\n-                    (if ledger-clear-whole-entries\n-                        (save-excursion\n-                          (goto-line (nth 1 item))\n-                          (point-marker))\n-                      (save-excursion\n-                        (goto-line (nth 0 xact))\n-                        (point-marker)))))))\n-            (insert (format "%s %-30s %-25s %15s\\n"\n-                            (format-time-string "%m/%d" (nth 2 item))\n-                            (nth 4 item) (nth 1 xact) (nth 2 xact)))\n-            (if (nth 3 xact)\n-                (set-text-properties beg (1- (point))\n-                                     (list \'face \'bold\n-                                           \'where where))\n-              (set-text-properties beg (1- (point))\n-                                   (list \'where where))))\n-          (setq index (1+ index)))))\n-    (goto-char (point-min))\n-    (set-buffer-modified-p nil)\n-    (toggle-read-only t)))\n-\n-(defun ledger-reconcile (account)\n-  (interactive "sAccount to reconcile: ")\n-  (let ((buf (current-buffer))\n-        (rbuf (get-buffer "*Reconcile*")))\n-    (if rbuf\n-        (kill-buffer rbuf))\n-    (add-hook \'after-save-hook \'ledger-reconcile-refresh-after-save)\n-    (with-current-buffer\n-        (pop-to-buffer (get-buffer-create "*Reconcile*"))\n-      (ledger-reconcile-mode)\n-      (set (make-local-variable \'ledger-buf) buf)\n-      (set (make-local-variable \'ledger-acct) account)\n-      (ledger-do-reconcile))))\n-\n-(defvar ledger-reconcile-mode-abbrev-table)\n-\n-(defvar ledger-reconcile-mode-map\n-  (let ((map (make-sparse-keymap)))\n-    (define-key map [(control ?m)] \'ledger-reconcile-visit)\n-    (define-key map [return] \'ledger-reconcile-visit)\n-    (define-key map [(control ?c) (control ?c)] \'ledger-reconcile-finish)\n-    (define-key map [(control ?x) (control ?s)] \'ledger-reconcile-save)\n-    (define-key map [(control ?l)] \'ledger-reconcile-refresh)\n-    (define-key map [? ] \'ledger-reconcile-toggle)\n-    (define-key map [?a] \'ledger-reconcile-add)\n-    (define-key map [?d] \'ledger-reconcile-delete)\n-    (define-key map [?n] \'next-line)\n-    (define-key map [?p] \'previous-line)\n-    (define-key map [?s] \'ledger-reconcile-save)\n-    (define-key map [?q] \'ledger-reconcile-quit)\n-    map))\n-\n-(define-derived-mode ledger-reconcile-mode text-mode "Reconcile"\n-  "A mode for reconciling ledger entries.\n-\n-\\\\{ledger-reconcile-mode-map}")\n-\n-;; Context sensitivity\n-\n-(defconst ledger-line-config\n-  \'((entry\n-     (("^\\\\(\\\\([0-9][0-9][0-9][0-9]/\\\\)?[01]?[0-9]/[0123]?[0-9]\\\\)[ \\t]+\\\\(\\\\([!*]\\\\)[ \\t]\\\\)?[ \\t]*\\\\((\\\\(.*\\\\))\\\\)?[ \\t]*\\\\(.*?\\\\)[ \\t]*;\\\\(.*\\\\)[ \\t]*$"\n-       (date nil status nil nil code payee comment))\n-      ("^\\\\(\\\\([0-9][0-9][0-9][0-9]/\\\\)?[01]?[0-9]/[0123]?[0-9]\\\\)[ \\t]+\\\\(\\\\([!*]\\\\)[ \\t]\\\\)?[ \\t]*\\\\((\\\\(.*\\\\))\\\\)?[ \\t]*\\\\(.*\\\\)[ \\t]*$"\n-       (date nil status nil nil code payee))))\n-    (acct-transaction\n-     (("\\\\(^[ \\t]+\\\\)\\\\(.*?\\\\)[ \\t]+\\\\([$]\\\\)\\\\(-?[0-9]*\\\\(\\\\.[0-9]*\\\\)?\\\\)[ \\t]*;[ \\t]*\\\\(.*?\\\\)[ \\t]*$"\n-       (indent account commodity amount nil comment))\n-      ("\\\\(^[ \\t]+\\\\)\\\\(.*?\\\\)[ \\t]+\\\\([$]\\\\)\\\\(-?[0-9]*\\\\(\\\\.[0-9]*\\\\)?\\\\)[ \\t]*$"\n-       (indent account commodity amount nil))\n-      ("\\\\(^[ \\t]+\\\\)\\\\(.*?\\\\)[ \\t]+\\\\(-?[0-9]+\\\\(\\\\.[0-9]*\\\\)?\\\\)[ \\t]+\\\\(.*?\\\\)[ \\t]*;[ \\t]*\\\\(.*?\\\\)[ \\t]*$"\n-       (indent account amount nil commodity comment))\n-      ("\\\\(^[ \\t]+\\\\)\\\\(.*?\\\\)[ \\t]+\\\\(-?[0-9]+\\\\(\\\\.[0-9]*\\\\)?\\\\)[ \\t]+\\\\(.*?\\\\)[ \\t]*$"\n-       (indent account amount nil commodity))\n-      ("\\\\(^[ \\t]+\\\\)\\\\(.*?\\\\)[ \\t]+\\\\(-?\\\\(\\\\.[0-9]*\\\\)\\\\)[ \\t]+\\\\(.*?\\\\)[ \\t]*;[ \\t]*\\\\(.*?\\\\)[ \\t]*$"\n-       (indent account amount nil commodity comment))\n-      ("\\\\(^[ \\t]+\\\\)\\\\(.*?\\\\)[ \\t]+\\\\(-?\\\\(\\\\.[0-9]*\\\\)\\\\)[ \\t]+\\\\(.*?\\\\)[ \\t]*$"\n-       (indent account amount nil commodity))\n-      ("\\\\(^[ \\t]+\\\\)\\\\(.*?\\\\)[ \\t]*;[ \\t]*\\\\(.*?\\\\)[ \\t]*$"\n-       (indent account comment))\n-      ("\\\\(^[ \\t]+\\\\)\\\\(.*?\\\\)[ \\t]*$"\n-       (indent account))))))\n-\n-(defun ledger-extract-context-info (line-type pos)\n-  "Get context info for current line.\n-\n-Assumes point is at beginning of line, and the pos argument specifies\n-where the \\"users\\" point was."\n-  (let ((linfo (assoc line-type ledger-line-config))\n-        found field fields)\n-    (dolist (re-info (nth 1 linfo))\n-      (let ((re (nth 0 re-info))\n-            (names (nth 1 re-info)))\n-        (unless found\n-          (when (looking-at re)\n-            (setq found t)\n-            (dotimes (i (length names))\n-              (when (nth i names)\n-                (setq fields (append fields\n-                                     (list\n-                                      (list (nth i names)\n-                                            (match-string-no-properties (1+ i))\n-                                            (match-beginning (1+ i))))))))\n-            (dolist (f fields)\n-              (and (nth 1 f)\n-                   (>= pos (nth 2 f))\n-                   (setq field (nth 0 f))))))))\n-    (list line-type field fields)))\n-\n-(defun ledger-context-at-point ()\n-  "Return a list describing the context around point.\n-\n-The contents of the list are the line type, the name of the field\n-point containing point, and for selected line types, the content of\n-the fields in the line in a association list."\n-  (let ((pos (point)))\n-    (save-excursion\n-      (beginning-of-line)\n-      (let ((first-char (char-after)))\n-        (cond ((equal (point) (line-end-position))\n-               \'(empty-line nil nil))\n-              ((memq first-char \'(?\\ ?\\t))\n-               (ledger-extract-context-info \'acct-transaction pos))\n-              ((memq first-char \'(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9))\n-               (ledger-extract-context-info \'entry pos))\n-              ((equal first-char ?\\=)\n-               \'(automated-entry nil nil))\n-              ((equal first-char ?\\~)\n-               \'(period-entry nil nil))\n-              ((equal first-char ?\\!)\n-               \'(command-directive))\n-              ((equal first-char ?\\;)\n-               \'(comment nil nil))\n-              ((equal first-char ?Y)\n-               \'(default-year nil nil))\n-              ((equal first-char ?P)\n-               \'(commodity-price nil nil))\n-              ((equal first-char ?N)\n-               \'(price-ignored-commodity nil nil))\n-              ((equal first-char ?D)\n-               \'(default-commodity nil nil))\n-              ((equal first-char ?C)\n-               \'(commodity-conversion nil nil))\n-              ((equal first-char ?i)\n-               \'(timeclock-i nil nil))\n-              ((equal first-char ?o)\n-               \'(timeclock-o nil nil))\n-              ((equal first-char ?b)\n-               \'(timeclock-b nil nil))\n-              ((equal first-char ?h)\n-               \'(timeclock-h  nil nil))\n-              (t\n-               \'(unknown nil nil)))))))\n-\n-(defun ledger-context-other-line (offset)\n-  "Return a list describing context of line offset for existing position.\n-\n-Offset can be positive or negative.  If run out of buffer before reaching\n-specified line, returns nil."\n-  (save-excursion\n-    (let ((left (forward-line offset)))\n-      (if (not (equal left 0))\n-          nil\n-        (ledger-context-at-point)))))\n-\n-(defun ledger-context-line-type (context-info)\n-  (nth 0 context-info))\n-\n-(defun ledger-context-current-field (context-info)\n-  (nth 1 context-info))\n-\n-(defun ledger-context-field-info (context-info field-name)\n-  (assoc field-name (nth 2 context-info)))\n-\n-(defun ledger-context-field-present-p (context-info field-name)\n-  (not (null (ledger-context-field-info context-info field-name))))\n-\n-(defun ledger-context-field-value (context-info field-name)\n-  (nth 1 (ledger-context-field-info context-info field-name)))\n-\n-(defun ledger-context-field-position (context-info field-name)\n-  (nth 2 (ledger-context-field-info context-info field-name)))\n-\n-(defun ledger-context-field-end-position (context-info field-name)\n-  (+ (ledger-context-field-position context-info field-name)\n-     (length (ledger-context-field-value context-info field-name))))\n-\n-(defun ledger-context-goto-field-start (context-info field-name)\n-  (goto-char (ledger-context-field-position context-info field-name)))\n-\n-(defun ledger-context-goto-field-end (context-info field-name)\n-  (goto-char (ledger-context-field-end-position context-info field-name)))\n-\n-(defun ledger-entry-payee ()\n-  "Returns the payee of the entry containing point or nil."\n-  (let ((i 0))\n-    (while (eq (ledger-context-line-type (ledger-context-other-line i)) \'acct-transaction)\n-      (setq i (- i 1)))\n-    (let ((context-info (ledger-context-other-line i)))\n-      (if (eq (ledger-context-line-type context-info) \'entry)\n-          (ledger-context-field-value context-info \'payee)\n-        nil))))\n-\n-;; Ledger report mode\n-\n-(defvar ledger-report-buffer-name "*Ledger Report*")\n-\n-(defvar ledger-report-name nil)\n-(defvar ledger-report-cmd nil)\n-(defvar ledger-report-name-prompt-history nil)\n-(defvar ledger-report-cmd-prompt-history nil)\n-(defvar ledger-original-window-cfg nil)\n-\n-(defvar ledger-report-mode-abbrev-table)\n-\n-(defvar ledger-report-mode-map\n-  (let ((map (make-sparse-keymap)))\n-    (define-key map [? ] \'scroll-up)\n-    (define-key map [backspace] \'scroll-down)\n-    (define-key map [?r] \'ledger-report-redo)\n-    (define-key map [?s] \'ledger-report-save)\n-    (define-key map [?k] \'ledger-report-kill)\n-    (define-key map [?e] \'ledger-report-edit)\n-    (define-key map [?q] \'ledger-report-quit)\n-    (define-key map [(control ?c) (control ?l) (control ?r)]\n-      \'ledger-report-redo)\n-    (define-key map [(control ?c) (control ?l) (control ?S)]\n-      \'ledger-report-save)\n-    (define-key map [(control ?c) (control ?l) (control ?k)]\n-      \'ledger-report-kill)\n-    (define-key map [(control ?c) (control ?l) (control ?e)]\n-      \'ledger-report-edit)\n-    map))\n-\n-(define-derived-mode ledger-report-mode text-mode "Ledger-Report"\n-  "A mode for viewing ledger reports.")\n-\n-(defun ledger-report-read-name ()\n-  "Read the name of a ledger report to use, with completion.\n-\n-The empty string and unknown names are allowed."\n-  (completing-read "Report name: "\n-                   ledger-reports nil nil nil\n-                   \'ledger-report-name-prompt-history nil))\n-\n-(defun ledger-report (report-name edit)\n-  "Run a user-specified report from `ledger-reports\'.\n-\n-Prompts the user for the name of the report to run.  If no name is\n-entered, the user will be prompted for a command line to run.  The\n-command line specified or associated with the selected report name\n-is run and the output is made available in another buffer for viewing.\n-If a prefix argument is given and the user selects a valid report\n-name, the user is prompted with the corresponding command line for\n-editing before the command is run.\n-\n-The output buffer will be in `ledger-report-mode\', which defines\n-commands for saving a new named report based on the command line\n-used to generate the buffer, navigating the buffer, etc."\n-  (interactive\n-   (progn\n-     (when (and (buffer-modified-p)\n-                (y-or-n-p "Buffer modified, save it? "))\n-       (save-buffer))\n-     (let ((rname (ledger-report-read-name))\n-           (edit (not (null current-prefix-arg))))\n-       (list rname edit))))\n-  (let ((buf (current-buffer))\n-        (rbuf (get-buffer ledger-report-buffer-name))\n-        (wcfg (current-window-configuration)))\n-    (if rbuf\n-        (kill-buffer rbuf))\n-    (with-current-buffer\n-        (pop-to-buffer (get-buffer-create ledger-report-buffer-name))\n-      (ledger-report-mode)\n-      (set (make-local-variable \'ledger-buf) buf)\n-      (set (make-local-variable \'ledger-report-name) report-name)\n-      (set (make-local-variable \'ledger-original-window-cfg) wcfg)\n-      (ledger-do-report (ledger-report-cmd report-name edit))\n-      (shrink-window-if-larger-than-buffer)\n-      (set-buffer-modified-p nil)\n-      (setq buffer-read-only t)\n-      (message "q to quit; r to redo; e to edit; k to kill; s to save; SPC and DEL to scroll"))))\n-\n-(defun string-empty-p (s)\n-  "Check for the empty string."\n-  (string-equal "" s))\n-\n-(defun ledger-report-name-exists (name)\n-  "Check to see if the given report name exists.\n-\n-If name exists, returns the object naming the report, otherwise returns nil."\n-  (unless (string-empty-p name)\n-    (car (assoc name ledger-reports))))\n-\n-(defun ledger-reports-add (name cmd)\n-  "Add a new report to `ledger-reports\'."\n-  (setq ledger-reports (cons (list name cmd) ledger-reports)))\n-\n-(defun ledger-reports-custom-save ()\n-  "Save the `ledger-reports\' variable using the customize framework."\n-  (customize-save-variable \'ledger-reports ledger-reports))\n-\n-(defun ledger-report-read-command (report-cmd)\n-  "Read the command line to create a report."\n-  (read-from-minibuffer "Report command line: "\n-                        (if (null report-cmd) "ledger " report-cmd)\n-                        nil nil \'ledger-report-cmd-prompt-history))\n-\n-(defun ledger-report-ledger-file-format-specifier ()\n-  "Substitute the full path to master or current ledger file\n-\n-The master file name is determined by the ledger-master-file buffer-local\n-variable which can be set using file variables.  If it is set, it is used,\n-otherwise the current buffer file is used."\n-  (ledger-master-file))\n-\n-(defun ledger-read-string-with-default (prompt default)\n-  (let ((default-prompt (concat prompt\n-                                (if default\n-                                    (concat " (" default "): ")\n-                                  ": "))))\n-    (read-string default-prompt nil nil default)))\n-\n-(defun ledger-report-payee-format-specifier ()\n-  "Substitute a payee name\n-\n-The user is prompted to enter a payee and that is substitued.  If\n-point is in an entry, the payee for that entry is used as the\n-default."\n-  ;; It is intended copmletion should be available on existing\n-  ;; payees, but the list of possible completions needs to be\n-  ;; developed to allow this.\n-  (ledger-read-string-with-default "Payee" (regexp-quote (ledger-entry-payee))))\n-\n-(defun ledger-report-account-format-specifier ()\n-  "Substitute an account name\n-\n-The user is prompted to enter an account name, which can be any\n-regular expression identifying an account.  If point is on an account\n-transaction line for an entry, the full account name on that line is\n-the default."\n-  ;; It is intended completion should be available on existing account\n-  ;; names, but it remains to be implemented.\n-  (let* ((context (ledger-context-at-point))\n-         (default\n-          (if (eq (ledger-context-line-type context) \'acct-transaction)\n-              (regexp-quote (ledger-context-field-value context \'account))\n-            nil)))\n-    (ledger-read-string-with-default "Account" default)))\n-\n-(defun ledger-report-expand-format-specifiers (report-cmd)\n-  (let ((expanded-cmd report-cmd))\n-    (while (string-match "%(\\\\([^)]*\\\\))" expanded-cmd)\n-      (let* ((specifier (match-string 1 expanded-cmd))\n-             (f (cdr (assoc specifier ledger-report-format-specifiers))))\n-        (if f\n-            (setq expanded-cmd (replace-match\n-                                (save-match-data\n-                                  (with-current-buffer ledger-buf\n-                                    (shell-quote-argument (funcall f))))\n-                                t t expanded-cmd))\n-          (progn\n-            (set-window-configuration ledger-original-window-cfg)\n-            (error "Invalid ledger report format specifier \'%s\'" specifier)))))\n-    expanded-cmd))\n-\n-(defun ledger-report-cmd (report-name edit)\n-  "Get the command line to run the report."\n-  (let ((report-cmd (car (cdr (assoc report-name ledger-reports)))))\n-    ;; logic for substitution goes here\n-    (when (or (null report-cmd) edit)\n-      (setq report-cmd (ledger-report-read-command report-cmd)))\n-    (setq report-cmd (ledger-report-expand-format-specifiers report-cmd))\n-    (set (make-local-variable \'ledger-report-cmd) report-cmd)\n-    (or (string-empty-p report-name)\n-        (ledger-report-name-exists report-name)\n-        (ledger-reports-add report-name report-cmd)\n-        (ledger-reports-custom-save))\n-    report-cmd))\n-\n-(defun ledger-do-report (cmd)\n-  "Run a report command line."\n-  (goto-char (point-min))\n-  (insert (format "Report: %s\\n" ledger-report-name)\n-          (format "Command: %s\\n" cmd)\n-          (make-string (- (window-width) 1) ?=)\n-          "\\n")\n-  (shell-command cmd t nil))\n-\n-(defun ledger-report-goto ()\n-  "Goto the ledger report buffer."\n-  (interactive)\n-  (let ((rbuf (get-buffer ledger-report-buffer-name)))\n-    (if (not rbuf)\n-        (error "There is no ledger report buffer"))\n-    (pop-to-buffer rbuf)\n-    (shrink-window-if-larger-than-buffer)))\n-\n-(defun ledger-report-redo ()\n-  "Redo the report in the current ledger report buffer."\n-  (interactive)\n-  (ledger-report-goto)\n-  (setq buffer-read-only nil)\n-  (erase-buffer)\n-  (ledger-do-report ledger-report-cmd)\n-  (setq buffer-read-only nil))\n-\n-(defun ledger-report-quit ()\n-  "Quit the ledger report buffer by burying it."\n-  (interactive)\n-  (ledger-report-goto)\n-  (set-window-configuration ledger-original-window-cfg)\n-  (bury-buffer (get-buffer ledger-report-buffer-name)))\n-\n-(defun ledger-report-kill ()\n-  "Kill the ledger report buffer."\n-  (interactive)\n-  (ledger-report-quit)\n-  (kill-buffer (get-buffer ledger-report-buffer-name)))\n-\n-(defun ledger-report-edit ()\n-  "Edit the defined ledger reports."\n-  (interactive)\n-  (customize-variable \'ledger-reports))\n-\n-(defun ledger-report-read-new-name ()\n-  "Read the name for a new report from the minibuffer."\n-  (let ((name ""))\n-    (while (string-empty-p name)\n-      (setq name (read-from-minibuffer "Report name: " nil nil nil\n-                                       \'ledger-report-name-prompt-history)))\n-    name))\n-\n-(defun ledger-report-save ()\n-  "Save the current report command line as a named report."\n-  (interactive)\n-  (ledger-report-goto)\n-  (let (existing-name)\n-    (when (string-empty-p ledger-report-name)\n-      (setq ledger-report-name (ledger-report-read-new-name)))\n-\n-    (while (setq existing-name (ledger-report-name-exists ledger-report-name))\n-      (cond ((y-or-n-p (format "Overwrite existing report named \'%s\' "\n-                               ledger-report-name))\n-             (when (string-equal\n-                    ledger-report-cmd\n-                    (car (cdr (assq existing-name ledger-reports))))\n-               (error "Current command is identical to existing saved one"))\n-             (setq ledger-reports\n-                   (assq-delete-all existing-name ledger-reports)))\n-            (t\n-             (setq ledger-report-name (ledger-report-read-new-name)))))\n-\n-    (ledger-reports-add ledger-report-name ledger-report-cmd)\n-    (ledger-reports-custom-save)))\n-\n-;; In-place completion support\n-\n-(defun ledger-thing-at-point ()\n-  (let ((here (point)))\n-    (goto-char (line-beginning-position))\n-    (cond ((looking-at "^[0-9/.=-]+\\\\(\\\\s-+\\\\*\\\\)?\\\\(\\\\s-+(.+?)\\\\)?\\\\s-+")\n-           (goto-char (match-end 0))\n-           \'entry)\n-          ((looking-at "^\\\\s-+\\\\([*!]\\\\s-+\\\\)?[[(]?\\\\(.\\\\)")\n-           (goto-char (match-beginning 2))\n-           \'transaction)\n-          ((looking-at "^\\\\(sun\\\\|mon\\\\|tue\\\\|wed\\\\|thu\\\\|fri\\\\|sat\\\\)\\\\s-+")\n-           (goto-char (match-end 0))\n-           \'entry)\n-          (t\n-           (ignore (goto-char here))))))\n-\n-(defun ledger-parse-arguments ()\n-  "Parse whitespace separated arguments in the current region."\n-  (let* ((info (save-excursion\n-                 (cons (ledger-thing-at-point) (point))))\n-         (begin (cdr info))\n-         (end (point))\n-         begins args)\n-    (save-excursion\n-      (goto-char begin)\n-      (when (< (point) end)\n-        (skip-chars-forward " \\t\\n")\n-        (setq begins (cons (point) begins))\n-        (setq args (cons (buffer-substring-no-properties\n-                          (car begins) end)\n-                         args)))\n-      (cons (reverse args) (reverse begins)))))\n-\n-(defun ledger-entries ()\n-  (let ((origin (point))\n-        entries-list)\n-    (save-excursion\n-      (goto-char (point-min))\n-      (while (re-search-forward\n-              (concat "^[0-9/.=-]+\\\\(\\\\s-+\\\\*\\\\)?\\\\(\\\\s-+(.*?)\\\\)?\\\\s-+"\n-                      "\\\\(.+?\\\\)\\\\(\\t\\\\|\\n\\\\| [ \\t]\\\\)") nil t)\n-        (unless (and (>= origin (match-beginning 0))\n-                     (< origin (match-end 0)))\n-          (setq entries-list (cons (match-string-no-properties 3)\n-                                   entries-list)))))\n-    (pcomplete-uniqify-list (nreverse entries-list))))\n-\n-(defvar ledger-account-tree nil)\n-\n-(defun ledger-find-accounts ()\n-  (let ((origin (point)) account-path elements)\n-    (save-excursion\n-      (setq ledger-account-tree (list t))\n-      (goto-char (point-min))\n-      (while (re-search-forward\n-              "^[ \\t]+\\\\([*!]\\\\s-+\\\\)?[[(]?\\\\(.+?\\\\)\\\\(\\t\\\\|\\n\\\\| [ \\t]\\\\)" nil t)\n-        (unless (and (>= origin (match-beginning 0))\n-                     (< origin (match-end 0)))\n-          (setq account-path (match-string-no-properties 2))\n-          (setq elements (split-string account-path ":"))\n-          (let ((root ledger-account-tree))\n-            (while elements\n-              (let ((entry (assoc (car elements) root)))\n-                (if entry\n-                    (setq root (cdr entry))\n-                  (setq entry (cons (car elements) (list t)))\n-                  (nconc root (list entry))\n-                  (setq root (cdr entry))))\n-              (setq elements (cdr elements)))))))))\n-\n-(defun ledger-accounts ()\n-  (ledger-find-accounts)\n-  (let* ((current (caar (ledger-parse-arguments)))\n-         (elements (and current (split-string current ":")))\n-         (root ledger-account-tree)\n-         (prefix nil))\n-    (while (cdr elements)\n-      (let ((entry (assoc (car elements) root)))\n-        (if entry\n-            (setq prefix (concat prefix (and prefix ":")\n-                                 (car elements))\n-                  root (cdr entry))\n-          (setq root nil elements nil)))\n-      (setq elements (cdr elements)))\n-    (and root\n-         (sort\n-          (mapcar (function\n-                   (lambda (x)\n-                     (let ((term (if prefix\n-                                     (concat prefix ":" (car x))\n-                                   (car x))))\n-                       (if (> (length (cdr x)) 1)\n-                           (concat term ":")\n-                         term))))\n-                  (cdr root))\n-          \'string-lessp))))\n-\n-(defun ledger-complete-at-point ()\n-  "Do appropriate completion for the thing at point"\n-  (interactive)\n-  (while (pcomplete-here\n-          (if (eq (save-excursion\n-                    (ledger-thing-at-point)) \'entry)\n-              (if (null current-prefix-arg)\n-                  (ledger-entries)  ; this completes against entry names\n-                (progn\n-                  (let ((text (buffer-substring (line-beginning-position)\n-                                                (line-end-position))))\n-                    (delete-region (line-beginning-position)\n-                                   (line-end-position))\n-                    (condition-case err\n-                        (ledger-add-entry text t)\n-                      ((error)\n-                       (insert text))))\n-                  (forward-line)\n-                  (goto-char (line-end-position))\n-                  (search-backward ";" (line-beginning-position) t)\n-                  (skip-chars-backward " \\t0123456789.,")\n-                  (throw \'pcompleted t)))\n-            (ledger-accounts)))))\n-\n-(defun ledger-fully-complete-entry ()\n-  "Do appropriate completion for the thing at point"\n-  (interactive)\n-  (let ((name (caar (ledger-parse-arguments)))\n-        xacts)\n-    (save-excursion\n-      (when (eq \'entry (ledger-thing-at-point))\n-        (when (re-search-backward\n-               (concat "^[0-9/.=-]+\\\\(\\\\s-+\\\\*\\\\)?\\\\(\\\\s-+(.*?)\\\\)?\\\\s-+"\n-                       (regexp-quote name) "\\\\(\\t\\\\|\\n\\\\| [ \\t]\\\\)") nil t)\n-          (forward-line)\n-          (while (looking-at "^\\\\s-+")\n-            (setq xacts (cons (buffer-substring-no-properties\n-                               (line-beginning-position)\n-                               (line-end-position))\n-                              xacts))\n-            (forward-line))\n-          (setq xacts (nreverse xacts)))))\n-    (when xacts\n-      (save-excursion\n-        (insert ?\\n)\n-        (while xacts\n-          (insert (car xacts) ?\\n)\n-          (setq xacts (cdr xacts))))\n-      (forward-line)\n-      (goto-char (line-end-position))\n-      (if (re-search-backward "\\\\(\\t\\\\| [ \\t]\\\\)" nil t)\n-          (goto-char (match-end 0))))))\n-\n-;; A sample function for $ users\n-\n-(defun ledger-next-amount (&optional end)\n-  (when (re-search-forward "\\\\(  \\\\|\\t\\\\| \\t\\\\)[ \\t]*-?\\\\([A-Z$€£]+ *\\\\)?\\\\(-?[0-9,]+?\\\\)\\\\(.[0-9]+\\\\)?\\\\( *[A-Z$€£]+\\\\)?\\\\([ \\t]*@@?[^\\n;]+?\\\\)?\\\\([ \\t]+;.+?\\\\)?$" (marker-position end) t)\n-    (goto-char (match-beginning 0))\n-    (skip-syntax-forward " ")\n-    (- (or (match-end 4)\n-           (match-end 3)) (point))))\n-\n-(defun ledger-align-amounts (&optional column)\n-  "Align amounts in the current region.\n-This is done so that the last digit falls in COLUMN, which defaults to 52."\n-  (interactive "p")\n-  (if (or (null column) (= column 1))\n-      (setq column 52))\n-  (save-excursion\n-    (let* ((mark-first (< (mark) (point)))\n-           (begin (if mark-first (mark) (point)))\n-           (end (if mark-first (point-marker) (mark-marker)))\n-           offset)\n-      (goto-char begin)\n-      (while (setq offset (ledger-next-amount end))\n-        (let ((col (current-column))\n-              (target-col (- column offset))\n-              adjust)\n-          (setq adjust (- target-col col))\n-          (if (< col target-col)\n-              (insert (make-string (- target-col col) ? ))\n-            (move-to-column target-col)\n-            (if (looking-back "  ")\n-                (delete-char (- col target-col))\n-              (skip-chars-forward "^ \\t")\n-              (delete-horizontal-space)\n-              (insert "  ")))\n-          (forward-line))))))\n-\n-(defalias \'ledger-align-dollars \'ledger-align-amounts)\n-\n-;; A sample entry sorting function, which works if entry dates are of\n-;; the form YYYY/mm/dd.\n-\n-(defun ledger-sort ()\n-  (interactive)\n-  (save-excursion\n-    (goto-char (point-min))\n-    (sort-subr\n-     nil\n-     (function\n-      (lambda ()\n-        (if (re-search-forward\n-             (concat "^[0-9/.=-]+\\\\(\\\\s-+\\\\*\\\\)?\\\\(\\\\s-+(.*?)\\\\)?\\\\s-+"\n-                     "\\\\(.+?\\\\)\\\\(\\t\\\\|\\n\\\\| [ \\t]\\\\)") nil t)\n-            (goto-char (match-beginning 0))\n-          (goto-char (point-max)))))\n-     (function\n-      (lambda ()\n-        (forward-paragraph))))))\n-\n-;; General helper functions\n-\n-(defvar ledger-delete-after nil)\n-\n-(defun ledger-run-ledger (buffer &rest args)\n-  "run ledger with supplied arguments"\n-  ;; Let\'s try again, just in case they moved it while we were sleeping.\n-  (cond\n-   ((null ledger-binary-path)\n-    (error "The variable `ledger-binary-path\' has not been set"))\n-   (t\n-    (let ((buf (current-buffer)))\n-      (with-current-buffer buffer\n-        (let ((coding-system-for-write \'utf-8)\n-              (coding-system-for-read \'utf-8))\n-          (apply #\'call-process-region\n-                 (append (list (point-min) (point-max)\n-                               ledger-binary-path ledger-delete-after\n-                               buf nil "-f" "-")\n-                         args))))))))\n-\n-(defun ledger-run-ledger-and-delete (buffer &rest args)\n-  (let ((ledger-delete-after t))\n-    (apply #\'ledger-run-ledger buffer args)))\n-\n-(defun ledger-set-year (newyear)\n-  "Set ledger\'s idea of the current year to the prefix argument."\n-  (interactive "p")\n-  (if (= newyear 1)\n-      (setq ledger-year (read-string "Year: " (ledger-current-year)))\n-    (setq ledger-year (number-to-string newyear))))\n-\n-(defun ledger-set-month (newmonth)\n-  "Set ledger\'s idea of the current month to the prefix argument."\n-  (interactive "p")\n-  (if (= newmonth 1)\n-      (setq ledger-month (read-string "Month: " (ledger-current-month)))\n-    (setq ledger-month (format "%02d" newmonth))))\n-\n-(defvar ledger-master-file nil)\n-\n-(defun ledger-master-file ()\n-  "Return the master file for a ledger file.\n-\n-The master file is either the file for the current ledger buffer or the\n-file specified by the buffer-local variable ledger-master-file.  Typically\n-this variable would be set in a file local variable comment block at the\n-end of a ledger file which is included in some other file."\n-  (if ledger-master-file\n-      (expand-file-name ledger-master-file)\n-    (buffer-file-name)))\n-\n-(easy-menu-define ledger-menu ledger-mode-map \n-  "Ledger menu"\n-  \'("Ledger"\n-    ["New entry" ledger-add-entry t]\n-    ["Toggle cleared status of current entry" ledger-toggle-current-entry t]\n-    ["Set default year for entry" ledger-set-year t]\n-    ["Set default month for entry" ledger-set-month t]\n-    "--"\n-    ["Reconcile uncleared entries for account" ledger-reconcile t]\n-    "--"\n-    "Reports"\n-    ["Run a report" ledger-report t]\n-    ["Go to report buffer" ledger-report-goto t]\n-    ["Edit defined reports" ledger-report-edit t]\n-    ["Save report definition" ledger-report-save t]\n-    ["Re-run ledger report" ledger-report-redo t]\n-    ["Kill report buffer" ledger-report-kill t]))\n-\n-(provide \'ledger)\n-\n-;;; ledger.el ends here\ndiff --git a/lisp/timeclock.el b/lisp/timeclock.el\ndeleted file mode 100644\nindex 2cafa8eb..00000000\n--- a/lisp/timeclock.el\n+++ /dev/null\n@@ -1,1362 +0,0 @@\n-;;; timeclock.el --- mode for keeping track of how much you work\n-\n-;; Copyright (C) 1999, 2000, 2001, 2003, 2004 Free Software Foundation, Inc.\n-\n-;; Author: John Wiegley <johnw@gnu.org>\n-;; Created: 25 Mar 1999\n-;; Version: 2.6\n-;; Keywords: calendar data\n-\n-;; This file is part of GNU Emacs.\n-\n-;; GNU Emacs is free software; you can redistribute it and/or modify\n-;; it under the terms of the GNU General Public License as published by\n-;; the Free Software Foundation; either version 2, or (at your option)\n-;; any later version.\n-\n-;; GNU Emacs is distributed in the hope that it will be useful,\n-;; but WITHOUT ANY WARRANTY; without even the implied warranty of\n-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n-;; GNU General Public License for more details.\n-\n-;; You should have received a copy of the GNU General Public License\n-;; along with GNU Emacs; see the file COPYING.  If not, write to the\n-;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,\n-;; Boston, MA 02111-1307, USA.\n-\n-;;; Commentary:\n-\n-;; This mode is for keeping track of time intervals.  You can use it\n-;; for whatever purpose you like, but the typical scenario is to keep\n-;; track of how much time you spend working on certain projects.\n-;;\n-;; Use `timeclock-in\' when you start on a project, and `timeclock-out\'\n-;; when you\'re done.  Once you\'ve collected some data, you can use\n-;; `timeclock-workday-remaining\' to see how much time is left to be\n-;; worked today (where `timeclock-workday\' specifies the length of the\n-;; working day), and `timeclock-when-to-leave\' to calculate when you\'re free.\n-\n-;; You\'ll probably want to bind the timeclock commands to some handy\n-;; keystrokes.  At the moment, C-x t is unused:\n-;;\n-;;   (require \'timeclock)\n-;;\n-;;   (define-key ctl-x-map "ti" \'timeclock-in)\n-;;   (define-key ctl-x-map "to" \'timeclock-out)\n-;;   (define-key ctl-x-map "tc" \'timeclock-change)\n-;;   (define-key ctl-x-map "tr" \'timeclock-reread-log)\n-;;   (define-key ctl-x-map "tu" \'timeclock-update-modeline)\n-;;   (define-key ctl-x-map "tw" \'timeclock-when-to-leave-string)\n-\n-;; If you want Emacs to display the amount of time "left" to your\n-;; workday in the modeline, you can either set the value of\n-;; `timeclock-modeline-display\' to t using M-x customize, or you\n-;; can add this code to your .emacs file:\n-;;\n-;;   (require \'timeclock)\n-;;   (timeclock-modeline-display)\n-;;\n-;; To cancel this modeline display at any time, just call\n-;; `timeclock-modeline-display\' again.\n-\n-;; You may also want Emacs to ask you before exiting, if you are\n-;; currently working on a project.  This can be done either by setting\n-;; `timeclock-ask-before-exiting\' to t using M-x customize (this is\n-;; the default), or by adding the following to your .emacs file:\n-;;\n-;;   (add-hook \'kill-emacs-query-functions \'timeclock-query-out)\n-\n-;; NOTE: If you change your .timelog file without using timeclock\'s\n-;; functions, or if you change the value of any of timeclock\'s\n-;; customizable variables, you should run the command\n-;; `timeclock-reread-log\'.  This will recompute any discrepancies in\n-;; your average working time, and will make sure that the various\n-;; display functions return the correct value.\n-\n-;;; History:\n-\n-;;; Code:\n-\n-(defgroup timeclock nil\n-  "Keeping track time of the time that gets spent."\n-  :group \'data)\n-\n-;;; User Variables:\n-\n-(defcustom timeclock-file (convert-standard-filename "~/.timelog")\n-  "*The file used to store timeclock data in."\n-  :type \'file\n-  :group \'timeclock)\n-\n-(defcustom timeclock-workday (* 8 60 60)\n-  "*The length of a work period."\n-  :type \'integer\n-  :group \'timeclock)\n-\n-(defcustom timeclock-relative t\n-  "*Whether to maken reported time relative to `timeclock-workday\'.\n-For example, if the length of a normal workday is eight hours, and you\n-work four hours on Monday, then the amount of time \\"remaining\\" on\n-Tuesday is twelve hours -- relative to an averaged work period of\n-eight hours -- or eight hours, non-relative.  So relative time takes\n-into account any discrepancy of time under-worked or over-worked on\n-previous days.  This only affects the timeclock modeline display."\n-  :type \'boolean\n-  :group \'timeclock)\n-\n-(defcustom timeclock-get-project-function \'timeclock-ask-for-project\n-  "*The function used to determine the name of the current project.\n-When clocking in, and no project is specified, this function will be\n-called to determine what is the current project to be worked on.\n-If this variable is nil, no questions will be asked."\n-  :type \'function\n-  :group \'timeclock)\n-\n-(defcustom timeclock-get-reason-function \'timeclock-ask-for-reason\n-  "*A function used to determine the reason for clocking out.\n-When clocking out, and no reason is specified, this function will be\n-called to determine what is the reason.\n-If this variable is nil, no questions will be asked."\n-  :type \'function\n-  :group \'timeclock)\n-\n-(defcustom timeclock-get-workday-function nil\n-  "*A function used to determine the length of today\'s workday.\n-The first time that a user clocks in each day, this function will be\n-called to determine what is the length of the current workday.  If\n-the return value is nil, or equal to `timeclock-workday\', nothing special\n-will be done.  If it is a quantity different from `timeclock-workday\',\n-however, a record will be output to the timelog file to note the fact that\n-that day has a length that is different from the norm."\n-  :type \'(choice (const nil) function)\n-  :group \'timeclock)\n-\n-(defcustom timeclock-ask-before-exiting t\n-  "*If non-nil, ask if the user wants to clock out before exiting Emacs.\n-This variable only has effect if set with \\\\[customize]."\n-  :set (lambda (symbol value)\n-         (if value\n-             (add-hook \'kill-emacs-query-functions \'timeclock-query-out)\n-           (remove-hook \'kill-emacs-query-functions \'timeclock-query-out))\n-         (setq timeclock-ask-before-exiting value))\n-  :type \'boolean\n-  :group \'timeclock)\n-\n-(defvar timeclock-update-timer nil\n-  "The timer used to update `timeclock-mode-string\'.")\n-\n-;; For byte-compiler.\n-(defvar display-time-hook)\n-(defvar timeclock-modeline-display)\n-\n-(defcustom timeclock-use-display-time t\n-  "*If non-nil, use `display-time-hook\' for doing modeline updates.\n-The advantage of this is that one less timer has to be set running\n-amok in Emacs\' process space.  The disadvantage is that it requires\n-you to have `display-time\' running.  If you don\'t want to use\n-`display-time\', but still want the modeline to show how much time is\n-left, set this variable to nil.  Changing the value of this variable\n-while timeclock information is being displayed in the modeline has no\n-effect.  You should call the function `timeclock-modeline-display\' with\n-a positive argument to force an update."\n-  :set (lambda (symbol value)\n-         (let ((currently-displaying\n-                (and (boundp \'timeclock-modeline-display)\n-                     timeclock-modeline-display)))\n-           ;; if we\'re changing to the state that\n-           ;; `timeclock-modeline-display\' is already using, don\'t\n-           ;; bother toggling it.  This happens on the initial loading\n-           ;; of timeclock.el.\n-           (if (and currently-displaying\n-                    (or (and value\n-                             (boundp \'display-time-hook)\n-                             (memq \'timeclock-update-modeline\n-                                   display-time-hook))\n-                        (and (not value)\n-                             timeclock-update-timer)))\n-               (setq currently-displaying nil))\n-           (and currently-displaying\n-                (set-variable \'timeclock-modeline-display nil))\n-           (setq timeclock-use-display-time value)\n-           (and currently-displaying\n-                (set-variable \'timeclock-modeline-display t))\n-           timeclock-use-display-time))\n-  :type \'boolean\n-  :group \'timeclock\n-  :require \'time)\n-\n-(defcustom timeclock-first-in-hook nil\n-  "*A hook run for the first \\"in\\" event each day.\n-Note that this hook is run before recording any events.  Thus the\n-value of `timeclock-hours-today\', `timeclock-last-event\' and the\n-return value of function `timeclock-last-period\' are relative previous\n-to today."\n-  :type \'hook\n-  :group \'timeclock)\n-\n-(defcustom timeclock-load-hook nil\n-  "*Hook that gets run after timeclock has been loaded."\n-  :type \'hook\n-  :group \'timeclock)\n-\n-(defcustom timeclock-in-hook nil\n-  "*A hook run every time an \\"in\\" event is recorded."\n-  :type \'hook\n-  :group \'timeclock)\n-\n-(defcustom timeclock-day-over-hook nil\n-  "*A hook that is run when the workday has been completed.\n-This hook is only run if the current time remaining is being displayed\n-in the modeline.  See the variable `timeclock-modeline-display\'."\n-  :type \'hook\n-  :group \'timeclock)\n-\n-(defcustom timeclock-out-hook nil\n-  "*A hook run every time an \\"out\\" event is recorded."\n-  :type \'hook\n-  :group \'timeclock)\n-\n-(defcustom timeclock-done-hook nil\n-  "*A hook run every time a project is marked as completed."\n-  :type \'hook\n-  :group \'timeclock)\n-\n-(defcustom timeclock-event-hook nil\n-  "*A hook run every time any event is recorded."\n-  :type \'hook\n-  :group \'timeclock)\n-\n-(defvar timeclock-last-event nil\n-  "A list containing the last event that was recorded.\n-The format of this list is (CODE TIME PROJECT).")\n-\n-(defvar timeclock-last-event-workday nil\n-  "The number of seconds in the workday of `timeclock-last-event\'.")\n-\n-;;; Internal Variables:\n-\n-(defvar timeclock-discrepancy nil\n-  "A variable containing the time discrepancy before the last event.\n-Normally, timeclock assumes that you intend to work for\n-`timeclock-workday\' seconds every day.  Any days in which you work\n-more or less than this amount is considered either a positive or\n-a negative discrepancy.  If you work in such a manner that the\n-discrepancy is always brought back to zero, then you will by\n-definition have worked an average amount equal to `timeclock-workday\'\n-each day.")\n-\n-(defvar timeclock-elapsed nil\n-  "A variable containing the time elapsed for complete periods today.\n-This value is not accurate enough to be useful by itself.  Rather,\n-call `timeclock-workday-elapsed\', to determine how much time has been\n-worked so far today.  Also, if `timeclock-relative\' is nil, this value\n-will be the same as `timeclock-discrepancy\'.") ; ? gm\n-\n-(defvar timeclock-last-period nil\n-  "Integer representing the number of seconds in the last period.\n-Note that you shouldn\'t access this value, but instead should use the\n-function `timeclock-last-period\'.")\n-\n-(defvar timeclock-mode-string nil\n-  "The timeclock string (optionally) displayed in the modeline.\n-The time is bracketed by <> if you are clocked in, otherwise by [].")\n-\n-(defvar timeclock-day-over nil\n-  "The date of the last day when notified \\"day over\\" for.")\n-\n-;;; User Functions:\n-\n-;;;###autoload\n-(defun timeclock-modeline-display (&optional arg)\n-  "Toggle display of the amount of time left today in the modeline.\n-If `timeclock-use-display-time\' is non-nil (the default), then\n-the function `display-time-mode\' must be active, and the modeline\n-will be updated whenever the time display is updated.  Otherwise,\n-the timeclock will use its own sixty second timer to do its\n-updating.  With prefix ARG, turn modeline display on if and only\n-if ARG is positive.  Returns the new status of timeclock modeline\n-display (non-nil means on)."\n-  (interactive "P")\n-  ;; cf display-time-mode.\n-  (setq timeclock-mode-string "")\n-  (or global-mode-string (setq global-mode-string \'("")))\n-  (let ((on-p (if arg\n-                  (> (prefix-numeric-value arg) 0)\n-                (not timeclock-modeline-display))))\n-    (if on-p\n-        (progn\n-          (or (memq \'timeclock-mode-string global-mode-string)\n-              (setq global-mode-string\n-                    (append global-mode-string \'(timeclock-mode-string))))\n-          (unless (memq \'timeclock-update-modeline timeclock-event-hook)\n-            (add-hook \'timeclock-event-hook \'timeclock-update-modeline))\n-          (when timeclock-update-timer\n-            (cancel-timer timeclock-update-timer)\n-            (setq timeclock-update-timer nil))\n-          (if (boundp \'display-time-hook)\n-              (remove-hook \'display-time-hook \'timeclock-update-modeline))\n-          (if timeclock-use-display-time\n-              (progn\n-                ;; Update immediately so there is a visible change\n-                ;; on calling this function.\n-                (if display-time-mode (timeclock-update-modeline)\n-                  (message "Activate `display-time-mode\' to see \\\n-timeclock information"))\n-                (add-hook \'display-time-hook \'timeclock-update-modeline))\n-            (setq timeclock-update-timer\n-                  (run-at-time nil 60 \'timeclock-update-modeline))))\n-      (setq global-mode-string\n-            (delq \'timeclock-mode-string global-mode-string))\n-      (remove-hook \'timeclock-event-hook \'timeclock-update-modeline)\n-      (if (boundp \'display-time-hook)\n-          (remove-hook \'display-time-hook\n-                       \'timeclock-update-modeline))\n-      (when timeclock-update-timer\n-        (cancel-timer timeclock-update-timer)\n-        (setq timeclock-update-timer nil)))\n-    (force-mode-line-update)\n-    (setq timeclock-modeline-display on-p)))\n-\n-;; This has to be here so that the function definition of\n-;; `timeclock-modeline-display\' is known to the "set" function.\n-(defcustom timeclock-modeline-display nil\n-  "Toggle modeline display of time remaining.\n-You must modify via \\\\[customize] for this variable to have an effect."\n-  :set (lambda (symbol value)\n-         (setq timeclock-modeline-display\n-               (timeclock-modeline-display (or value 0))))\n-  :type \'boolean\n-  :group \'timeclock\n-  :require \'timeclock)\n-\n-(defsubst timeclock-time-to-date (time)\n-  "Convert the TIME value to a textual date string."\n-  (format-time-string "%Y/%m/%d" time))\n-\n-;;;###autoload\n-(defun timeclock-in (&optional arg project find-project)\n-  "Clock in, recording the current time moment in the timelog.\n-With a numeric prefix ARG, record the fact that today has only that\n-many hours in it to be worked.  If arg is a non-numeric prefix arg\n-\\(non-nil, but not a number), 0 is assumed (working on a holiday or\n-weekend).  *If not called interactively, ARG should be the number of\n-_seconds_ worked today*.  This feature only has effect the first time\n-this function is called within a day.\n-\n-PROJECT is the project being clocked into.  If PROJECT is nil, and\n-FIND-PROJECT is non-nil -- or the user calls `timeclock-in\'\n-interactively -- call the function `timeclock-get-project-function\' to\n-discover the name of the project."\n-  (interactive\n-   (list (and current-prefix-arg\n-              (if (numberp current-prefix-arg)\n-                  (* current-prefix-arg 60 60)\n-                0))))\n-  (if (equal (car timeclock-last-event) "i")\n-      (error "You\'ve already clocked in!")\n-    (unless timeclock-last-event\n-      (timeclock-reread-log))\n-    ;; Either no log file, or day has rolled over.\n-    (unless (and timeclock-last-event\n-                 (equal (timeclock-time-to-date\n-                         (cadr timeclock-last-event))\n-                        (timeclock-time-to-date (current-time))))\n-      (let ((workday (or (and (numberp arg) arg)\n-                         (and arg 0)\n-                         (and timeclock-get-workday-function\n-                              (funcall timeclock-get-workday-function))\n-                         timeclock-workday)))\n-        (run-hooks \'timeclock-first-in-hook)\n-        ;; settle the discrepancy for the new day\n-        (setq timeclock-discrepancy\n-              (- (or timeclock-discrepancy 0) workday))\n-        (if (not (= workday timeclock-workday))\n-            (timeclock-log "h" (and (numberp arg)\n-                                    (number-to-string arg))))))\n-    (timeclock-log "i" (or project\n-                           (and timeclock-get-project-function\n-                                (or find-project (interactive-p))\n-                                (funcall timeclock-get-project-function))))\n-    (run-hooks \'timeclock-in-hook)))\n-\n-;;;###autoload\n-(defun timeclock-out (&optional arg reason find-reason)\n-  "Clock out, recording the current time moment in the timelog.\n-If a prefix ARG is given, the user has completed the project that was\n-begun during the last time segment.\n-\n-REASON is the user\'s reason for clocking out.  If REASON is nil, and\n-FIND-REASON is non-nil -- or the user calls `timeclock-out\'\n-interactively -- call the function `timeclock-get-reason-function\' to\n-discover the reason."\n-  (interactive "P")\n-  (or timeclock-last-event\n-      (error "You haven\'t clocked in!"))\n-  (if (equal (downcase (car timeclock-last-event)) "o")\n-      (error "You\'ve already clocked out!")\n-    (timeclock-log\n-     (if arg "O" "o")\n-     (or reason\n-         (and timeclock-get-reason-function\n-              (or find-reason (interactive-p))\n-              (funcall timeclock-get-reason-function))))\n-    (run-hooks \'timeclock-out-hook)\n-    (if arg\n-        (run-hooks \'timeclock-done-hook))))\n-\n-;; Should today-only be removed in favour of timeclock-relative? - gm\n-(defsubst timeclock-workday-remaining (&optional today-only)\n-  "Return the number of seconds until the workday is complete.\n-The amount returned is relative to the value of `timeclock-workday\'.\n-If TODAY-ONLY is non-nil, the value returned will be relative only to\n-the time worked today, and not to past time."\n-  (let ((discrep (timeclock-find-discrep)))\n-    (if discrep\n-        (- (if today-only (cadr discrep)\n-             (car discrep)))\n-      0.0)))\n-\n-;;;###autoload\n-(defun timeclock-status-string (&optional show-seconds today-only)\n-  "Report the overall timeclock status at the present moment.\n-If SHOW-SECONDS is non-nil, display second resolution.\n-If TODAY-ONLY is non-nil, the display will be relative only to time\n-worked today, ignoring the time worked on previous days."\n-  (interactive "P")\n-  (let ((remainder (timeclock-workday-remaining)) ; today-only?\n-        (last-in (equal (car timeclock-last-event) "i"))\n-        status)\n-    (setq status\n-          (format "Currently %s since %s (%s), %s %s, leave at %s"\n-                  (if last-in "IN" "OUT")\n-                  (if show-seconds\n-                      (format-time-string "%-I:%M:%S %p"\n-                                          (nth 1 timeclock-last-event))\n-                    (format-time-string "%-I:%M %p"\n-                                        (nth 1 timeclock-last-event)))\n-                  (or (nth 2 timeclock-last-event)\n-                      (if last-in "**UNKNOWN**" "workday over"))\n-                  (timeclock-seconds-to-string remainder show-seconds t)\n-                  (if (> remainder 0)\n-                      "remaining" "over")\n-                  (timeclock-when-to-leave-string show-seconds today-only)))\n-    (if (interactive-p)\n-        (message status)\n-      status)))\n-\n-;;;###autoload\n-(defun timeclock-change (&optional arg project)\n-  "Change to working on a different project.\n-This clocks out of the current project, then clocks in on a new one.\n-With a prefix ARG, consider the previous project as finished at the\n-time of changeover.  PROJECT is the name of the last project you were\n-working on."\n-  (interactive "P")\n-  (timeclock-out arg)\n-  (timeclock-in nil project (interactive-p)))\n-\n-;;;###autoload\n-(defun timeclock-query-out ()\n-  "Ask the user whether to clock out.\n-This is a useful function for adding to `kill-emacs-query-functions\'."\n-  (and (equal (car timeclock-last-event) "i")\n-       (y-or-n-p "You\'re currently clocking time, clock out? ")\n-       (timeclock-out))\n-  ;; Unconditionally return t for `kill-emacs-query-functions\'.\n-  t)\n-\n-;;;###autoload\n-(defun timeclock-reread-log ()\n-  "Re-read the timeclock, to account for external changes.\n-Returns the new value of `timeclock-discrepancy\'."\n-  (interactive)\n-  (setq timeclock-discrepancy nil)\n-  (timeclock-find-discrep)\n-  (if (and timeclock-discrepancy timeclock-modeline-display)\n-      (timeclock-update-modeline))\n-  timeclock-discrepancy)\n-\n-(defun timeclock-seconds-to-string (seconds &optional show-seconds\n-                                            reverse-leader)\n-  "Convert SECONDS into a compact time string.\n-If SHOW-SECONDS is non-nil, make the resolution of the return string\n-include the second count.  If REVERSE-LEADER is non-nil, it means to\n-output a \\"+\\" if the time value is negative, rather than a \\"-\\".\n-This is used when negative time values have an inverted meaning (such\n-as with time remaining, where negative time really means overtime)."\n-  (if show-seconds\n-      (format "%s%d:%02d:%02d"\n-              (if (< seconds 0) (if reverse-leader "+" "-") "")\n-              (truncate (/ (abs seconds) 60 60))\n-              (% (truncate (/ (abs seconds) 60)) 60)\n-              (% (truncate (abs seconds)) 60))\n-    (format "%s%d:%02d"\n-            (if (< seconds 0) (if reverse-leader "+" "-") "")\n-            (truncate (/ (abs seconds) 60 60))\n-            (% (truncate (/ (abs seconds) 60)) 60))))\n-\n-(defsubst timeclock-currently-in-p ()\n-  "Return non-nil if the user is currently clocked in."\n-  (equal (car timeclock-last-event) "i"))\n-\n-;;;###autoload\n-(defun timeclock-workday-remaining-string (&optional show-seconds\n-                                                     today-only)\n-  "Return a string representing the amount of time left today.\n-Display second resolution if SHOW-SECONDS is non-nil.  If TODAY-ONLY\n-is non-nil, the display will be relative only to time worked today.\n-See `timeclock-relative\' for more information about the meaning of\n-\\"relative to today\\"."\n-  (interactive)\n-  (let ((string (timeclock-seconds-to-string\n-                 (timeclock-workday-remaining today-only)\n-                 show-seconds t)))\n-    (if (interactive-p)\n-        (message string)\n-      string)))\n-\n-(defsubst timeclock-workday-elapsed ()\n-  "Return the number of seconds worked so far today.\n-If RELATIVE is non-nil, the amount returned will be relative to past\n-time worked.  The default is to return only the time that has elapsed\n-so far today."\n-  (let ((discrep (timeclock-find-discrep)))\n-    (if discrep\n-        (nth 2 discrep)\n-      0.0)))\n-\n-;;;###autoload\n-(defun timeclock-workday-elapsed-string (&optional show-seconds)\n-  "Return a string representing the amount of time worked today.\n-Display seconds resolution if SHOW-SECONDS is non-nil.  If RELATIVE is\n-non-nil, the amount returned will be relative to past time worked."\n-  (interactive)\n-  (let ((string (timeclock-seconds-to-string (timeclock-workday-elapsed)\n-                                             show-seconds)))\n-    (if (interactive-p)\n-        (message string)\n-      string)))\n-\n-(defsubst timeclock-time-to-seconds (time)\n-  "Convert TIME to a floating point number."\n-  (+ (* (car time) 65536.0)\n-     (cadr time)\n-     (/ (or (car (cdr (cdr time))) 0) 1000000.0)))\n-\n-(defsubst timeclock-seconds-to-time (seconds)\n-  "Convert SECONDS (a floating point number) to an Emacs time structure."\n-  (list (floor seconds 65536)\n-        (floor (mod seconds 65536))\n-        (floor (* (- seconds (ffloor seconds)) 1000000))))\n-\n-;; Should today-only be removed in favour of timeclock-relative? - gm\n-(defsubst timeclock-when-to-leave (&optional today-only)\n-  "Return a time value representing the end of today\'s workday.\n-If TODAY-ONLY is non-nil, the value returned will be relative only to\n-the time worked today, and not to past time."\n-  (timeclock-seconds-to-time\n-   (- (timeclock-time-to-seconds (current-time))\n-      (let ((discrep (timeclock-find-discrep)))\n-        (if discrep\n-            (if today-only\n-                (cadr discrep)\n-              (car discrep))\n-          0.0)))))\n-\n-;;;###autoload\n-(defun timeclock-when-to-leave-string (&optional show-seconds\n-                                                 today-only)\n-  "Return a string representing the end of today\'s workday.\n-This string is relative to the value of `timeclock-workday\'.  If\n-SHOW-SECONDS is non-nil, the value printed/returned will include\n-seconds.  If TODAY-ONLY is non-nil, the value returned will be\n-relative only to the time worked today, and not to past time."\n-  ;; Should today-only be removed in favour of timeclock-relative? - gm\n-  (interactive)\n-  (let* ((then (timeclock-when-to-leave today-only))\n-         (string\n-          (if show-seconds\n-              (format-time-string "%-I:%M:%S %p" then)\n-            (format-time-string "%-I:%M %p" then))))\n-    (if (interactive-p)\n-        (message string)\n-      string)))\n-\n-;;; Internal Functions:\n-\n-(defvar timeclock-project-list nil)\n-(defvar timeclock-last-project nil)\n-\n-(defun timeclock-completing-read (prompt alist &optional default)\n-  "A version of `completing-read\' that works on both Emacs and XEmacs."\n-  (if (featurep \'xemacs)\n-      (let ((str (completing-read prompt alist)))\n-        (if (or (null str) (= (length str) 0))\n-            default\n-          str))\n-    (completing-read prompt alist nil nil nil nil default)))\n-\n-(defun timeclock-ask-for-project ()\n-  "Ask the user for the project they are clocking into."\n-  (timeclock-completing-read\n-   (format "Clock into which project (default \\"%s\\"): "\n-           (or timeclock-last-project\n-               (car timeclock-project-list)))\n-   (mapcar \'list timeclock-project-list)\n-   (or timeclock-last-project\n-       (car timeclock-project-list))))\n-\n-(defvar timeclock-reason-list nil)\n-\n-(defun timeclock-ask-for-reason ()\n-  "Ask the user for the reason they are clocking out."\n-  (timeclock-completing-read "Reason for clocking out: "\n-                             (mapcar \'list timeclock-reason-list)))\n-\n-(defun timeclock-update-modeline ()\n-  "Update the `timeclock-mode-string\' displayed in the modeline.\n-The value of `timeclock-relative\' affects the display as described in\n-that variable\'s documentation."\n-  (interactive)\n-  (let ((remainder (timeclock-workday-remaining (not timeclock-relative)))\n-        (last-in (equal (car timeclock-last-event) "i")))\n-    (when (and (< remainder 0)\n-               (not (and timeclock-day-over\n-                         (equal timeclock-day-over\n-                                (timeclock-time-to-date\n-                                 (current-time))))))\n-      (setq timeclock-day-over\n-            (timeclock-time-to-date (current-time)))\n-      (run-hooks \'timeclock-day-over-hook))\n-    (setq timeclock-mode-string\n-          (propertize\n-           (format " %c%s%c "\n-                   (if last-in ?< ?[)\n-                   (timeclock-seconds-to-string remainder nil t)\n-                   (if last-in ?> ?]))\n-           \'help-echo "timeclock: time remaining"))))\n-\n-(put \'timeclock-mode-string \'risky-local-variable t)\n-\n-(defun timeclock-log (code &optional project)\n-  "Log the event CODE to the timeclock log, at the time of call.\n-If PROJECT is a string, it represents the project which the event is\n-being logged for.  Normally only \\"in\\" events specify a project."\n-  (with-current-buffer (find-file-noselect timeclock-file)\n-    (goto-char (point-max))\n-    (if (not (bolp))\n-        (insert "\\n"))\n-    (let ((now (current-time)))\n-      (insert code " "\n-              (format-time-string "%Y/%m/%d %H:%M:%S" now)\n-              (or (and project\n-                       (stringp project)\n-                       (> (length project) 0)\n-                       (concat " " project))\n-                  "")\n-              "\\n")\n-      (if (equal (downcase code) "o")\n-          (setq timeclock-last-period\n-                (- (timeclock-time-to-seconds now)\n-                   (timeclock-time-to-seconds\n-                    (cadr timeclock-last-event)))\n-                timeclock-discrepancy\n-                (+ timeclock-discrepancy\n-                   timeclock-last-period)))\n-      (setq timeclock-last-event (list code now project)))\n-    (save-buffer)\n-    (run-hooks \'timeclock-event-hook)\n-    (kill-buffer (current-buffer))))\n-\n-(defvar timeclock-moment-regexp\n-  (concat "\\\\([bhioO]\\\\)\\\\s-+"\n-          "\\\\([0-9]+\\\\)/\\\\([0-9]+\\\\)/\\\\([0-9]+\\\\)\\\\s-+"\n-          "\\\\([0-9]+\\\\):\\\\([0-9]+\\\\):\\\\([0-9]+\\\\)[ \\t]*" "\\\\([^\\n]*\\\\)"))\n-\n-(defsubst timeclock-read-moment ()\n-  "Read the moment under point from the timelog."\n-  (if (looking-at timeclock-moment-regexp)\n-      (let ((code (match-string 1))\n-            (year (string-to-number (match-string 2)))\n-            (mon  (string-to-number (match-string 3)))\n-            (mday (string-to-number (match-string 4)))\n-            (hour (string-to-number (match-string 5)))\n-            (min  (string-to-number (match-string 6)))\n-            (sec  (string-to-number (match-string 7)))\n-            (project (match-string 8)))\n-        (list code (encode-time sec min hour mday mon year) project))))\n-\n-(defun timeclock-last-period (&optional moment)\n-  "Return the value of the last event period.\n-If the last event was a clock-in, the period will be open ended, and\n-growing every second.  Otherwise, it is a fixed amount which has been\n-recorded to disk.  If MOMENT is non-nil, use that as the current time.\n-This is only provided for coherency when used by\n-`timeclock-discrepancy\'."\n-  (if (equal (car timeclock-last-event) "i")\n-      (- (timeclock-time-to-seconds (or moment (current-time)))\n-         (timeclock-time-to-seconds\n-          (cadr timeclock-last-event)))\n-    timeclock-last-period))\n-\n-(defsubst timeclock-entry-length (entry)\n-  (- (timeclock-time-to-seconds (cadr entry))\n-     (timeclock-time-to-seconds (car entry))))\n-\n-(defsubst timeclock-entry-begin (entry)\n-  (car entry))\n-\n-(defsubst timeclock-entry-end (entry)\n-  (cadr entry))\n-\n-(defsubst timeclock-entry-project (entry)\n-  (nth 2 entry))\n-\n-(defsubst timeclock-entry-comment (entry)\n-  (nth 3 entry))\n-\n-\n-(defsubst timeclock-entry-list-length (entry-list)\n-  (let ((length 0))\n-    (while entry-list\n-      (setq length (+ length (timeclock-entry-length (car entry-list))))\n-      (setq entry-list (cdr entry-list)))\n-    length))\n-\n-(defsubst timeclock-entry-list-begin (entry-list)\n-  (timeclock-entry-begin (car entry-list)))\n-\n-(defsubst timeclock-entry-list-end (entry-list)\n-  (timeclock-entry-end (car (last entry-list))))\n-\n-(defsubst timeclock-entry-list-span (entry-list)\n-  (- (timeclock-time-to-seconds (timeclock-entry-list-end entry-list))\n-     (timeclock-time-to-seconds (timeclock-entry-list-begin entry-list))))\n-\n-(defsubst timeclock-entry-list-break (entry-list)\n-  (- (timeclock-entry-list-span entry-list)\n-     (timeclock-entry-list-length entry-list)))\n-\n-(defsubst timeclock-entry-list-projects (entry-list)\n-  (let (projects)\n-    (while entry-list\n-      (let ((project (timeclock-entry-project (car entry-list))))\n-        (if projects\n-            (add-to-list \'projects project)\n-          (setq projects (list project))))\n-      (setq entry-list (cdr entry-list)))\n-    projects))\n-\n-\n-(defsubst timeclock-day-required (day)\n-  (or (car day) timeclock-workday))\n-\n-(defsubst timeclock-day-length (day)\n-  (timeclock-entry-list-length (cdr day)))\n-\n-(defsubst timeclock-day-debt (day)\n-  (- (timeclock-day-required day)\n-     (timeclock-day-length day)))\n-\n-(defsubst timeclock-day-begin (day)\n-  (timeclock-entry-list-begin (cdr day)))\n-\n-(defsubst timeclock-day-end (day)\n-  (timeclock-entry-list-end (cdr day)))\n-\n-(defsubst timeclock-day-span (day)\n-  (timeclock-entry-list-span (cdr day)))\n-\n-(defsubst timeclock-day-break (day)\n-  (timeclock-entry-list-break (cdr day)))\n-\n-(defsubst timeclock-day-projects (day)\n-  (timeclock-entry-list-projects (cdr day)))\n-\n-(defmacro timeclock-day-list-template (func)\n-  `(let ((length 0))\n-     (while day-list\n-       (setq length (+ length (,(eval func) (car day-list))))\n-       (setq day-list (cdr day-list)))\n-     length))\n-\n-(defun timeclock-day-list-required (day-list)\n-  (timeclock-day-list-template \'timeclock-day-required))\n-\n-(defun timeclock-day-list-length (day-list)\n-  (timeclock-day-list-template \'timeclock-day-length))\n-\n-(defun timeclock-day-list-debt (day-list)\n-  (timeclock-day-list-template \'timeclock-day-debt))\n-\n-(defsubst timeclock-day-list-begin (day-list)\n-  (timeclock-day-begin (car day-list)))\n-\n-(defsubst timeclock-day-list-end (day-list)\n-  (timeclock-day-end (car (last day-list))))\n-\n-(defun timeclock-day-list-span (day-list)\n-  (timeclock-day-list-template \'timeclock-day-span))\n-\n-(defun timeclock-day-list-break (day-list)\n-  (timeclock-day-list-template \'timeclock-day-break))\n-\n-(defun timeclock-day-list-projects (day-list)\n-  (let (projects)\n-    (while day-list\n-      (let ((projs (timeclock-day-projects (car day-list))))\n-        (while projs\n-          (if projects\n-              (add-to-list \'projects (car projs))\n-            (setq projects (list (car projs))))\n-          (setq projs (cdr projs))))\n-      (setq day-list (cdr day-list)))\n-    projects))\n-\n-\n-(defsubst timeclock-current-debt (&optional log-data)\n-  (nth 0 (or log-data (timeclock-log-data))))\n-\n-(defsubst timeclock-day-alist (&optional log-data)\n-  (nth 1 (or log-data (timeclock-log-data))))\n-\n-(defun timeclock-day-list (&optional log-data)\n-  (let ((alist (timeclock-day-alist log-data))\n-        day-list)\n-    (while alist\n-      (setq day-list (cons (cdar alist) day-list)\n-            alist (cdr alist)))\n-    day-list))\n-\n-(defsubst timeclock-project-alist (&optional log-data)\n-  (nth 2 (or log-data (timeclock-log-data))))\n-\n-\n-(defun timeclock-log-data (&optional recent-only filename)\n-  "Return the contents of the timelog file, in a useful format.\n-If the optional argument RECENT-ONLY is non-nil, only show the contents\n-from the last point where the time debt (see below) was set.\n-If the optional argument FILENAME is non-nil, it is used instead of\n-the file specified by `timeclock-file.\'\n-\n-A timelog contains data in the form of a single entry per line.\n-Each entry has the form:\n-\n-  CODE YYYY/MM/DD HH:MM:SS [COMMENT]\n-\n-CODE is one of: b, h, i, o or O.  COMMENT is optional when the code is\n-i, o or O.  The meanings of the codes are:\n-\n-  b  Set the current time balance, or \\"time debt\\".  Useful when\n-     archiving old log data, when a debt must be carried forward.\n-     The COMMENT here is the number of seconds of debt.\n-\n-  h  Set the required working time for the given day.  This must\n-     be the first entry for that day.  The COMMENT in this case is\n-     the number of hours in this workday.  Floating point amounts\n-     are allowed.\n-\n-  i  Clock in.  The COMMENT in this case should be the name of the\n-     project worked on.\n-\n-  o  Clock out.  COMMENT is unnecessary, but can be used to provide\n-     a description of how the period went, for example.\n-\n-  O  Final clock out.  Whatever project was being worked on, it is\n-     now finished.  Useful for creating summary reports.\n-\n-When this function is called, it will return a data structure with the\n-following format:\n-\n-  (DEBT ENTRIES-BY-DAY ENTRIES-BY-PROJECT)\n-\n-DEBT is a floating point number representing the number of seconds\n-\\"owed\\" before any work was done.  For a new file (one without a \'b\'\n-entry), this is always zero.\n-\n-The two entries lists have similar formats.  They are both alists,\n-where the CAR is the index, and the CDR is a list of time entries.\n-For ENTRIES-BY-DAY, the CAR is a textual date string, of the form\n-YYYY/MM/DD.  For ENTRIES-BY-PROJECT, it is the name of the project\n-worked on, or t for the default project.\n-\n-The CDR for ENTRIES-BY-DAY is slightly different than for\n-ENTRIES-BY-PROJECT.  It has the following form:\n-\n-  (DAY-LENGTH TIME-ENTRIES...)\n-\n-For ENTRIES-BY-PROJECT, there is no DAY-LENGTH member.  It is simply a\n-list of TIME-ENTRIES.  Note that if DAY-LENGTH is nil, it means\n-whatever is the default should be used.\n-\n-A TIME-ENTRY is a recorded time interval.  It has the following format\n-\\(although generally one does not have to manipulate these entries\n-directly; see below):\n-\n-  (BEGIN-TIME END-TIME PROJECT [COMMENT] [FINAL-P])\n-\n-Anyway, suffice it to say there are a lot of structures.  Typically\n-the user is expected to manipulate to the day(s) or project(s) that he\n-or she wants, at which point the following helper functions may be\n-used:\n-\n-  timeclock-day-required\n-  timeclock-day-length\n-  timeclock-day-debt\n-  timeclock-day-begin\n-  timeclock-day-end\n-  timeclock-day-span\n-  timeclock-day-break\n-  timeclock-day-projects\n-\n-  timeclock-day-list-required\n-  timeclock-day-list-length\n-  timeclock-day-list-debt\n-  timeclock-day-list-begin\n-  timeclock-day-list-end\n-  timeclock-day-list-span\n-  timeclock-day-list-break\n-  timeclock-day-list-projects\n-\n-  timeclock-entry-length\n-  timeclock-entry-begin\n-  timeclock-entry-end\n-  timeclock-entry-project\n-  timeclock-entry-comment\n-\n-  timeclock-entry-list-length\n-  timeclock-entry-list-begin\n-  timeclock-entry-list-end\n-  timeclock-entry-list-span\n-  timeclock-entry-list-break\n-  timeclock-entry-list-projects\n-\n-A few comments should make the use of the above functions obvious:\n-\n-  `required\' is the amount of time that must be spent during a day, or\n-  sequence of days, in order to have no debt.\n-\n-  `length\' is the actual amount of time that was spent.\n-\n-  `debt\' is the difference between required time and length.  A\n-  negative debt signifies overtime.\n-\n-  `begin\' is the earliest moment at which work began.\n-\n-  `end\' is the final moment work was done.\n-\n-  `span\' is the difference between begin and end.\n-\n-  `break\' is the difference between span and length.\n-\n-  `project\' is the project that was worked on, and `projects\' is a\n-  list of all the projects that were worked on during a given period.\n-\n-  `comment\', where it applies, could mean anything.\n-\n-There are a few more functions available, for locating day and entry\n-lists:\n-\n-  timeclock-day-alist LOG-DATA\n-  timeclock-project-alist LOG-DATA\n-  timeclock-current-debt LOG-DATA\n-\n-See the documentation for the given function if more info is needed."\n-  (let* ((log-data (list 0.0 nil nil))\n-         (now (current-time))\n-         (todays-date (timeclock-time-to-date now))\n-         last-date-limited last-date-seconds last-date\n-         (line 0) last beg day entry event)\n-    (with-temp-buffer\n-      (insert-file-contents (or filename timeclock-file))\n-      (when recent-only\n-        (goto-char (point-max))\n-        (unless (re-search-backward "^b\\\\s-+" nil t)\n-          (goto-char (point-min))))\n-      (while (or (setq event (timeclock-read-moment))\n-                 (and beg (not last)\n-                      (setq last t event (list "o" now))))\n-        (setq line (1+ line))\n-        (cond ((equal (car event) "b")\n-               (setcar log-data (string-to-number (nth 2 event))))\n-              ((equal (car event) "h")\n-               (setq last-date-limited (timeclock-time-to-date (cadr event))\n-                     last-date-seconds (* (string-to-number (nth 2 event))\n-                                          3600.0)))\n-              ((equal (car event) "i")\n-               (if beg\n-                   (error "Error in format of timelog file, line %d" line)\n-                 (setq beg t))\n-               (setq entry (list (cadr event) nil\n-                                 (and (> (length (nth 2 event)) 0)\n-                                      (nth 2 event))))\n-               (let ((date (timeclock-time-to-date (cadr event))))\n-                 (if (and last-date\n-                          (not (equal date last-date)))\n-                     (progn\n-                       (setcar (cdr log-data)\n-                               (cons (cons last-date day)\n-                                     (cadr log-data)))\n-                       (setq day (list (and last-date-limited\n-                                            last-date-seconds))))\n-                   (unless day\n-                     (setq day (list (and last-date-limited\n-                                          last-date-seconds)))))\n-                 (setq last-date date\n-                       last-date-limited nil)))\n-              ((equal (downcase (car event)) "o")\n-               (if (not beg)\n-                   (error "Error in format of timelog file, line %d" line)\n-                 (setq beg nil))\n-               (setcar (cdr entry) (cadr event))\n-               (let ((desc (and (> (length (nth 2 event)) 0)\n-                                (nth 2 event))))\n-                 (if desc\n-                     (nconc entry (list (nth 2 event))))\n-                 (if (equal (car event) "O")\n-                     (nconc entry (if desc\n-                                      (list t)\n-                                    (list nil t))))\n-                 (nconc day (list entry))\n-                 (setq desc (nth 2 entry))\n-                 (let ((proj (assoc desc (nth 2 log-data))))\n-                   (if (null proj)\n-                       (setcar (cddr log-data)\n-                               (cons (cons desc (list entry))\n-                                     (car (cddr log-data))))\n-                     (nconc (cdr proj) (list entry)))))))\n-        (forward-line))\n-      (if day\n-          (setcar (cdr log-data)\n-                  (cons (cons last-date day)\n-                        (cadr log-data))))\n-      log-data)))\n-\n-(defun timeclock-find-discrep ()\n-  "Calculate time discrepancies, in seconds.\n-The result is a three element list, containing the total time\n-discrepancy, today\'s discrepancy, and the time worked today."\n-  ;; This is not implemented in terms of the functions above, because\n-  ;; it\'s a bit wasteful to read all of that data in, just to throw\n-  ;; away more than 90% of the information afterwards.\n-  ;;\n-  ;; If it were implemented using those functions, it would look\n-  ;; something like this:\n-  ;;  (let ((days (timeclock-day-alist (timeclock-log-data)))\n-  ;;        (total 0.0))\n-  ;;    (while days\n-  ;;      (setq total (+ total (- (timeclock-day-length (cdar days))\n-  ;;                              (timeclock-day-required (cdar days))))\n-  ;;            days (cdr days)))\n-  ;;    total)\n-  (let* ((now (current-time))\n-         (todays-date (timeclock-time-to-date now))\n-         (first t) (accum 0) (elapsed 0)\n-         event beg last-date avg\n-         last-date-limited last-date-seconds)\n-    (unless timeclock-discrepancy\n-      (when (file-readable-p timeclock-file)\n-        (setq timeclock-project-list nil\n-              timeclock-last-project nil\n-              timeclock-reason-list nil\n-              timeclock-elapsed 0)\n-        (with-temp-buffer\n-          (insert-file-contents timeclock-file)\n-          (goto-char (point-max))\n-          (unless (re-search-backward "^b\\\\s-+" nil t)\n-            (goto-char (point-min)))\n-          (while (setq event (timeclock-read-moment))\n-            (cond ((equal (car event) "b")\n-                   (setq accum (string-to-number (nth 2 event))))\n-                  ((equal (car event) "h")\n-                   (setq last-date-limited\n-                         (timeclock-time-to-date (cadr event))\n-                         last-date-seconds\n-                         (* (string-to-number (nth 2 event)) 3600.0)))\n-                  ((equal (car event) "i")\n-                   (when (and (nth 2 event)\n-                              (> (length (nth 2 event)) 0))\n-                     (add-to-list \'timeclock-project-list (nth 2 event))\n-                     (setq timeclock-last-project (nth 2 event)))\n-                   (let ((date (timeclock-time-to-date (cadr event))))\n-                     (if (if last-date\n-                             (not (equal date last-date))\n-                           first)\n-                         (setq first nil\n-                               accum (- accum (if last-date-limited\n-                                                  last-date-seconds\n-                                                timeclock-workday))))\n-                     (setq last-date date\n-                           last-date-limited nil)\n-                     (if beg\n-                         (error "Error in format of timelog file!")\n-                       (setq beg (timeclock-time-to-seconds (cadr event))))))\n-                  ((equal (downcase (car event)) "o")\n-                   (if (and (nth 2 event)\n-                            (> (length (nth 2 event)) 0))\n-                       (add-to-list \'timeclock-reason-list (nth 2 event)))\n-                   (if (not beg)\n-                       (error "Error in format of timelog file!")\n-                     (setq timeclock-last-period\n-                           (- (timeclock-time-to-seconds (cadr event)) beg)\n-                           accum (+ timeclock-last-period accum)\n-                           beg nil))\n-                   (if (equal last-date todays-date)\n-                       (setq timeclock-elapsed\n-                             (+ timeclock-last-period timeclock-elapsed)))))\n-            (setq timeclock-last-event event\n-                  timeclock-last-event-workday\n-                  (if (equal (timeclock-time-to-date now) last-date-limited)\n-                      last-date-seconds\n-                    timeclock-workday))\n-            (forward-line))\n-          (setq timeclock-discrepancy accum))))\n-    (unless timeclock-last-event-workday\n-      (setq timeclock-last-event-workday timeclock-workday))\n-    (setq accum (or timeclock-discrepancy 0)\n-          elapsed (or timeclock-elapsed elapsed))\n-    (if timeclock-last-event\n-        (if (equal (car timeclock-last-event) "i")\n-            (let ((last-period (timeclock-last-period now)))\n-              (setq accum (+ accum last-period)\n-                    elapsed (+ elapsed last-period)))\n-          (if (not (equal (timeclock-time-to-date\n-                           (cadr timeclock-last-event))\n-                          (timeclock-time-to-date now)))\n-              (setq accum (- accum timeclock-last-event-workday)))))\n-    (list accum (- elapsed timeclock-last-event-workday)\n-          elapsed)))\n-\n-;;; A reporting function that uses timeclock-log-data\n-\n-(defun timeclock-day-base (&optional time)\n-  "Given a time within a day, return 0:0:0 within that day.\n-If optional argument TIME is non-nil, use that instead of the current time."\n-  (let ((decoded (decode-time (or time (current-time)))))\n-    (setcar (nthcdr 0 decoded) 0)\n-    (setcar (nthcdr 1 decoded) 0)\n-    (setcar (nthcdr 2 decoded) 0)\n-    (apply \'encode-time decoded)))\n-\n-(defun timeclock-geometric-mean (l)\n-  "Compute the geometric mean of the values in the list L."\n-  (let ((total 0)\n-        (count 0))\n-    (while l\n-      (setq total (+ total (car l))\n-            count (1+ count)\n-            l (cdr l)))\n-    (if (> count 0)\n-        (/ total count)\n-      0)))\n-\n-(defun timeclock-generate-report (&optional html-p)\n-  "Generate a summary report based on the current timelog file.\n-By default, the report is in plain text, but if the optional argument\n-HTML-P is non-nil, HTML markup is added."\n-  (interactive)\n-  (let ((log (timeclock-log-data))\n-        (today (timeclock-day-base)))\n-    (if html-p (insert "<p>"))\n-    (insert "Currently ")\n-    (let ((project (nth 2 timeclock-last-event))\n-          (begin (nth 1 timeclock-last-event))\n-          done)\n-      (if (timeclock-currently-in-p)\n-          (insert "IN")\n-        (if (or (null project) (= (length project) 0))\n-            (progn (insert "Done Working Today")\n-                   (setq done t))\n-          (insert "OUT")))\n-      (unless done\n-        (insert " since " (format-time-string "%Y/%m/%d %-I:%M %p" begin))\n-        (if html-p\n-            (insert "<br>\\n<b>")\n-          (insert "\\n*"))\n-        (if (timeclock-currently-in-p)\n-            (insert "Working on "))\n-        (if html-p\n-            (insert project "</b><br>\\n")\n-          (insert project "*\\n"))\n-        (let ((proj-data (cdr (assoc project (timeclock-project-alist log))))\n-              (two-weeks-ago (timeclock-seconds-to-time\n-                              (- (timeclock-time-to-seconds today)\n-                                 (* 2 7 24 60 60))))\n-              two-week-len today-len)\n-          (while proj-data\n-            (if (not (time-less-p\n-                      (timeclock-entry-begin (car proj-data)) today))\n-                (setq today-len (timeclock-entry-list-length proj-data)\n-                      proj-data nil)\n-              (if (and (null two-week-len)\n-                       (not (time-less-p\n-                             (timeclock-entry-begin (car proj-data))\n-                             two-weeks-ago)))\n-                  (setq two-week-len (timeclock-entry-list-length proj-data)))\n-              (setq proj-data (cdr proj-data))))\n-          (if (null two-week-len)\n-              (setq two-week-len today-len))\n-          (if html-p (insert "<p>"))\n-          (if today-len\n-              (insert "\\nTime spent on this task today: "\n-                      (timeclock-seconds-to-string today-len)\n-                      ".  In the last two weeks: "\n-                      (timeclock-seconds-to-string two-week-len))\n-            (if two-week-len\n-                (insert "\\nTime spent on this task in the last two weeks: "\n-                        (timeclock-seconds-to-string two-week-len))))\n-          (if html-p (insert "<br>"))\n-          (insert "\\n"\n-                  (timeclock-seconds-to-string (timeclock-workday-elapsed))\n-                  " worked today, "\n-                  (timeclock-seconds-to-string (timeclock-workday-remaining))\n-                  " remaining, done at "\n-                  (timeclock-when-to-leave-string) "\\n")))\n-      (if html-p (insert "<p>"))\n-      (insert "\\nThere have been "\n-              (number-to-string\n-               (length (timeclock-day-alist log)))\n-              " days of activity, starting "\n-              (caar (last (timeclock-day-alist log))))\n-      (if html-p (insert "</p>"))\n-      (when html-p\n-        (insert "<p>\n-<table>\n-<td width=\\"25\\"><br></td><td>\n-<table border=1 cellpadding=3>\n-<tr><th><i>Statistics</i></th>\n-    <th>Entire</th>\n-    <th>-30 days</th>\n-    <th>-3 mons</th>\n-    <th>-6 mons</th>\n-    <th>-1 year</th>\n-</tr>")\n-        (let* ((day-list (timeclock-day-list))\n-               (thirty-days-ago (timeclock-seconds-to-time\n-                                 (- (timeclock-time-to-seconds today)\n-                                    (* 30 24 60 60))))\n-               (three-months-ago (timeclock-seconds-to-time\n-                                  (- (timeclock-time-to-seconds today)\n-                                     (* 90 24 60 60))))\n-               (six-months-ago (timeclock-seconds-to-time\n-                                (- (timeclock-time-to-seconds today)\n-                                   (* 180 24 60 60))))\n-               (one-year-ago (timeclock-seconds-to-time\n-                              (- (timeclock-time-to-seconds today)\n-                                 (* 365 24 60 60))))\n-               (time-in  (vector (list t) (list t) (list t) (list t) (list t)))\n-               (time-out (vector (list t) (list t) (list t) (list t) (list t)))\n-               (breaks   (vector (list t) (list t) (list t) (list t) (list t)))\n-               (workday  (vector (list t) (list t) (list t) (list t) (list t)))\n-               (lengths  (vector \'(0 0) thirty-days-ago three-months-ago\n-                                 six-months-ago one-year-ago)))\n-          ;; collect statistics from complete timelog\n-          (while day-list\n-            (let ((i 0) (l 5))\n-              (while (< i l)\n-                (unless (time-less-p\n-                         (timeclock-day-begin (car day-list))\n-                         (aref lengths i))\n-                  (let ((base (timeclock-time-to-seconds\n-                               (timeclock-day-base\n-                                (timeclock-day-begin (car day-list))))))\n-                    (nconc (aref time-in i)\n-                           (list (- (timeclock-time-to-seconds\n-                                     (timeclock-day-begin (car day-list)))\n-                                    base)))\n-                    (let ((span (timeclock-day-span (car day-list)))\n-                          (len (timeclock-day-length (car day-list)))\n-                          (req (timeclock-day-required (car day-list))))\n-                      ;; If the day\'s actual work length is less than\n-                      ;; 70% of its span, then likely the exit time\n-                      ;; and break amount are not worthwhile adding to\n-                      ;; the statistic\n-                      (when (and (> span 0)\n-                                 (> (/ (float len) (float span)) 0.70))\n-                        (nconc (aref time-out i)\n-                               (list (- (timeclock-time-to-seconds\n-                                         (timeclock-day-end (car day-list)))\n-                                        base)))\n-                        (nconc (aref breaks i) (list (- span len))))\n-                      (if req\n-                          (setq len (+ len (- timeclock-workday req))))\n-                      (nconc (aref workday i) (list len)))))\n-                (setq i (1+ i))))\n-            (setq day-list (cdr day-list)))\n-          ;; average statistics\n-          (let ((i 0) (l 5))\n-            (while (< i l)\n-              (aset time-in i (timeclock-geometric-mean\n-                               (cdr (aref time-in i))))\n-              (aset time-out i (timeclock-geometric-mean\n-                                (cdr (aref time-out i))))\n-              (aset breaks i (timeclock-geometric-mean\n-                              (cdr (aref breaks i))))\n-              (aset workday i (timeclock-geometric-mean\n-                               (cdr (aref workday i))))\n-              (setq i (1+ i))))\n-          ;; Output the HTML table\n-          (insert "<tr>\\n")\n-          (insert "<td align=\\"center\\">Time in</td>\\n")\n-          (let ((i 0) (l 5))\n-            (while (< i l)\n-              (insert "<td align=\\"right\\">"\n-                      (timeclock-seconds-to-string (aref time-in i))\n-                      "</td>\\n")\n-              (setq i (1+ i))))\n-          (insert "</tr>\\n")\n-\n-          (insert "<tr>\\n")\n-          (insert "<td align=\\"center\\">Time out</td>\\n")\n-          (let ((i 0) (l 5))\n-            (while (< i l)\n-              (insert "<td align=\\"right\\">"\n-                      (timeclock-seconds-to-string (aref time-out i))\n-                      "</td>\\n")\n-              (setq i (1+ i))))\n-          (insert "</tr>\\n")\n-\n-          (insert "<tr>\\n")\n-          (insert "<td align=\\"center\\">Break</td>\\n")\n-          (let ((i 0) (l 5))\n-            (while (< i l)\n-              (insert "<td align=\\"right\\">"\n-                      (timeclock-seconds-to-string (aref breaks i))\n-                      "</td>\\n")\n-              (setq i (1+ i))))\n-          (insert "</tr>\\n")\n-\n-          (insert "<tr>\\n")\n-          (insert "<td align=\\"center\\">Workday</td>\\n")\n-          (let ((i 0) (l 5))\n-            (while (< i l)\n-              (insert "<td align=\\"right\\">"\n-                      (timeclock-seconds-to-string (aref workday i))\n-                      "</td>\\n")\n-              (setq i (1+ i))))\n-          (insert "</tr>\\n"))\n-        (insert "<tfoot>\n-<td colspan=\\"6\\" align=\\"center\\">\n-  <i>These are approximate figures</i></td>\n-</tfoot>\n-</table>\n-</td></table>")))))\n-\n-;;; A helpful little function\n-\n-(defun timeclock-visit-timelog ()\n-  "Open the file named by `timeclock-file\' in another window."\n-  (interactive)\n-  (find-file-other-window timeclock-file))\n-\n-(provide \'timeclock)\n-\n-(run-hooks \'timeclock-load-hook)\n-\n-;; make sure we know the list of reasons, projects, and have computed\n-;; the last event and current discrepancy.\n-(if (file-readable-p timeclock-file)\n-    (timeclock-reread-log))\n-\n-;;; arch-tag: a0be3377-deb6-44ec-b9a2-a7be28436a40\n-;;; timeclock.el ends here\ndiff --git a/src/account.cc b/src/account.cc\nindex 7dcd5faa..c3fc80f1 100644\n--- a/src/account.cc\n+++ b/src/account.cc\n@@ -692,12 +692,10 @@ void account_t::xdata_t::details_t::update(post_t& post,\n   }\n }\n \n-void put_account(property_tree::ptree& pt, const account_t& acct,\n+void put_account(property_tree::ptree& st, const account_t& acct,\n                  function<bool(const account_t&)> pred)\n {\n   if (pred(acct)) {\n-    property_tree::ptree& st(pt.put("account", ""));\n-\n     std::ostringstream buf;\n     buf.width(sizeof(unsigned long) * 2);\n     buf.fill(\'0\');\n@@ -709,18 +707,15 @@ void put_account(property_tree::ptree& pt, const account_t& acct,\n     st.put("fullname", acct.fullname());\n \n     value_t total = acct.amount();\n-    if (! total.is_null()) {\n-      property_tree::ptree& t(st.put("account-amount", ""));\n-      put_value(t, total);\n-    }\n+    if (! total.is_null())\n+      put_value(st.put("account-amount", ""), total);\n+\n     total = acct.total();\n-    if (! total.is_null()) {\n-      property_tree::ptree& t(st.put("account-total", ""));\n-      put_value(t, total);\n-    }\n+    if (! total.is_null())\n+      put_value(st.put("account-total", ""), total);\n \n     foreach (const accounts_map::value_type& pair, acct.accounts)\n-      put_account(st, *pair.second, pred);\n+      put_account(st.add("account", ""), *pair.second, pred);\n   }\n }\n \ndiff --git a/src/amount.cc b/src/amount.cc\nindex 88f2d124..55cbabeb 100644\n--- a/src/amount.cc\n+++ b/src/amount.cc\n@@ -1320,13 +1320,11 @@ bool amount_t::valid() const\n   return true;\n }\n \n-void put_amount(property_tree::ptree& pt, const amount_t& amt,\n-                bool wrap, bool commodity_details)\n+void put_amount(property_tree::ptree& st, const amount_t& amt,\n+                bool commodity_details)\n {\n-  property_tree::ptree& st(wrap ? pt.put("amount", "") : pt);\n-\n   if (amt.has_commodity())\n-    put_commodity(st, amt.commodity(), commodity_details);\n+    put_commodity(st.put("commodity", ""), amt.commodity(), commodity_details);\n \n   st.put("quantity", amt.quantity_string());\n }\ndiff --git a/src/amount.h b/src/amount.h\nindex b70058ff..938d4b7b 100644\n--- a/src/amount.h\n+++ b/src/amount.h\n@@ -793,7 +793,7 @@ inline std::istream& operator>>(std::istream& in, amount_t& amt) {\n }\n \n void put_amount(property_tree::ptree& pt, const amount_t& amt,\n-                bool wrap = true, bool commodity_details = false);\n+                bool commodity_details = false);\n \n } // namespace ledger\n \ndiff --git a/src/annotate.cc b/src/annotate.cc\nindex 12016868..8816a89c 100644\n--- a/src/annotate.cc\n+++ b/src/annotate.cc\n@@ -216,19 +216,13 @@ void annotation_t::print(std::ostream& out, bool keep_base,\n     out << " ((" << *value_expr << "))";\n }\n \n-void put_annotation(property_tree::ptree& pt, const annotation_t& details)\n+void put_annotation(property_tree::ptree& st, const annotation_t& details)\n {\n-  property_tree::ptree& st(pt.put("annotation", ""));\n+  if (details.price)\n+    put_amount(st.put("price", ""), *details.price);\n \n-  if (details.price) {\n-    property_tree::ptree& t(st.put("price", ""));\n-    put_amount(t, *details.price, false);\n-  }\n-\n-  if (details.date) {\n-    property_tree::ptree& t(st.put("date", ""));\n-    put_date(t, *details.date, false);\n-  }\n+  if (details.date)\n+    put_date(st.put("date", ""), *details.date);\n \n   if (details.tag)\n     st.put("tag", *details.tag);\ndiff --git a/src/balance.cc b/src/balance.cc\nindex f86d6561..b702cb7b 100644\n--- a/src/balance.cc\n+++ b/src/balance.cc\n@@ -336,12 +336,10 @@ void balance_t::print(std::ostream&       out,\n     amount_printer.close();\n }\n \n-void put_balance(property_tree::ptree& pt, const balance_t& bal)\n+void put_balance(property_tree::ptree& st, const balance_t& bal)\n {\n-  property_tree::ptree& st(pt.put("balance", ""));\n-\n   foreach (const balance_t::amounts_map::value_type& pair, bal.amounts)\n-    put_amount(st, pair.second);\n+    put_amount(st.add("amount", ""), pair.second);\n }\n \n } // namespace ledger\ndiff --git a/src/commodity.cc b/src/commodity.cc\nindex c7a893f1..535b31c9 100644\n--- a/src/commodity.cc\n+++ b/src/commodity.cc\n@@ -497,11 +497,9 @@ bool commodity_t::compare_by_commodity::operator()(const amount_t * left,\n   }\n }\n \n-void put_commodity(property_tree::ptree& pt, const commodity_t& comm,\n+void put_commodity(property_tree::ptree& st, const commodity_t& comm,\n                    bool commodity_details)\n {\n-  property_tree::ptree& st(pt.put("commodity", ""));\n-\n   std::string flags;\n   if (! (comm.has_flags(COMMODITY_STYLE_SUFFIXED)))  flags += \'P\';\n   if (comm.has_flags(COMMODITY_STYLE_SEPARATED))     flags += \'S\';\n@@ -512,7 +510,7 @@ void put_commodity(property_tree::ptree& pt, const commodity_t& comm,\n   st.put("symbol", comm.symbol());\n \n   if (commodity_details && comm.has_annotation())\n-    put_annotation(st, as_annotated_commodity(comm).details);\n+    put_annotation(st.put("annotation", ""), as_annotated_commodity(comm).details);\n }\n \n } // namespace ledger\ndiff --git a/src/item.cc b/src/item.cc\nindex 896589e9..a29a3fd3 100644\n--- a/src/item.cc\n+++ b/src/item.cc\n@@ -600,16 +600,15 @@ string item_context(const item_t& item, const string& desc)\n   return out.str();\n }\n \n-void put_metadata(property_tree::ptree& pt, const item_t::string_map& metadata)\n+void put_metadata(property_tree::ptree& st, const item_t::string_map& metadata)\n {\n-  property_tree::ptree& st(pt.put("metadata", ""));\n   foreach (const item_t::string_map::value_type& pair, metadata) {\n     if (pair.second.first) {\n-      property_tree::ptree& vt(st.put("pair", ""));\n-      vt.put("key", pair.first);\n+      property_tree::ptree& vt(st.add("value", ""));\n+      vt.put("<xmlattr>.key", pair.first);\n       put_value(vt, *pair.second.first);\n     } else {\n-      st.put("tag", pair.first);\n+      st.add("tag", pair.first);\n     }\n   }\n }\ndiff --git a/src/mask.h b/src/mask.h\nindex d46b3349..877a0011 100644\n--- a/src/mask.h\n+++ b/src/mask.h\n@@ -151,7 +151,7 @@ inline std::ostream& operator<<(std::ostream& out, const mask_t& mask) {\n }\n \n inline void put_mask(property_tree::ptree& pt, const mask_t& mask) {\n-  pt.put("mask", mask.str());\n+  pt.put_value(mask.str());\n }\n \n } // namespace ledger\ndiff --git a/src/post.cc b/src/post.cc\nindex c7435aec..1a24429b 100644\n--- a/src/post.cc\n+++ b/src/post.cc\n@@ -696,10 +696,8 @@ void extend_post(post_t& post, journal_t& journal)\n   }\n }\n \n-void put_post(property_tree::ptree& pt, const post_t& post)\n+void put_post(property_tree::ptree& st, const post_t& post)\n {\n-  property_tree::ptree& st(pt.put("posting", ""));\n-\n   if (post.state() == item_t::CLEARED)\n     st.put("<xmlattr>.state", "cleared");\n   else if (post.state() == item_t::PENDING)\n@@ -710,14 +708,10 @@ void put_post(property_tree::ptree& pt, const post_t& post)\n   if (post.has_flags(ITEM_GENERATED))\n     st.put("<xmlattr>.generated", "true");\n \n-  if (post._date) {\n-    property_tree::ptree& t(st.put("date", ""));\n-    put_date(t, *post._date, false);\n-  }\n-  if (post._date_aux) {\n-    property_tree::ptree& t(st.put("aux-date", ""));\n-    put_date(t, *post._date_aux, false);\n-  }\n+  if (post._date)\n+    put_date(st.put("date", ""), *post._date);\n+  if (post._date_aux)\n+    put_date(st.put("aux-date", ""), *post._date_aux);\n \n   if (post.account) {\n     property_tree::ptree& t(st.put("account", ""));\n@@ -736,34 +730,27 @@ void put_post(property_tree::ptree& pt, const post_t& post)\n     if (post.has_xdata() && post.xdata().has_flags(POST_EXT_COMPOUND))\n       put_value(t, post.xdata().compound_value);\n     else\n-      put_amount(t, post.amount);\n+      put_amount(t.put("amount", ""), post.amount);\n   }\n \n-  if (post.cost) {\n-    property_tree::ptree& t(st.put("cost", ""));\n-    put_amount(t, *post.cost, false);\n-  }\n+  if (post.cost)\n+    put_amount(st.put("cost", ""), *post.cost);\n \n   if (post.assigned_amount) {\n-    if (post.has_flags(POST_CALCULATED)) {\n-      property_tree::ptree& t(st.put("balance-assertion", ""));\n-      put_amount(t, *post.assigned_amount, false);\n-    } else {\n-      property_tree::ptree& t(st.put("balance-assignment", ""));\n-      put_amount(t, *post.assigned_amount, false);\n-    }\n+    if (post.has_flags(POST_CALCULATED))\n+      put_amount(st.put("balance-assertion", ""), *post.assigned_amount);\n+    else\n+      put_amount(st.put("balance-assignment", ""), *post.assigned_amount);\n   }\n \n   if (post.note)\n     st.put("note", *post.note);\n \n   if (post.metadata)\n-    put_metadata(st,  *post.metadata);\n+    put_metadata(st.put("metadata", ""),  *post.metadata);\n \n-  if (post.xdata_ && ! post.xdata_->total.is_null()) {\n-    property_tree::ptree& t(st.put("total", ""));\n-    put_value(t, post.xdata_->total);\n-  }\n+  if (post.xdata_ && ! post.xdata_->total.is_null())\n+    put_value(st.put("total", ""), post.xdata_->total);\n }\n \n } // namespace ledger\ndiff --git a/src/ptree.cc b/src/ptree.cc\nindex 1e271465..e7afdcd1 100644\n--- a/src/ptree.cc\n+++ b/src/ptree.cc\n@@ -61,28 +61,28 @@ void format_ptree::flush()\n \n   property_tree::ptree& ct(pt.put("ledger.commodities", ""));\n   foreach (const commodities_pair& pair, commodities)\n-    put_commodity(ct, *pair.second, true);\n+    put_commodity(ct.add("commodity", ""), *pair.second, true);\n \n   property_tree::ptree& at(pt.put("ledger.accounts", ""));\n-  put_account(at, *report.session.journal->master, account_visited_p);\n+  put_account(at.add("account", ""), *report.session.journal->master, account_visited_p);\n \n   property_tree::ptree& tt(pt.put("ledger.transactions", ""));\n   foreach (const xact_t * xact, transactions) {\n-    put_xact(tt, *xact);\n+    property_tree::ptree& t(tt.add("transaction", ""));\n+    put_xact(t, *xact);\n \n-    property_tree::ptree& post_tree(tt.put("postings", ""));\n+    property_tree::ptree& post_tree(t.put("postings", ""));\n     foreach (const post_t * post, xact->posts)\n       if (post->has_xdata() &&\n           post->xdata().has_flags(POST_EXT_VISITED))\n-        put_post(post_tree, *post);\n+        put_post(post_tree.add("posting", ""), *post);\n   }\n \n   switch (format) {\n   case FORMAT_XML:\n-    property_tree::write_xml(out, pt);\n-    break;\n-  case FORMAT_JSON:\n-    property_tree::write_json(out, pt);\n+    property_tree::xml_writer_settings<char> indented(\' \', 2);\n+    property_tree::write_xml(out, pt, indented);\n+    out << std::endl;\n     break;\n   }\n }\ndiff --git a/src/ptree.h b/src/ptree.h\nindex ac9e1060..154c8837 100644\n--- a/src/ptree.h\n+++ b/src/ptree.h\n@@ -75,8 +75,7 @@ protected:\n \n public:\n   enum format_t {\n-    FORMAT_XML,\n-    FORMAT_JSON\n+    FORMAT_XML\n   } format;\n \n   format_ptree(report_t& _report, format_t _format = FORMAT_XML)\ndiff --git a/src/report.cc b/src/report.cc\nindex 8bb38fb6..29077f10 100644\n--- a/src/report.cc\n+++ b/src/report.cc\n@@ -1628,11 +1628,6 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,\n       }\n       break;\n \n-    case \'j\':\n-      if (is_eq(p, "json"))\n-        return POSTS_REPORTER(new format_ptree(*this,\n-                                               format_ptree::FORMAT_JSON));\n-      break;\n     case \'l\':\n       if (is_eq(p, "lisp"))\n         return POSTS_REPORTER(new format_emacs_posts(output_stream));\ndiff --git a/src/system.hh.in b/src/system.hh.in\nindex 08a45a15..ff7ea75b 100644\n--- a/src/system.hh.in\n+++ b/src/system.hh.in\n@@ -204,7 +204,6 @@ typedef std::ostream::pos_type ostream_pos_type;\n \n #include <boost/property_tree/ptree.hpp>\n #include <boost/property_tree/xml_parser.hpp>\n-#include <boost/property_tree/json_parser.hpp>\n \n #include <boost/random/mersenne_twister.hpp>\n #include <boost/random/uniform_int.hpp>\ndiff --git a/src/times.h b/src/times.h\nindex ae4e4c75..4b89cc0c 100644\n--- a/src/times.h\n+++ b/src/times.h\n@@ -111,20 +111,12 @@ std::string format_date(const date_t& when,\n void set_date_format(const char * format);\n void set_input_date_format(const char * format);\n \n-inline void put_datetime(property_tree::ptree& pt, const datetime_t& when,\n-                         bool wrap = true) {\n-  if (wrap)\n-    pt.put("datetime", format_datetime(when, FMT_WRITTEN));\n-  else\n-    pt.put_value(format_datetime(when, FMT_WRITTEN));\n+inline void put_datetime(property_tree::ptree& pt, const datetime_t& when) {\n+  pt.put_value(format_datetime(when, FMT_WRITTEN));\n }\n \n-inline void put_date(property_tree::ptree& pt, const date_t& when,\n-                      bool wrap = true) {\n-  if (wrap)\n-    pt.put("date", format_date(when, FMT_WRITTEN));\n-  else\n-    pt.put_value(format_date(when, FMT_WRITTEN));\n+inline void put_date(property_tree::ptree& pt, const date_t& when) {\n+  pt.put_value(format_date(when, FMT_WRITTEN));\n }\n \n struct date_traits_t\ndiff --git a/src/value.cc b/src/value.cc\nindex e8afac50..98e48c2f 100644\n--- a/src/value.cc\n+++ b/src/value.cc\n@@ -2062,35 +2062,35 @@ void put_value(property_tree::ptree& pt, const value_t& value)\n {\n   switch (value.type()) {\n   case value_t::VOID:\n-    pt.put("void", "");\n+    pt.add("void", "");\n     break;\n   case value_t::BOOLEAN:\n-    pt.put("bool", value.as_boolean() ? "true" : "false");\n+    pt.add("bool", value.as_boolean() ? "true" : "false");\n     break;\n   case value_t::INTEGER:\n-    pt.put("int", value.to_string());\n+    pt.add("int", value.to_string());\n     break;\n   case value_t::AMOUNT:\n-    put_amount(pt, value.as_amount());\n+    put_amount(pt.add("amount", ""), value.as_amount());\n     break;\n   case value_t::BALANCE:\n-    put_balance(pt, value.as_balance());\n+    put_balance(pt.add("balance", ""), value.as_balance());\n     break;\n   case value_t::DATETIME:\n-    put_datetime(pt, value.as_datetime());\n+    put_datetime(pt.add("datetime", ""), value.as_datetime());\n     break;\n   case value_t::DATE:\n-    put_date(pt, value.as_date());\n+    put_date(pt.add("date", ""), value.as_date());\n     break;\n   case value_t::STRING:\n-    pt.put("string", value.as_string());\n+    pt.add("string", value.as_string());\n     break;\n   case value_t::MASK:\n-    put_mask(pt, value.as_mask());\n+    put_mask(pt.add("mask", ""), value.as_mask());\n     break;\n \n   case value_t::SEQUENCE: {\n-    property_tree::ptree& st(pt.put("sequence", ""));\n+    property_tree::ptree& st(pt.add("sequence", ""));\n     foreach (const value_t& member, value.as_sequence())\n       put_value(st, member);\n     break;\ndiff --git a/src/xact.cc b/src/xact.cc\nindex d33520b4..8aecf5d6 100644\n--- a/src/xact.cc\n+++ b/src/xact.cc\n@@ -847,10 +847,8 @@ void auto_xact_t::extend_xact(xact_base_t& xact, parse_context_t& context)\n   }\n }\n \n-void put_xact(property_tree::ptree& pt, const xact_t& xact)\n+void put_xact(property_tree::ptree& st, const xact_t& xact)\n {\n-  property_tree::ptree& st(pt.put("transaction", ""));\n-\n   if (xact.state() == item_t::CLEARED)\n     st.put("<xmlattr>.state", "cleared");\n   else if (xact.state() == item_t::PENDING)\n@@ -859,14 +857,10 @@ void put_xact(property_tree::ptree& pt, const xact_t& xact)\n   if (xact.has_flags(ITEM_GENERATED))\n     st.put("<xmlattr>.generated", "true");\n \n-  if (xact._date) {\n-    property_tree::ptree& t(st.put("date", ""));\n-    put_date(t, *xact._date, false);\n-  }\n-  if (xact._date_aux) {\n-    property_tree::ptree& t(st.put("aux-date", ""));\n-    put_date(t, *xact._date_aux, false);\n-  }\n+  if (xact._date)\n+    put_date(st.put("date", ""), *xact._date);\n+  if (xact._date_aux)\n+    put_date(st.put("aux-date", ""), *xact._date_aux);\n \n   if (xact.code)\n     st.put("code", *xact.code);\n@@ -877,7 +871,7 @@ void put_xact(property_tree::ptree& pt, const xact_t& xact)\n     st.put("note", *xact.note);\n \n   if (xact.metadata)\n-    put_metadata(st,  *xact.metadata);\n+    put_metadata(st.put("metadata", ""),  *xact.metadata);\n }\n \n } // namespace ledger\n', 'commit_subject': "Merge branch 'next' into ledger-mode-automatic-transactions", 'repo_name': 'ledger/ledger', 'committer': {'name': 'Craig Earls', 'email': 'ee4f8a2f94e72300b4c5aec6797db88cfde04cdd@gmail.com', 'time_sec': '1362850028', 'tz_offset': '-300', 'date': {'seconds': '1362850028'}}, 'commit_tree': 'bf0dd4586adb4b7df1adad8a5eff5d2bca36f4e3', 'commit_message': "Merge branch 'next' into ledger-mode-automatic-transactions\n", 'language_name': 'C', 'license': 'bsd-3-clause', 'hunks': '@@ -847,10 +847,8 @@ void auto_xact_t::extend_xact(xact_base_t& xact, parse_context_t& context)\n   }\n }\n \n-void put_xact(property_tree::ptree& pt, const xact_t& xact)\n+void put_xact(property_tree::ptree& st, const xact_t& xact)\n {\n-  property_tree::ptree& st(pt.put("transaction", ""));\n-\n   if (xact.state() == item_t::CLEARED)\n     st.put("<xmlattr>.state", "cleared");\n   else if (xact.state() == item_t::PENDING)\n@@ -859,14 +857,10 @@ void put_xact(property_tree::ptree& pt, const xact_t& xact)\n   if (xact.has_flags(ITEM_GENERATED))\n     st.put("<xmlattr>.generated", "true");\n \n-  if (xact._date) {\n-    property_tree::ptree& t(st.put("date", ""));\n-    put_date(t, *xact._date, false);\n-  }\n-  if (xact._date_aux) {\n-    property_tree::ptree& t(st.put("aux-date", ""));\n-    put_date(t, *xact._date_aux, false);\n-  }\n+  if (xact._date)\n+    put_date(st.put("date", ""), *xact._date);\n+  if (xact._date_aux)\n+    put_date(st.put("aux-date", ""), *xact._date_aux);\n \n   if (xact.code)\n     st.put("code", *xact.code);\n@@ -877,7 +871,7 @@ void put_xact(property_tree::ptree& pt, const xact_t& xact)\n     st.put("note", *xact.note);\n \n   if (xact.metadata)\n-    put_metadata(st,  *xact.metadata);\n+    put_metadata(st.put("metadata", ""),  *xact.metadata);\n }\n \n } // namespace ledger\n', 'addition_count': 6, 'deletion_count': 12, 'src_file': 'a/src/xact.cc', 'tgt_file': 'b/src/xact.cc', 'file_extension': '.cc', 'before_file': '/*\n * Copyright (c) 2003-2022, John Wiegley.  All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are\n * met:\n *\n * - Redistributions of source code must retain the above copyright\n *   notice, this list of conditions and the following disclaimer.\n *\n * - Redistributions in binary form must reproduce the above copyright\n *   notice, this list of conditions and the following disclaimer in the\n *   documentation and/or other materials provided with the distribution.\n *\n * - Neither the name of New Artisans LLC nor the names of its\n *   contributors may be used to endorse or promote products derived from\n *   this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#include <system.hh>\n\n#include "xact.h"\n#include "post.h"\n#include "account.h"\n#include "journal.h"\n#include "context.h"\n#include "format.h"\n#include "pool.h"\n\nnamespace ledger {\n\nxact_base_t::xact_base_t(const xact_base_t& xact_base)\n  : item_t(xact_base), journal(xact_base.journal)\n{\n  TRACE_CTOR(xact_base_t, "copy");\n}\n\nxact_base_t::~xact_base_t()\n{\n  TRACE_DTOR(xact_base_t);\n\n  if (! has_flags(ITEM_TEMP)) {\n    foreach (post_t * post, posts) {\n      // If the posting is a temporary, it will be destructed when the\n      // temporary is.\n      assert(! post->has_flags(ITEM_TEMP));\n\n      if (post->account)\n        post->account->remove_post(post);\n      checked_delete(post);\n    }\n  }\n}\n\nvoid xact_base_t::add_post(post_t * post)\n{\n#if !NO_ASSERTS\n  // You can add temporary postings to transactions, but not real postings to\n  // temporary transactions.\n  if (! post->has_flags(ITEM_TEMP))\n    assert(! has_flags(ITEM_TEMP));\n#endif\n\n  posts.push_back(post);\n}\n\nbool xact_base_t::remove_post(post_t * post)\n{\n  posts.remove(post);\n  post->xact = NULL;\n  return true;\n}\n\nbool xact_base_t::has_xdata()\n{\n  foreach (post_t * post, posts)\n    if (post->has_xdata())\n      return true;\n\n  return false;\n}\n\nvoid xact_base_t::clear_xdata()\n{\n  foreach (post_t * post, posts)\n    if (! post->has_flags(ITEM_TEMP))\n      post->clear_xdata();\n}\n\nvalue_t xact_base_t::magnitude() const\n{\n  value_t halfbal = 0L;\n  foreach (const post_t * post, posts) {\n    if (post->amount.sign() > 0) {\n      if (post->cost)\n        halfbal += *post->cost;\n      else\n        halfbal += post->amount;\n    }\n  }\n  return halfbal;\n}\n\nnamespace {\n  inline bool account_ends_with_special_char(const string& name) {\n    string::size_type len(name.length());\n    return (std::isdigit(name[len - 1]) || name[len - 1] == \')\' ||\n            name[len - 1] == \'}\' || name[len - 1] == \']\');\n  }\n\n  struct add_balancing_post\n  {\n    bool         first;\n    xact_base_t& xact;\n    post_t *     null_post;\n\n    explicit add_balancing_post(xact_base_t& _xact, post_t * _null_post)\n      : first(true), xact(_xact), null_post(_null_post) {\n      TRACE_CTOR(add_balancing_post, "xact_base_t&, post_t *");\n    }\n    add_balancing_post(const add_balancing_post& other)\n      : first(other.first), xact(other.xact), null_post(other.null_post) {\n      TRACE_CTOR(add_balancing_post, "copy");\n    }\n    ~add_balancing_post() throw() {\n      TRACE_DTOR(add_balancing_post);\n    }\n\n    void operator()(const amount_t& amount) {\n      if (first) {\n        null_post->amount = amount.negated();\n        null_post->add_flags(POST_CALCULATED);\n        first = false;\n      } else {\n        unique_ptr<post_t> p(new post_t(null_post->account, amount.negated(),\n                                        null_post->flags() | ITEM_GENERATED | POST_CALCULATED));\n        p->set_state(null_post->state());\n        xact.add_post(p.release());\n      }\n    }\n  };\n}\n\nbool xact_base_t::finalize()\n{\n  // Scan through and compute the total balance for the xact.  This is used\n  // for auto-calculating the value of xacts with no cost, and the per-unit\n  // price of unpriced commodities.\n\n  value_t  balance;\n  post_t * null_post = NULL;\n\n  foreach (post_t * post, posts) {\n    if (! post->must_balance())\n      continue;\n\n    amount_t& p(post->cost ? *post->cost : post->amount);\n    if (! p.is_null()) {\n      DEBUG("xact.finalize", "post must balance = " << p.reduced());\n      // If the amount was a cost, it very likely has the\n      // "keep_precision" flag set, meaning commodity display precision\n      // is ignored when displaying the amount.  We never want this set\n      // for the balance, so we must clear the flag in a temporary to\n      // avoid it propagating into the balance.\n      add_or_set_value(balance, p.keep_precision() ?\n                       p.rounded().reduced() : p.reduced());\n    }\n    else if (null_post) {\n      bool post_account_bad =\n        account_ends_with_special_char(post->account->fullname());\n      bool null_post_account_bad =\n        account_ends_with_special_char(null_post->account->fullname());\n\n      if (post_account_bad || null_post_account_bad)\n        throw_(std::logic_error,\n               _f("Posting with null amount\'s account may be misspelled:\\n  \\"%1%\\"")\n               % (post_account_bad ? post->account->fullname() :\n                   null_post->account->fullname()));\n      else\n        throw_(std::logic_error,\n               _("Only one posting with null amount allowed per transaction"));\n    }\n    else {\n      null_post = post;\n    }\n  }\n  VERIFY(balance.valid());\n\n#if DEBUG_ON\n  DEBUG("xact.finalize", "initial balance = " << balance);\n  DEBUG("xact.finalize", "balance is " << balance.label());\n  if (balance.is_balance())\n    DEBUG("xact.finalize", "balance commodity count = "\n          << balance.as_balance().amounts.size());\n#endif\n\n  // If there is only one post, balance against the default account if one has\n  // been set.\n\n  if (journal && journal->bucket && posts.size() == 1 && ! balance.is_null()) {\n    null_post = new post_t(journal->bucket, ITEM_GENERATED);\n    null_post->_state = (*posts.begin())->_state;\n    add_post(null_post);\n  }\n\n  if (! null_post && balance.is_balance() &&\n      balance.as_balance().amounts.size() == 2) {\n    // When an xact involves two different commodities (regardless of how\n    // many posts there are) determine the conversion ratio by dividing the\n    // total value of one commodity by the total value of the other.  This\n    // establishes the per-unit cost for this post for both commodities.\n\n    DEBUG("xact.finalize",\n          "there were exactly two commodities, and no null post");\n\n    bool     saw_cost = false;\n    post_t * top_post = NULL;\n\n    foreach (post_t * post, posts) {\n      if (! post->amount.is_null() && post->must_balance()) {\n        if (post->amount.has_annotation())\n          top_post = post;\n        else if (! top_post)\n          top_post = post;\n      }\n\n      if (post->cost && ! post->has_flags(POST_COST_CALCULATED)) {\n        saw_cost = true;\n        break;\n      }\n    }\n\n    if (! saw_cost && top_post) {\n      const balance_t& bal(balance.as_balance());\n\n      DEBUG("xact.finalize", "there were no costs, and a valid top_post");\n\n      balance_t::amounts_map::const_iterator a = bal.amounts.begin();\n\n      const amount_t * x = &(*a++).second;\n      const amount_t * y = &(*a++).second;\n\n      if (*x && *y) {\n        if (x->commodity() != top_post->amount.commodity())\n          std::swap(x, y);\n\n        DEBUG("xact.finalize", "primary   amount = " << *x);\n        DEBUG("xact.finalize", "secondary amount = " << *y);\n\n        commodity_t& comm(x->commodity());\n        amount_t     per_unit_cost = (*y / *x).abs().unrounded();\n\n        DEBUG("xact.finalize", "per_unit_cost = " << per_unit_cost);\n\n        foreach (post_t * post, posts) {\n          const amount_t& amt(post->amount.reduced());\n\n          if (post->must_balance() && amt.commodity() == comm) {\n            balance -= amt;\n            post->cost = per_unit_cost * amt;\n            post->add_flags(POST_COST_CALCULATED);\n            balance += *post->cost;\n\n            DEBUG("xact.finalize", "set post->cost to = " << *post->cost);\n          }\n        }\n      }\n    }\n  }\n\n  posts_list copy(posts);\n\n  if (has_date()) {\n    foreach (post_t * post, copy) {\n      if (! post->cost)\n        continue;\n\n      if (post->amount.commodity() == post->cost->commodity())\n        throw_(balance_error,\n               _("A posting\'s cost must be of a different commodity than its amount"));\n\n      cost_breakdown_t breakdown =\n        commodity_pool_t::current_pool->exchange(\n          post->amount, *post->cost, false, ! post->has_flags(POST_COST_VIRTUAL),\n          datetime_t(date(), time_duration(0, 0, 0, 0)));\n\n      if (post->amount.has_annotation() && post->amount.annotation().price) {\n        if (breakdown.basis_cost.commodity() == breakdown.final_cost.commodity()) {\n          DEBUG("xact.finalize", "breakdown.basis_cost = " << breakdown.basis_cost);\n          DEBUG("xact.finalize", "breakdown.final_cost = " << breakdown.final_cost);\n          if (amount_t gain_loss = breakdown.basis_cost - breakdown.final_cost) {\n            DEBUG("xact.finalize", "gain_loss = " << gain_loss);\n            gain_loss.in_place_round();\n            DEBUG("xact.finalize", "gain_loss rounds to = " << gain_loss);\n            if (post->must_balance())\n              add_or_set_value(balance, gain_loss.reduced());\n#if 0\n            account_t * account;\n            if (gain_loss.sign() > 0)\n              account = journal->find_account(_("Equity:Capital Gains"));\n            else\n              account = journal->find_account(_("Equity:Capital Losses"));\n\n            post_t * p = new post_t(account, gain_loss, ITEM_GENERATED);\n            p->set_state(post->state());\n            if (post->has_flags(POST_VIRTUAL)) {\n              DEBUG("xact.finalize", "gain_loss came from a virtual post");\n              p->add_flags(post->flags() & (POST_VIRTUAL | POST_MUST_BALANCE));\n            }\n            add_post(p);\n#else\n            *post->cost += gain_loss;\n#endif\n            DEBUG("xact.finalize", "added gain_loss, balance = " << balance);\n          } else {\n            DEBUG("xact.finalize", "gain_loss would have displayed as zero");\n          }\n        }\n      } else {\n        post->amount =\n          breakdown.amount.has_annotation() ?\n          amount_t(breakdown.amount,\n                   annotation_t(breakdown.amount.annotation().price,\n                                breakdown.amount.annotation().date,\n                                post->amount.has_annotation() ?\n                                post->amount.annotation().tag :\n                                breakdown.amount.annotation().tag,\n                                breakdown.amount.annotation().value_expr)) :\n          breakdown.amount;\n        DEBUG("xact.finalize", "added breakdown, balance = " << balance);\n      }\n\n      if (post->has_flags(POST_COST_FIXATED) &&\n          post->amount.has_annotation() && post->amount.annotation().price) {\n        DEBUG("xact.finalize", "fixating annotation price");\n        post->amount.annotation().add_flags(ANNOTATION_PRICE_FIXATED);\n      }\n    }\n  }\n\n  if (null_post != NULL) {\n    // If one post has no value at all, its value will become the inverse of\n    // the rest.  If multiple commodities are involved, multiple posts are\n    // generated to balance them all.\n\n    DEBUG("xact.finalize", "there was a null posting");\n    add_balancing_post post_adder(*this, null_post);\n\n    if (balance.is_balance())\n      balance.as_balance_lval().map_sorted_amounts(post_adder);\n    else if (balance.is_amount())\n      post_adder(balance.as_amount_lval());\n    else if (balance.is_long())\n      post_adder(balance.to_amount());\n    else if (! balance.is_null() && ! balance.is_realzero())\n      throw_(balance_error, _("Transaction does not balance"));\n\n    balance = NULL_VALUE;\n\n  }\n  DEBUG("xact.finalize", "resolved balance = " << balance);\n\n  if (! balance.is_null() && ! balance.is_zero()) {\n    add_error_context(item_context(*this, _("While balancing transaction")));\n    add_error_context(_("Unbalanced remainder is:"));\n    add_error_context(value_context(balance));\n    add_error_context(_("Amount to balance against:"));\n    add_error_context(value_context(magnitude()));\n    throw_(balance_error, _("Transaction does not balance"));\n  }\n\n  // Add a pointer to each posting to their related accounts\n\n  if (dynamic_cast<xact_t *>(this)) {\n    bool all_null  = true;\n    bool some_null = false;\n\n    foreach (post_t * post, posts) {\n      assert(post->account);\n\n      if (! post->amount.is_null()) {\n        all_null = false;\n        post->amount.in_place_reduce();\n      } else {\n        some_null = true;\n      }\n\n      if (post->has_flags(POST_DEFERRED)) {\n        if (!post->amount.is_null())\n          post->account->add_deferred_post(id(), post);\n      } else {\n        post->account->add_post(post);\n      }\n\n      post->xdata().add_flags(POST_EXT_VISITED);\n      post->account->xdata().add_flags(ACCOUNT_EXT_VISITED);\n    }\n\n    if (all_null)\n      return false;             // ignore this xact completely\n    else if (some_null)\n      throw_(balance_error,\n             _("There cannot be null amounts after balancing a transaction"));\n  }\n\n  VERIFY(valid());\n\n  return true;\n}\n\nbool xact_base_t::verify()\n{\n  // Scan through and compute the total balance for the xact.\n\n  value_t  balance;\n\n  foreach (post_t * post, posts) {\n    if (! post->must_balance())\n      continue;\n\n    amount_t& p(post->cost ? *post->cost : post->amount);\n    assert(! p.is_null());\n\n    // If the amount was a cost, it very likely has the "keep_precision" flag\n    // set, meaning commodity display precision is ignored when displaying the\n    // amount.  We never want this set for the balance, so we must clear the\n    // flag in a temporary to avoid it propagating into the balance.\n    add_or_set_value(balance, p.keep_precision() ?\n                     p.rounded().reduced() : p.reduced());\n  }\n  VERIFY(balance.valid());\n\n  // Now that the post list has its final form, calculate the balance once\n  // more in terms of total cost, accounting for any possible gain/loss\n  // amounts.\n\n  foreach (post_t * post, posts) {\n    if (! post->cost)\n      continue;\n\n    if (post->amount.commodity() == post->cost->commodity())\n      throw_(amount_error,\n             _("A posting\'s cost must be of a different commodity than its amount"));\n  }\n\n  if (! balance.is_null() && ! balance.is_zero()) {\n    add_error_context(item_context(*this, _("While balancing transaction")));\n    add_error_context(_("Unbalanced remainder is:"));\n    add_error_context(value_context(balance));\n    add_error_context(_("Amount to balance against:"));\n    add_error_context(value_context(magnitude()));\n    throw_(balance_error, _("Transaction does not balance"));\n  }\n\n  VERIFY(valid());\n\n  return true;\n}\n\nxact_t::xact_t(const xact_t& e)\n  : xact_base_t(e), code(e.code), payee(e.payee)\n#if DOCUMENT_MODEL\n    , data(NULL)\n#endif\n{\n  TRACE_CTOR(xact_t, "copy");\n}\n\nvoid xact_t::add_post(post_t * post)\n{\n  post->xact = this;\n  xact_base_t::add_post(post);\n}\n\nnamespace {\n  value_t get_magnitude(xact_t& xact) {\n    return xact.magnitude();\n  }\n\n  value_t get_code(xact_t& xact) {\n    if (xact.code)\n      return string_value(*xact.code);\n    else\n      return NULL_VALUE;\n  }\n\n  value_t get_payee(xact_t& xact) {\n    return string_value(xact.payee);\n  }\n\n  template <value_t (*Func)(xact_t&)>\n  value_t get_wrapper(call_scope_t& scope) {\n    return (*Func)(find_scope<xact_t>(scope));\n  }\n\n  value_t fn_any(call_scope_t& args)\n  {\n    post_t& post(args.context<post_t>());\n    expr_t::ptr_op_t expr(args.get<expr_t::ptr_op_t>(0));\n\n    foreach (post_t * p, post.xact->posts) {\n      bind_scope_t bound_scope(args, *p);\n      if (expr->calc(bound_scope, args.locus, args.depth).to_boolean())\n        return true;\n    }\n    return false;\n  }\n\n  value_t fn_all(call_scope_t& args)\n  {\n    post_t& post(args.context<post_t>());\n    expr_t::ptr_op_t expr(args.get<expr_t::ptr_op_t>(0));\n\n    foreach (post_t * p, post.xact->posts) {\n      bind_scope_t bound_scope(args, *p);\n      if (! expr->calc(bound_scope, args.locus, args.depth).to_boolean())\n        return false;\n    }\n    return true;\n  }\n}\n\nexpr_t::ptr_op_t xact_t::lookup(const symbol_t::kind_t kind,\n                                const string& name)\n{\n  if (kind != symbol_t::FUNCTION)\n    return item_t::lookup(kind, name);\n\n  switch (name[0]) {\n  case \'a\':\n    if (name == "any")\n      return WRAP_FUNCTOR(&fn_any);\n    else if (name == "all")\n      return WRAP_FUNCTOR(&fn_all);\n    break;\n\n  case \'c\':\n    if (name == "code")\n      return WRAP_FUNCTOR(get_wrapper<&get_code>);\n    break;\n\n  case \'m\':\n    if (name == "magnitude")\n      return WRAP_FUNCTOR(get_wrapper<&get_magnitude>);\n    break;\n\n  case \'p\':\n    if (name[1] == \'\\0\' || name == "payee")\n      return WRAP_FUNCTOR(get_wrapper<&get_payee>);\n    break;\n  }\n\n  return item_t::lookup(kind, name);\n}\n\nbool xact_t::valid() const\n{\n  if (! _date) {\n    DEBUG("ledger.validate", "xact_t: ! _date");\n    return false;\n  }\n\n  foreach (post_t * post, posts)\n    if (post->xact != this || ! post->valid()) {\n      DEBUG("ledger.validate", "xact_t: post not valid");\n      return false;\n    }\n\n  return true;\n}\n\nnamespace {\n  bool post_pred(expr_t::ptr_op_t op, post_t& post)\n  {\n    switch (op->kind) {\n    case expr_t::op_t::VALUE:\n      return op->as_value().to_boolean();\n\n    case expr_t::op_t::O_MATCH:\n      if (op->left()->kind == expr_t::op_t::IDENT &&\n          op->left()->as_ident() == "account" &&\n          op->right()->kind == expr_t::op_t::VALUE &&\n          op->right()->as_value().is_mask())\n        return op->right()->as_value().as_mask()\n          .match(post.reported_account()->fullname());\n      else\n        break;\n\n    case expr_t::op_t::O_EQ:\n      return post_pred(op->left(), post) == post_pred(op->right(), post);\n\n    case expr_t::op_t::O_NOT:\n      return ! post_pred(op->left(), post);\n\n    case expr_t::op_t::O_AND:\n      return post_pred(op->left(), post) && post_pred(op->right(), post);\n\n    case expr_t::op_t::O_OR:\n      return post_pred(op->left(), post) || post_pred(op->right(), post);\n\n    case expr_t::op_t::O_QUERY:\n      if (post_pred(op->left(), post))\n        return post_pred(op->right()->left(), post);\n      else\n        return post_pred(op->right()->right(), post);\n\n    default:\n      break;\n    }\n\n    throw_(calc_error, _("Unhandled operator"));\n    return false;\n  }\n}\n\nstatic string apply_format(const string& str, scope_t& scope)\n{\n  if (contains(str, "%(")) {\n    format_t str_format(str);\n    std::ostringstream buf;\n    buf << str_format(scope);\n    return buf.str();\n  } else {\n    return str;\n  }\n}\n\nvoid auto_xact_t::extend_xact(xact_base_t& xact, parse_context_t& context)\n{\n  posts_list initial_posts(xact.posts.begin(), xact.posts.end());\n\n  try {\n\n  bool needs_further_verification = false;\n\n  foreach (post_t * initial_post, initial_posts) {\n    if (initial_post->has_flags(ITEM_GENERATED))\n      continue;\n\n    bind_scope_t bound_scope(*scope_t::default_scope, *initial_post);\n\n    bool matches_predicate = false;\n    if (try_quick_match) {\n      try {\n        bool found_memoized_result = false;\n        if (! memoized_results.empty()) {\n          std::map<string, bool>::iterator i =\n            memoized_results.find(initial_post->account->fullname());\n          if (i != memoized_results.end()) {\n            found_memoized_result = true;\n            matches_predicate = (*i).second;\n          }\n        }\n\n        // Since the majority of people who use automated transactions simply\n        // match against account names, try using a *much* faster version of\n        // the predicate evaluator.\n        if (! found_memoized_result) {\n          matches_predicate = post_pred(predicate.get_op(), *initial_post);\n          memoized_results.insert\n            (std::pair<string, bool>(initial_post->account->fullname(),\n                                     matches_predicate));\n        }\n      }\n      catch (...) {\n        DEBUG("xact.extend.fail",\n              "The quick matcher failed, going back to regular eval");\n        try_quick_match   = false;\n        matches_predicate = predicate(bound_scope);\n      }\n    } else {\n      matches_predicate = predicate(bound_scope);\n    }\n\n    if (matches_predicate) {\n      if (deferred_notes) {\n        foreach (deferred_tag_data_t& data, *deferred_notes) {\n          if (data.apply_to_post == NULL)\n            initial_post->append_note(\n              apply_format(data.tag_data, bound_scope).c_str(),\n              bound_scope, data.overwrite_existing);\n        }\n      }\n\n      if (check_exprs) {\n        foreach (expr_t::check_expr_pair& pair, *check_exprs) {\n          if (pair.second == expr_t::EXPR_GENERAL) {\n            pair.first.calc(bound_scope);\n          }\n          else if (! pair.first.calc(bound_scope).to_boolean()) {\n            if (pair.second == expr_t::EXPR_ASSERTION)\n              throw_(parse_error,\n                     _f("Transaction assertion failed: %1%") % pair.first);\n            else\n              context.warning(_f("Transaction check failed: %1%") % pair.first);\n          }\n        }\n      }\n\n      foreach (post_t * post, posts) {\n        amount_t post_amount;\n        if (post->amount.is_null()) {\n          if (! post->amount_expr)\n            throw_(amount_error,\n                   _("Automated transaction\'s posting has no amount"));\n\n          value_t result(post->amount_expr->calc(bound_scope));\n          if (result.is_long()) {\n            post_amount = result.to_amount();\n          } else {\n            if (! result.is_amount())\n              throw_(amount_error,\n                     _("Amount expressions must result in a simple amount"));\n            post_amount = result.as_amount();\n          }\n        } else {\n          post_amount = post->amount;\n        }\n\n        amount_t amt;\n        if (! post_amount.commodity())\n          amt = initial_post->amount * post_amount;\n        else\n          amt = post_amount;\n\n#if DEBUG_ON\n        IF_DEBUG("xact.extend") {\n          DEBUG("xact.extend",\n                "Initial post on line " << initial_post->pos->beg_line << ": "\n                << "amount " << initial_post->amount << " (precision "\n                << initial_post->amount.precision() << ")");\n\n          if (initial_post->amount.keep_precision())\n            DEBUG("xact.extend", "  precision is kept");\n\n          DEBUG("xact.extend",\n                "Posting on line " << post->pos->beg_line << ": "\n                << "amount " << post_amount << ", amt " << amt\n                << " (precision " << post_amount.precision()\n                << " != " << amt.precision() << ")");\n\n          if (post_amount.keep_precision())\n            DEBUG("xact.extend", "  precision is kept");\n          if (amt.keep_precision())\n            DEBUG("xact.extend", "  amt precision is kept");\n        }\n#endif // DEBUG_ON\n\n        account_t * account  = post->account;\n        string fullname = account->fullname();\n        assert(! fullname.empty());\n\n        if (contains(fullname, "$account")) {\n          fullname = regex_replace(fullname, regex("\\\\$account\\\\>"),\n                                   initial_post->account->fullname());\n          while (account->parent)\n            account = account->parent;\n          account = account->find_account(fullname);\n        }\n        else if (contains(fullname, "%(")) {\n          format_t account_name(fullname);\n          std::ostringstream buf;\n          buf << account_name(bound_scope);\n          while (account->parent)\n            account = account->parent;\n          account = account->find_account(buf.str());\n        }\n\n        // Copy over details so that the resulting post is a mirror of\n        // the automated xact\'s one.\n        post_t * new_post = new post_t(account, amt);\n        new_post->copy_details(*post);\n\n        // A Cleared transaction implies all of its automatic posting are cleared\n        // CPR 2012/10/23\n        if (xact.state() == item_t::CLEARED) {\n          DEBUG("xact.extend.cleared", "CLEARED");\n          new_post->set_state(item_t::CLEARED);\n        }\n\n        new_post->add_flags(ITEM_GENERATED);\n        new_post->account =\n          journal->register_account(account->fullname(), new_post,\n                                    journal->master);\n\n        if (deferred_notes) {\n          foreach (deferred_tag_data_t& data, *deferred_notes) {\n            if (! data.apply_to_post || data.apply_to_post == post) {\n              new_post->append_note(\n                apply_format(data.tag_data, bound_scope).c_str(),\n                bound_scope, data.overwrite_existing);\n            }\n          }\n        }\n\n        extend_post(*new_post, *journal);\n\n        xact.add_post(new_post);\n        new_post->account->add_post(new_post);\n\n        // Add flags so this post updates the account balance\n        new_post->xdata().add_flags(POST_EXT_VISITED);\n        new_post->account->xdata().add_flags(ACCOUNT_EXT_VISITED);\n\n        if (new_post->must_balance())\n          needs_further_verification = true;\n      }\n    }\n  }\n\n  if (needs_further_verification)\n    xact.verify();\n\n  }\n  catch (const std::exception&) {\n    add_error_context(item_context(*this, _("While applying automated transaction")));\n    add_error_context(item_context(xact, _("While extending transaction")));\n    throw;\n  }\n}\n\nvoid put_xact(property_tree::ptree& st, const xact_t& xact)\n{\n  if (xact.state() == item_t::CLEARED)\n    st.put("<xmlattr>.state", "cleared");\n  else if (xact.state() == item_t::PENDING)\n    st.put("<xmlattr>.state", "pending");\n\n  if (xact.has_flags(ITEM_GENERATED))\n    st.put("<xmlattr>.generated", "true");\n\n  if (xact._date)\n    put_date(st.put("date", ""), *xact._date);\n  if (xact._date_aux)\n  }\n}\n\nvoid put_xact(property_tree::ptree& pt, const xact_t& xact)\n{\n  property_tree::ptree& st(pt.put("transaction", ""));\n\n  if (xact.state() == item_t::CLEARED)\n    st.put("<xmlattr>.state", "cleared");\n  else if (xact.state() == item_t::PENDING)\n    st.put("note", *xact.note);\n\n  if (xact.has_flags(ITEM_GENERATED))\n    st.put("<xmlattr>.generated", "true");\n\n  if (xact._date) {\n    property_tree::ptree& t(st.put("date", ""));\n    put_date(t, *xact._date, false);\n  }\n  if (xact._date_aux) {\n    property_tree::ptree& t(st.put("aux-date", ""));\n    put_date(t, *xact._date_aux, false);\n  }\n\n  if (xact.code)\n    st.put("code", *xact.code);\n    st.put("note", *xact.note);\n\n  if (xact.metadata)\n    put_metadata(st,  *xact.metadata);\n}\n\n} // namespace ledger\n', 'message': "Merge branch 'next' into ledger-mode-automatic-transactions\n", 'author_name': 'Craig Earls', 'lang': 'cc'}