ledger/src/journal.cc
2013-05-19 02:14:27 -05:00

482 lines
14 KiB
C++

/*
* Copyright (c) 2003-2013, 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 "journal.h"
#include "context.h"
#include "amount.h"
#include "commodity.h"
#include "pool.h"
#include "xact.h"
#include "post.h"
#include "account.h"
namespace ledger {
journal_t::journal_t()
{
initialize();
TRACE_CTOR(journal_t, "");
}
#if 0
journal_t::journal_t(const path& pathname)
{
initialize();
read(pathname);
TRACE_CTOR(journal_t, "path");
}
journal_t::journal_t(const string& str)
{
initialize();
read(str);
TRACE_CTOR(journal_t, "string");
}
#endif
journal_t::~journal_t()
{
TRACE_DTOR(journal_t);
// Don't bother unhooking each xact's posts from the accounts they refer to,
// because all accounts are about to be deleted.
foreach (xact_t * xact, xacts)
checked_delete(xact);
foreach (auto_xact_t * xact, auto_xacts)
checked_delete(xact);
foreach (period_xact_t * xact, period_xacts)
checked_delete(xact);
checked_delete(master);
}
void journal_t::initialize()
{
master = new account_t;
bucket = NULL;
fixed_accounts = false;
fixed_payees = false;
fixed_commodities = false;
fixed_metadata = false;
current_context = NULL;
was_loaded = false;
force_checking = false;
check_payees = false;
day_break = false;
checking_style = CHECK_PERMISSIVE;
}
void journal_t::add_account(account_t * acct)
{
master->add_account(acct);
}
bool journal_t::remove_account(account_t * acct)
{
return master->remove_account(acct);
}
account_t * journal_t::find_account(const string& name, bool auto_create)
{
return master->find_account(name, auto_create);
}
account_t * journal_t::find_account_re(const string& regexp)
{
return master->find_account_re(regexp);
}
account_t * journal_t::register_account(const string& name, post_t * post,
account_t * master_account)
{
account_t * result = NULL;
// If there any account aliases, substitute before creating an account
// object.
if (account_aliases.size() > 0) {
accounts_map::const_iterator i = account_aliases.find(name);
if (i != account_aliases.end()) {
result = (*i).second;
} else {
// only check the very first account for alias expansion, in case
// that can be expanded successfully
size_t colon = name.find(':');
if(colon != string::npos) {
accounts_map::const_iterator j = account_aliases.find(name.substr(0, colon));
if (j != account_aliases.end()) {
result = find_account((*j).second->fullname() + name.substr(colon));
}
}
}
}
// Create the account object and associate it with the journal; this
// is registering the account.
if (! result)
result = master_account->find_account(name);
// If the account name being registered is "Unknown", check whether
// the payee indicates an account that should be used.
if (result->name == _("Unknown")) {
foreach (account_mapping_t& value, payees_for_unknown_accounts) {
if (value.first.match(post->xact->payee)) {
result = value.second;
break;
}
}
}
// Now that we have an account, make certain that the account is
// "known", if the user has requested validation of that fact.
if (checking_style == CHECK_WARNING || checking_style == CHECK_ERROR) {
if (! result->has_flags(ACCOUNT_KNOWN)) {
if (! post) {
if (force_checking)
fixed_accounts = true;
result->add_flags(ACCOUNT_KNOWN);
}
else if (! fixed_accounts && post->_state != item_t::UNCLEARED) {
result->add_flags(ACCOUNT_KNOWN);
}
else if (checking_style == CHECK_WARNING) {
current_context->warning(_f("Unknown account '%1%'") % result->fullname());
}
else if (checking_style == CHECK_ERROR) {
throw_(parse_error, _f("Unknown account '%1%'") % result->fullname());
}
}
}
return result;
}
string journal_t::register_payee(const string& name, xact_t * xact)
{
string payee;
if (check_payees &&
(checking_style == CHECK_WARNING || checking_style == CHECK_ERROR)) {
std::set<string>::iterator i = known_payees.find(name);
if (i == known_payees.end()) {
if (! xact) {
if (force_checking)
fixed_payees = true;
known_payees.insert(name);
}
else if (! fixed_payees && xact->_state != item_t::UNCLEARED) {
known_payees.insert(name);
}
else if (checking_style == CHECK_WARNING) {
current_context->warning(_f("Unknown payee '%1%'") % name);
}
else if (checking_style == CHECK_ERROR) {
throw_(parse_error, _f("Unknown payee '%1%'") % name);
}
}
}
foreach (payee_mapping_t& value, payee_mappings) {
if (value.first.match(name)) {
payee = value.second;
break;
}
}
return payee.empty() ? name : payee;
}
void journal_t::register_commodity(commodity_t& comm,
variant<int, xact_t *, post_t *> context)
{
if (checking_style == CHECK_WARNING || checking_style == CHECK_ERROR) {
if (! comm.has_flags(COMMODITY_KNOWN)) {
if (context.which() == 0) {
if (force_checking)
fixed_commodities = true;
comm.add_flags(COMMODITY_KNOWN);
}
else if (! fixed_commodities &&
((context.which() == 1 &&
boost::get<xact_t *>(context)->_state != item_t::UNCLEARED) ||
(context.which() == 2 &&
boost::get<post_t *>(context)->_state != item_t::UNCLEARED))) {
comm.add_flags(COMMODITY_KNOWN);
}
else if (checking_style == CHECK_WARNING) {
current_context->warning(_f("Unknown commodity '%1%'") % comm);
}
else if (checking_style == CHECK_ERROR) {
throw_(parse_error, _f("Unknown commodity '%1%'") % comm);
}
}
}
}
void journal_t::register_metadata(const string& key, const value_t& value,
variant<int, xact_t *, post_t *> context)
{
if (checking_style == CHECK_WARNING || checking_style == CHECK_ERROR) {
std::set<string>::iterator i = known_tags.find(key);
if (i == known_tags.end()) {
if (context.which() == 0) {
if (force_checking)
fixed_metadata = true;
known_tags.insert(key);
}
else if (! fixed_metadata &&
((context.which() == 1 &&
boost::get<xact_t *>(context)->_state != item_t::UNCLEARED) ||
(context.which() == 2 &&
boost::get<post_t *>(context)->_state != item_t::UNCLEARED))) {
known_tags.insert(key);
}
else if (checking_style == CHECK_WARNING) {
current_context->warning(_f("Unknown metadata tag '%1%'") % key);
}
else if (checking_style == CHECK_ERROR) {
throw_(parse_error, _f("Unknown metadata tag '%1%'") % key);
}
}
}
if (! value.is_null()) {
std::pair<tag_check_exprs_map::iterator,
tag_check_exprs_map::iterator> range =
tag_check_exprs.equal_range(key);
for (tag_check_exprs_map::iterator i = range.first;
i != range.second;
++i) {
bind_scope_t bound_scope
(*current_context->scope,
context.which() == 1 ?
static_cast<scope_t&>(*boost::get<xact_t *>(context)) :
static_cast<scope_t&>(*boost::get<post_t *>(context)));
value_scope_t val_scope(bound_scope, value);
if (! (*i).second.first.calc(val_scope).to_boolean()) {
if ((*i).second.second == expr_t::EXPR_ASSERTION)
throw_(parse_error,
_f("Metadata assertion failed for (%1%: %2%): %3%")
% key % value % (*i).second.first);
else
current_context->warning
(_f("Metadata check failed for (%1%: %2%): %3%")
% key % value % (*i).second.first);
}
}
}
}
namespace {
void check_all_metadata(journal_t& journal,
variant<int, xact_t *, post_t *> context)
{
xact_t * xact = context.which() == 1 ? boost::get<xact_t *>(context) : NULL;
post_t * post = context.which() == 2 ? boost::get<post_t *>(context) : NULL;
if ((xact || post) && xact ? xact->metadata : post->metadata) {
foreach (const item_t::string_map::value_type& pair,
xact ? *xact->metadata : *post->metadata) {
const string& key(pair.first);
if (optional<value_t> value = pair.second.first)
journal.register_metadata(key, *value, context);
else
journal.register_metadata(key, NULL_VALUE, context);
}
}
}
}
bool journal_t::add_xact(xact_t * xact)
{
xact->journal = this;
if (! xact->finalize()) {
xact->journal = NULL;
return false;
}
extend_xact(xact);
check_all_metadata(*this, xact);
foreach (post_t * post, xact->posts) {
extend_post(*post, *this);
check_all_metadata(*this, post);
}
// If a transaction with this UUID has already been seen, simply do
// not add this one to the journal. However, all automated checks
// will have been performed by extend_xact, so asserts can still be
// applied to it.
if (optional<value_t> ref = xact->get_tag(_("UUID"))) {
std::pair<checksum_map_t::iterator, bool> result
= checksum_map.insert(checksum_map_t::value_type(ref->to_string(), xact));
if (! result.second) {
// jww (2012-02-27): Confirm that the xact in
// (*result.first).second is exact match in its significant
// details to xact.
xact->journal = NULL;
return false;
}
}
xacts.push_back(xact);
return true;
}
void journal_t::extend_xact(xact_base_t * xact)
{
foreach (auto_xact_t * auto_xact, auto_xacts)
auto_xact->extend_xact(*xact, *current_context);
}
bool journal_t::remove_xact(xact_t * xact)
{
bool found = false;
xacts_list::iterator i;
for (i = xacts.begin(); i != xacts.end(); i++)
if (*i == xact) {
found = true;
break;
}
if (! found)
return false;
xacts.erase(i);
xact->journal = NULL;
return true;
}
std::size_t journal_t::read(parse_context_stack_t& context)
{
std::size_t count = 0;
try {
parse_context_t& current(context.get_current());
current_context = &current;
current.count = 0;
if (! current.scope)
current.scope = scope_t::default_scope;
if (! current.scope)
throw_(std::runtime_error,
_f("No default scope in which to read journal file '%1%'")
% current.pathname);
if (! current.master)
current.master = master;
count = read_textual(context);
if (count > 0) {
if (! current.pathname.empty())
sources.push_back(fileinfo_t(current.pathname));
else
sources.push_back(fileinfo_t());
}
}
catch (...) {
clear_xdata();
current_context = NULL;
throw;
}
// xdata may have been set for some accounts and transaction due to the use
// of balance assertions or other calculations performed in valexpr-based
// posting amounts.
clear_xdata();
return count;
}
bool journal_t::has_xdata()
{
foreach (xact_t * xact, xacts)
if (xact->has_xdata())
return true;
foreach (auto_xact_t * xact, auto_xacts)
if (xact->has_xdata())
return true;
foreach (period_xact_t * xact, period_xacts)
if (xact->has_xdata())
return true;
if (master->has_xdata() || master->children_with_xdata())
return true;
return false;
}
void journal_t::clear_xdata()
{
foreach (xact_t * xact, xacts)
if (! xact->has_flags(ITEM_TEMP))
xact->clear_xdata();
foreach (auto_xact_t * xact, auto_xacts)
if (! xact->has_flags(ITEM_TEMP))
xact->clear_xdata();
foreach (period_xact_t * xact, period_xacts)
if (! xact->has_flags(ITEM_TEMP))
xact->clear_xdata();
master->clear_xdata();
}
bool journal_t::valid() const
{
if (! master->valid()) {
DEBUG("ledger.validate", "journal_t: master not valid");
return false;
}
foreach (const xact_t * xact, xacts)
if (! xact->valid()) {
DEBUG("ledger.validate", "journal_t: xact not valid");
return false;
}
return true;
}
} // namespace ledger