Split commodity.h/cc into three files

commodity.h - code for commodity_t
annotate.h  - commodity annotations
pool.h      - commodity pool management
This commit is contained in:
John Wiegley 2009-06-24 16:43:46 +01:00
parent 77d69d0e24
commit 991e3a3eaf
19 changed files with 1004 additions and 794 deletions

View file

@ -30,6 +30,8 @@ libledger_util_la_LDFLAGS = -release $(VERSION).0
libledger_math_la_SOURCES = \
src/value.cc \
src/balance.cc \
src/pool.cc \
src/annotate.cc \
src/commodity.cc \
src/amount.cc
@ -94,6 +96,8 @@ pkginclude_HEADERS = \
\
src/amount.h \
src/commodity.h \
src/annotate.h \
src/pool.h \
src/balance.h \
src/value.h \
\

View file

@ -33,6 +33,8 @@
#include "amount.h"
#include "commodity.h"
#include "annotate.h"
#include "pool.h"
namespace ledger {

205
src/annotate.cc Normal file
View file

@ -0,0 +1,205 @@
/*
* Copyright (c) 2003-2009, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of New Artisans LLC nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <system.hh>
#include "amount.h"
#include "commodity.h"
#include "annotate.h"
#include "pool.h"
namespace ledger {
void annotation_t::parse(std::istream& in)
{
do {
istream_pos_type pos = in.tellg();
char buf[256];
char c = peek_next_nonws(in);
if (c == '{') {
if (price)
throw_(amount_error, _("Commodity specifies more than one price"));
in.get(c);
c = peek_next_nonws(in);
if (c == '=') {
in.get(c);
add_flags(ANNOTATION_PRICE_FIXATED);
}
READ_INTO(in, buf, 255, c, c != '}');
if (c == '}')
in.get(c);
else
throw_(amount_error, _("Commodity price lacks closing brace"));
amount_t temp;
temp.parse(buf, amount_t::PARSE_NO_MIGRATE);
DEBUG("commodity.annotations", "Parsed annotation price: " << temp);
// Since this price will maintain its own precision, make sure
// it is at least as large as the base commodity, since the user
// may have only specified {$1} or something similar.
if (temp.has_commodity() &&
temp.precision() > temp.commodity().precision())
temp = temp.rounded(); // no need to retain individual precision
price = temp;
}
else if (c == '[') {
if (date)
throw_(amount_error, _("Commodity specifies more than one date"));
in.get(c);
READ_INTO(in, buf, 255, c, c != ']');
if (c == ']')
in.get(c);
else
throw_(amount_error, _("Commodity date lacks closing bracket"));
date = parse_date(buf);
}
else if (c == '(') {
if (tag)
throw_(amount_error, _("Commodity specifies more than one tag"));
in.get(c);
READ_INTO(in, buf, 255, c, c != ')');
if (c == ')')
in.get(c);
else
throw_(amount_error, _("Commodity tag lacks closing parenthesis"));
tag = buf;
}
else {
in.clear();
in.seekg(pos, std::ios::beg);
break;
}
} while (true);
#if defined(DEBUG_ON)
if (SHOW_DEBUG("amounts.commodities") && *this) {
DEBUG("amounts.commodities",
"Parsed commodity annotations: " << std::endl << *this);
}
#endif
}
void annotation_t::print(std::ostream& out, bool keep_base) const
{
if (price)
out << " {"
<< (has_flags(ANNOTATION_PRICE_FIXATED) ? "=" : "")
<< (keep_base ? *price : price->unreduced()).rounded()
<< '}';
if (date)
out << " [" << format_date(*date, string("%Y/%m/%d")) << ']';
if (tag)
out << " (" << *tag << ')';
}
bool keep_details_t::keep_all(const commodity_t& comm) const
{
return (! comm.annotated ||
(keep_price && keep_date && keep_tag && ! only_actuals));
}
bool keep_details_t::keep_any(const commodity_t& comm) const
{
return comm.annotated && (keep_price || keep_date || keep_tag);
}
bool annotated_commodity_t::operator==(const commodity_t& comm) const
{
// If the base commodities don't match, the game's up.
if (base != comm.base)
return false;
assert(annotated);
if (! comm.annotated)
return false;
if (details != as_annotated_commodity(comm).details)
return false;
return true;
}
commodity_t&
annotated_commodity_t::strip_annotations(const keep_details_t& what_to_keep)
{
DEBUG("commodity.annotated.strip",
"Reducing commodity " << *this << std::endl
<< " keep price " << what_to_keep.keep_price << " "
<< " keep date " << what_to_keep.keep_date << " "
<< " keep tag " << what_to_keep.keep_tag);
commodity_t * new_comm;
bool keep_price = (what_to_keep.keep_price &&
(! what_to_keep.only_actuals ||
! details.has_flags(ANNOTATION_PRICE_CALCULATED)));
bool keep_date = (what_to_keep.keep_date &&
(! what_to_keep.only_actuals ||
! details.has_flags(ANNOTATION_DATE_CALCULATED)));
bool keep_tag = (what_to_keep.keep_tag &&
(! what_to_keep.only_actuals ||
! details.has_flags(ANNOTATION_TAG_CALCULATED)));
if ((keep_price && details.price) ||
(keep_date && details.date) ||
(keep_tag && details.tag))
{
new_comm = parent().find_or_create
(referent(), annotation_t(keep_price ? details.price : none,
keep_date ? details.date : none,
keep_tag ? details.tag : none));
} else {
new_comm = parent().find_or_create(base_symbol());
}
assert(new_comm);
return *new_comm;
}
void annotated_commodity_t::write_annotations(std::ostream& out) const
{
details.print(out, parent().keep_base);
}
} // namespace ledger

199
src/annotate.h Normal file
View file

@ -0,0 +1,199 @@
/*
* Copyright (c) 2003-2009, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of New Artisans LLC nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @addtogroup math
*/
/**
* @file annotate.h
* @author John Wiegley
*
* @ingroup math
*
* @brief Types for annotating commodities
*
* Long.
*/
#ifndef _ANNOTATE_H
#define _ANNOTATE_H
namespace ledger {
/**
* @brief Brief
*
* Long.
*/
struct annotation_t : public supports_flags<>,
public equality_comparable<annotation_t>
{
#define ANNOTATION_PRICE_CALCULATED 0x01
#define ANNOTATION_PRICE_FIXATED 0x02
#define ANNOTATION_DATE_CALCULATED 0x04
#define ANNOTATION_TAG_CALCULATED 0x08
optional<amount_t> price;
optional<date_t> date;
optional<string> tag;
explicit annotation_t(const optional<amount_t>& _price = none,
const optional<date_t>& _date = none,
const optional<string>& _tag = none)
: supports_flags<>(), price(_price), date(_date), tag(_tag) {
TRACE_CTOR(annotation_t, "const optional<amount_t>& + date_t + string");
}
annotation_t(const annotation_t& other)
: supports_flags<>(other.flags()),
price(other.price), date(other.date), tag(other.tag) {
TRACE_CTOR(annotation_t, "copy");
}
~annotation_t() {
TRACE_DTOR(annotation_t);
}
operator bool() const {
return price || date || tag;
}
bool operator==(const annotation_t& rhs) const {
return (price == rhs.price &&
date == rhs.date &&
tag == rhs.tag);
}
void parse(std::istream& in);
void print(std::ostream& out, bool keep_base = false) const;
bool valid() const {
assert(*this);
return true;
}
};
struct keep_details_t
{
bool keep_price;
bool keep_date;
bool keep_tag;
bool only_actuals;
explicit keep_details_t(bool _keep_price = false,
bool _keep_date = false,
bool _keep_tag = false,
bool _only_actuals = false)
: keep_price(_keep_price),
keep_date(_keep_date),
keep_tag(_keep_tag),
only_actuals(_only_actuals)
{
TRACE_CTOR(keep_details_t, "bool, bool, bool, bool");
}
keep_details_t(const keep_details_t& other)
: keep_price(other.keep_price), keep_date(other.keep_date),
keep_tag(other.keep_tag), only_actuals(other.only_actuals) {
TRACE_CTOR(keep_details_t, "copy");
}
~keep_details_t() throw() {
TRACE_DTOR(keep_details_t);
}
bool keep_all() const {
return keep_price && keep_date && keep_tag && ! only_actuals;
}
bool keep_all(const commodity_t& comm) const;
bool keep_any() const {
return keep_price || keep_date || keep_tag;
}
bool keep_any(const commodity_t& comm) const;
};
inline std::ostream& operator<<(std::ostream& out,
const annotation_t& details) {
details.print(out);
return out;
}
/**
* @brief Brief
*
* Long.
*/
class annotated_commodity_t
: public commodity_t,
public equality_comparable<annotated_commodity_t,
equality_comparable2<annotated_commodity_t, commodity_t,
noncopyable> >
{
public:
commodity_t * ptr;
annotation_t details;
explicit annotated_commodity_t(commodity_t * _ptr,
const annotation_t& _details)
: commodity_t(_ptr->parent_, _ptr->base), ptr(_ptr), details(_details) {
TRACE_CTOR(annotated_commodity_t, "");
annotated = true;
}
virtual ~annotated_commodity_t() {
TRACE_DTOR(annotated_commodity_t);
}
virtual bool operator==(const commodity_t& comm) const;
virtual bool operator==(const annotated_commodity_t& comm) const {
return *this == static_cast<const commodity_t&>(comm);
}
virtual commodity_t& referent() {
return *ptr;
}
virtual const commodity_t& referent() const {
return *ptr;
}
virtual commodity_t& strip_annotations(const keep_details_t& what_to_keep);
virtual void write_annotations(std::ostream& out) const;
};
inline annotated_commodity_t&
as_annotated_commodity(commodity_t& commodity) {
return downcast<annotated_commodity_t>(commodity);
}
inline const annotated_commodity_t&
as_annotated_commodity(const commodity_t& commodity) {
return downcast<const annotated_commodity_t>(commodity);
}
} // namespace ledger
#endif // _ANNOTATE_H

View file

@ -33,6 +33,7 @@
#include "balance.h"
#include "commodity.h"
#include "pool.h"
#include "unistring.h" // for justify()
namespace ledger {

View file

@ -33,13 +33,11 @@
#include "amount.h"
#include "commodity.h"
#include "annotate.h"
#include "pool.h"
namespace ledger {
optional<path> commodity_t::price_db;
long commodity_t::download_leeway = 86400;
bool commodity_t::download_quotes;
void commodity_t::base_t::history_t::add_price(commodity_t& source,
const datetime_t& date,
const amount_t& price,
@ -99,8 +97,9 @@ void commodity_t::base_t::varied_history_t::
hist->add_price(source, date, price, reflexive);
}
bool commodity_t::base_t::varied_history_t::remove_price(const datetime_t& date,
commodity_t& comm)
bool commodity_t::base_t::varied_history_t::
remove_price(const datetime_t& date,
commodity_t& comm)
{
DEBUG("commodity.prices.add", "varied_remove_price: " << date << ", " << comm);
@ -109,104 +108,6 @@ bool commodity_t::base_t::varied_history_t::remove_price(const datetime_t& date
return false;
}
optional<price_point_t> commodity_t::parse_commodity_price(char * line)
{
char * date_field_ptr = line;
char * time_field_ptr = next_element(date_field_ptr);
if (! time_field_ptr) return none;
string date_field = date_field_ptr;
char * symbol_and_price;
datetime_t datetime;
if (std::isdigit(time_field_ptr[0])) {
symbol_and_price = next_element(time_field_ptr);
if (! symbol_and_price) return none;
datetime = parse_datetime(date_field + " " + time_field_ptr);
} else {
symbol_and_price = time_field_ptr;
datetime = parse_datetime(date_field);
}
string symbol;
parse_symbol(symbol_and_price, symbol);
price_point_t point;
point.when = datetime;
point.price.parse(symbol_and_price);
VERIFY(point.price.valid());
if (commodity_t * commodity =
amount_t::current_pool->find_or_create(symbol)) {
commodity->add_price(point.when, point.price, true);
commodity->add_flags(COMMODITY_KNOWN);
return point;
}
return none;
}
optional<price_point_t>
commodity_t::download_quote(const optional<commodity_t&>& commodity) const
{
DEBUG("commodity.download", "downloading quote for symbol " << symbol());
#if defined(DEBUG_ON)
if (commodity)
DEBUG("commodity.download",
" in terms of commodity " << commodity->symbol());
#endif
char buf[256];
buf[0] = '\0';
string getquote_cmd("getquote \"");
getquote_cmd += symbol();
getquote_cmd += "\" \"";
if (commodity)
getquote_cmd += commodity->symbol();
getquote_cmd += "\"";
DEBUG("commodity.download", "invoking command: " << getquote_cmd);
bool success = true;
if (FILE * fp = popen(getquote_cmd.c_str(), "r")) {
if (std::feof(fp) || ! std::fgets(buf, 255, fp))
success = false;
if (pclose(fp) != 0)
success = false;
} else {
success = false;
}
if (success && buf[0]) {
char * p = std::strchr(buf, '\n');
if (p) *p = '\0';
DEBUG("commodity.download", "downloaded quote: " << buf);
optional<price_point_t> point = parse_commodity_price(buf);
if (point) {
if (price_db) {
#if defined(__GNUG__) && __GNUG__ < 3
ofstream database(*price_db, ios::out | ios::app);
#else
ofstream database(*price_db, std::ios_base::out | std::ios_base::app);
#endif
database << "P " << format_datetime(point->when, string("%Y/%m/%d %H:%M:%S"))
<< " " << symbol() << " " << point->price << std::endl;
}
return point;
}
} else {
throw_(std::runtime_error,
_("Failed to download price for '%1' (command: \"getquote %2\")")
<< symbol() << symbol());
}
return none;
}
optional<price_point_t>
commodity_t::base_t::history_t::
find_price(const optional<datetime_t>& moment,
@ -445,27 +346,27 @@ optional<price_point_t>
" found price " << best.price << " from " << best.when);
DEBUG("commodity.download",
"found price " << best.price << " from " << best.when);
if (moment)
DEBUG("commodity.download", "moment = " << *moment);
DEBUG("commodity.download", "leeway = " << download_leeway);
if (moment)
DEBUG("commodity.download",
"slip.moment = " << (*moment - best.when).total_seconds());
else
DEBUG("commodity.download",
"slip.now = " << (CURRENT_TIME() - best.when).total_seconds());
#endif
if (download_quotes &&
! source.has_flags(COMMODITY_NOMARKET) &&
((! moment &&
(CURRENT_TIME() - best.when).total_seconds() > download_leeway) ||
(moment &&
(*moment - best.when).total_seconds() > download_leeway))) {
#if 0
DEBUG("commodity.download", "leeway = " << download_leeway);
datetime_t::sec_type seconds_diff;
if (moment) {
seconds_diff = (*moment - best.when).total_seconds();
DEBUG("commodity.download", "moment = " << *moment);
DEBUG("commodity.download", "slip.moment = " << seconds_diff);
} else {
seconds_diff = (CURRENT_TIME() - best.when).total_seconds();
DEBUG("commodity.download", "slip.now = " << seconds_diff);
}
if (download_quotes && ! source.has_flags(COMMODITY_NOMARKET) &&
seconds_diff > download_leeway) {
DEBUG("commodity.download",
"attempting to download a more current quote...");
if (optional<price_point_t> quote = source.download_quote(commodity))
return quote;
}
#endif
return best;
}
return none;
@ -497,82 +398,6 @@ optional<commodity_t::base_t::history_t&>
return none;
}
void commodity_t::exchange(commodity_t& commodity,
const amount_t& per_unit_cost,
const datetime_t& moment)
{
DEBUG("commodity.prices.add", "exchanging commodity " << commodity
<< " at per unit cost " << per_unit_cost << " on " << moment);
commodity_t& base_commodity
(commodity.annotated ?
as_annotated_commodity(commodity).referent() : commodity);
base_commodity.add_price(moment, per_unit_cost);
}
commodity_t::cost_breakdown_t
commodity_t::exchange(const amount_t& amount,
const amount_t& cost,
const bool is_per_unit,
const optional<datetime_t>& moment,
const optional<string>& tag)
{
DEBUG("commodity.prices.add", "exchange: " << amount << " for " << cost);
DEBUG("commodity.prices.add", "exchange: is-per-unit = " << is_per_unit);
#if defined(DEBUG_ON)
if (moment)
DEBUG("commodity.prices.add", "exchange: moment = " << *moment);
if (tag)
DEBUG("commodity.prices.add", "exchange: tag = " << *tag);
#endif
commodity_t& commodity(amount.commodity());
annotation_t * current_annotation = NULL;
if (commodity.annotated)
current_annotation = &as_annotated_commodity(commodity).details;
amount_t per_unit_cost =
(is_per_unit || amount.is_realzero() ? cost : cost / amount).abs();
DEBUG("commodity.prices.add", "exchange: per-unit-cost = " << per_unit_cost);
if (! per_unit_cost.is_realzero())
exchange(commodity, per_unit_cost, moment ? *moment : CURRENT_TIME());
cost_breakdown_t breakdown;
breakdown.final_cost = ! is_per_unit ? cost : cost * amount;
DEBUG("commodity.prices.add",
"exchange: final-cost = " << breakdown.final_cost);
if (current_annotation && current_annotation->price)
breakdown.basis_cost
= (*current_annotation->price * amount).unrounded();
else
breakdown.basis_cost = breakdown.final_cost;
DEBUG("commodity.prices.add",
"exchange: basis-cost = " << breakdown.basis_cost);
annotation_t annotation(per_unit_cost, moment ?
moment->date() : optional<date_t>(), tag);
annotation.add_flags(ANNOTATION_PRICE_CALCULATED);
if (moment)
annotation.add_flags(ANNOTATION_DATE_CALCULATED);
if (tag)
annotation.add_flags(ANNOTATION_TAG_CALCULATED);
breakdown.amount = amount_t(amount, annotation);
DEBUG("commodity.prices.add",
"exchange: amount = " << breakdown.amount);
return breakdown;
}
commodity_t::operator bool() const
{
return this != parent().null_commodity;
@ -752,159 +577,6 @@ bool commodity_t::valid() const
return true;
}
void annotation_t::parse(std::istream& in)
{
do {
istream_pos_type pos = in.tellg();
char buf[256];
char c = peek_next_nonws(in);
if (c == '{') {
if (price)
throw_(amount_error, _("Commodity specifies more than one price"));
in.get(c);
c = peek_next_nonws(in);
if (c == '=') {
in.get(c);
add_flags(ANNOTATION_PRICE_FIXATED);
}
READ_INTO(in, buf, 255, c, c != '}');
if (c == '}')
in.get(c);
else
throw_(amount_error, _("Commodity price lacks closing brace"));
amount_t temp;
temp.parse(buf, amount_t::PARSE_NO_MIGRATE);
DEBUG("commodity.annotations", "Parsed annotation price: " << temp);
// Since this price will maintain its own precision, make sure
// it is at least as large as the base commodity, since the user
// may have only specified {$1} or something similar.
if (temp.has_commodity() &&
temp.precision() > temp.commodity().precision())
temp = temp.rounded(); // no need to retain individual precision
price = temp;
}
else if (c == '[') {
if (date)
throw_(amount_error, _("Commodity specifies more than one date"));
in.get(c);
READ_INTO(in, buf, 255, c, c != ']');
if (c == ']')
in.get(c);
else
throw_(amount_error, _("Commodity date lacks closing bracket"));
date = parse_date(buf);
}
else if (c == '(') {
if (tag)
throw_(amount_error, _("Commodity specifies more than one tag"));
in.get(c);
READ_INTO(in, buf, 255, c, c != ')');
if (c == ')')
in.get(c);
else
throw_(amount_error, _("Commodity tag lacks closing parenthesis"));
tag = buf;
}
else {
in.clear();
in.seekg(pos, std::ios::beg);
break;
}
} while (true);
#if defined(DEBUG_ON)
if (SHOW_DEBUG("amounts.commodities") && *this) {
DEBUG("amounts.commodities",
"Parsed commodity annotations: " << std::endl << *this);
}
#endif
}
bool annotated_commodity_t::operator==(const commodity_t& comm) const
{
// If the base commodities don't match, the game's up.
if (base != comm.base)
return false;
assert(annotated);
if (! comm.annotated)
return false;
if (details != as_annotated_commodity(comm).details)
return false;
return true;
}
commodity_t&
annotated_commodity_t::strip_annotations(const keep_details_t& what_to_keep)
{
DEBUG("commodity.annotated.strip",
"Reducing commodity " << *this << std::endl
<< " keep price " << what_to_keep.keep_price << " "
<< " keep date " << what_to_keep.keep_date << " "
<< " keep tag " << what_to_keep.keep_tag);
commodity_t * new_comm;
bool keep_price = (what_to_keep.keep_price &&
(! what_to_keep.only_actuals ||
! details.has_flags(ANNOTATION_PRICE_CALCULATED)));
bool keep_date = (what_to_keep.keep_date &&
(! what_to_keep.only_actuals ||
! details.has_flags(ANNOTATION_DATE_CALCULATED)));
bool keep_tag = (what_to_keep.keep_tag &&
(! what_to_keep.only_actuals ||
! details.has_flags(ANNOTATION_TAG_CALCULATED)));
if ((keep_price && details.price) ||
(keep_date && details.date) ||
(keep_tag && details.tag))
{
new_comm = parent().find_or_create
(referent(), annotation_t(keep_price ? details.price : none,
keep_date ? details.date : none,
keep_tag ? details.tag : none));
} else {
new_comm = parent().find_or_create(base_symbol());
}
assert(new_comm);
return *new_comm;
}
void annotated_commodity_t::write_annotations(std::ostream& out) const
{
details.print(out, parent().keep_base);
}
void annotation_t::print(std::ostream& out, bool keep_base) const
{
if (price)
out << " {"
<< (has_flags(ANNOTATION_PRICE_FIXATED) ? "=" : "")
<< (keep_base ? *price : price->unreduced()).rounded()
<< '}';
if (date)
out << " [" << format_date(*date, string("%Y/%m/%d")) << ']';
if (tag)
out << " (" << *tag << ')';
}
bool compare_amount_commodities::operator()(const amount_t * left,
const amount_t * right) const
{
@ -972,199 +644,4 @@ bool compare_amount_commodities::operator()(const amount_t * left,
}
}
commodity_pool_t::commodity_pool_t()
: default_commodity(NULL), keep_base(false)
{
TRACE_CTOR(commodity_pool_t, "");
null_commodity = create("");
null_commodity->add_flags(COMMODITY_BUILTIN | COMMODITY_NOMARKET);
}
commodity_t * commodity_pool_t::create(const string& symbol)
{
shared_ptr<commodity_t::base_t>
base_commodity(new commodity_t::base_t(symbol));
std::auto_ptr<commodity_t> commodity(new commodity_t(this, base_commodity));
DEBUG("amounts.commodities", "Creating base commodity " << symbol);
// Create the "qualified symbol" version of this commodity's symbol
if (commodity_t::symbol_needs_quotes(symbol)) {
commodity->qualified_symbol = "\"";
*commodity->qualified_symbol += symbol;
*commodity->qualified_symbol += "\"";
}
DEBUG("amounts.commodities",
"Creating commodity '" << commodity->symbol() << "'");
std::pair<commodities_map::iterator, bool> result
= commodities.insert(commodities_map::value_type(commodity->mapping_key(),
commodity.get()));
assert(result.second);
return commodity.release();
}
commodity_t * commodity_pool_t::find_or_create(const string& symbol)
{
DEBUG("amounts.commodities", "Find-or-create commodity " << symbol);
commodity_t * commodity = find(symbol);
if (commodity)
return commodity;
return create(symbol);
}
commodity_t * commodity_pool_t::find(const string& symbol)
{
DEBUG("amounts.commodities", "Find commodity " << symbol);
commodities_map::const_iterator i = commodities.find(symbol);
if (i != commodities.end())
return (*i).second;
return NULL;
}
commodity_t *
commodity_pool_t::create(const string& symbol, const annotation_t& details)
{
commodity_t * new_comm = create(symbol);
if (! new_comm)
return NULL;
if (details)
return find_or_create(*new_comm, details);
else
return new_comm;
}
namespace {
string make_qualified_name(const commodity_t& comm,
const annotation_t& details)
{
assert(details);
if (details.price && details.price->sign() < 0)
throw_(amount_error, _("A commodity's price may not be negative"));
std::ostringstream name;
comm.print(name);
details.print(name, comm.parent().keep_base);
DEBUG("amounts.commodities", "make_qualified_name for "
<< *comm.qualified_symbol << std::endl << details);
DEBUG("amounts.commodities", "qualified_name is " << name.str());
return name.str();
}
}
commodity_t *
commodity_pool_t::find(const string& symbol, const annotation_t& details)
{
commodity_t * comm = find(symbol);
if (! comm)
return NULL;
if (details) {
string name = make_qualified_name(*comm, details);
if (commodity_t * ann_comm = find(name)) {
assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details);
return ann_comm;
}
return NULL;
} else {
return comm;
}
}
commodity_t *
commodity_pool_t::find_or_create(const string& symbol,
const annotation_t& details)
{
commodity_t * comm = find(symbol);
if (! comm)
return NULL;
if (details)
return find_or_create(*comm, details);
else
return comm;
}
commodity_t *
commodity_pool_t::create(commodity_t& comm,
const annotation_t& details,
const string& mapping_key)
{
assert(comm);
assert(details);
assert(! mapping_key.empty());
std::auto_ptr<commodity_t> commodity
(new annotated_commodity_t(&comm, details));
commodity->qualified_symbol = comm.symbol();
assert(! commodity->qualified_symbol->empty());
DEBUG("amounts.commodities", "Creating annotated commodity "
<< "symbol " << commodity->symbol()
<< " key " << mapping_key << std::endl << details);
// Add the fully annotated name to the map, so that this symbol may
// quickly be found again.
commodity->mapping_key_ = mapping_key;
std::pair<commodities_map::iterator, bool> result
= commodities.insert(commodities_map::value_type(mapping_key,
commodity.get()));
assert(result.second);
return commodity.release();
}
commodity_t * commodity_pool_t::find_or_create(commodity_t& comm,
const annotation_t& details)
{
assert(comm);
assert(details);
string name = make_qualified_name(comm, details);
assert(! name.empty());
if (commodity_t * ann_comm = find(name)) {
assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details);
return ann_comm;
}
return create(comm, details, name);
}
commodity_t *
commodity_pool_t::parse_commodity_prices(const std::string& str,
const bool add_prices,
const optional<datetime_t>& moment)
{
scoped_array<char> buf(new char[str.length() + 1]);
std::strcpy(buf.get(), str.c_str());
char * price = std::strchr(buf.get(), '=');
if (price)
*price++ = '\0';
if (commodity_t * commodity = find_or_create(trim_ws(buf.get()))) {
if (price && add_prices) {
for (char * p = std::strtok(price, ";");
p;
p = std::strtok(NULL, ";")) {
commodity->add_price(moment ? *moment : CURRENT_TIME(), amount_t(p));
}
}
return commodity;
}
return NULL;
}
} // namespace ledger

View file

@ -309,37 +309,9 @@ public:
return none;
}
// Methods to exchange one commodity for another, while recording the
// factored price.
static void exchange(commodity_t& commodity,
const amount_t& per_unit_cost,
const datetime_t& moment);
struct cost_breakdown_t {
amount_t amount;
amount_t final_cost;
amount_t basis_cost;
};
static cost_breakdown_t exchange(const amount_t& amount,
const amount_t& cost,
const bool is_per_unit = false,
const optional<datetime_t>& moment = none,
const optional<string>& tag = none);
// Methods related to parsing, reading, writing, etc., the commodity
// itself.
static optional<path> price_db;
static long download_leeway;
static bool download_quotes;
static optional<price_point_t> parse_commodity_price(char * line);
optional<price_point_t>
download_quote(const optional<commodity_t&>& commodity = none) const;
static void parse_symbol(std::istream& in, string& symbol);
static void parse_symbol(char *& p, string& symbol);
static string parse_symbol(std::istream& in) {
@ -360,158 +332,6 @@ inline std::ostream& operator<<(std::ostream& out, const commodity_t& comm) {
return out;
}
/**
* @brief Brief
*
* Long.
*/
struct annotation_t : public supports_flags<>,
public equality_comparable<annotation_t>
{
#define ANNOTATION_PRICE_CALCULATED 0x01
#define ANNOTATION_PRICE_FIXATED 0x02
#define ANNOTATION_DATE_CALCULATED 0x04
#define ANNOTATION_TAG_CALCULATED 0x08
optional<amount_t> price;
optional<date_t> date;
optional<string> tag;
explicit annotation_t(const optional<amount_t>& _price = none,
const optional<date_t>& _date = none,
const optional<string>& _tag = none)
: supports_flags<>(), price(_price), date(_date), tag(_tag) {
TRACE_CTOR(annotation_t, "const optional<amount_t>& + date_t + string");
}
annotation_t(const annotation_t& other)
: supports_flags<>(other.flags()),
price(other.price), date(other.date), tag(other.tag) {
TRACE_CTOR(annotation_t, "copy");
}
~annotation_t() {
TRACE_DTOR(annotation_t);
}
operator bool() const {
return price || date || tag;
}
bool operator==(const annotation_t& rhs) const {
return (price == rhs.price &&
date == rhs.date &&
tag == rhs.tag);
}
void parse(std::istream& in);
void print(std::ostream& out, bool keep_base = false) const;
bool valid() const {
assert(*this);
return true;
}
};
struct keep_details_t
{
bool keep_price;
bool keep_date;
bool keep_tag;
bool only_actuals;
explicit keep_details_t(bool _keep_price = false,
bool _keep_date = false,
bool _keep_tag = false,
bool _only_actuals = false)
: keep_price(_keep_price),
keep_date(_keep_date),
keep_tag(_keep_tag),
only_actuals(_only_actuals)
{
TRACE_CTOR(keep_details_t, "bool, bool, bool, bool");
}
keep_details_t(const keep_details_t& other)
: keep_price(other.keep_price), keep_date(other.keep_date),
keep_tag(other.keep_tag), only_actuals(other.only_actuals) {
TRACE_CTOR(keep_details_t, "copy");
}
~keep_details_t() throw() {
TRACE_DTOR(keep_details_t);
}
bool keep_all() const {
return keep_price && keep_date && keep_tag && ! only_actuals;
}
bool keep_all(const commodity_t& comm) const {
return (! comm.annotated ||
(keep_price && keep_date && keep_tag && ! only_actuals));
}
bool keep_any() const {
return keep_price || keep_date || keep_tag;
}
bool keep_any(const commodity_t& comm) const {
return comm.annotated && (keep_price || keep_date || keep_tag);
}
};
inline std::ostream& operator<<(std::ostream& out,
const annotation_t& details) {
details.print(out);
return out;
}
/**
* @brief Brief
*
* Long.
*/
class annotated_commodity_t
: public commodity_t,
public equality_comparable<annotated_commodity_t,
equality_comparable2<annotated_commodity_t, commodity_t,
noncopyable> >
{
public:
commodity_t * ptr;
annotation_t details;
explicit annotated_commodity_t(commodity_t * _ptr,
const annotation_t& _details)
: commodity_t(_ptr->parent_, _ptr->base), ptr(_ptr), details(_details) {
TRACE_CTOR(annotated_commodity_t, "");
annotated = true;
}
virtual ~annotated_commodity_t() {
TRACE_DTOR(annotated_commodity_t);
}
virtual bool operator==(const commodity_t& comm) const;
virtual bool operator==(const annotated_commodity_t& comm) const {
return *this == static_cast<const commodity_t&>(comm);
}
virtual commodity_t& referent() {
return *ptr;
}
virtual const commodity_t& referent() const {
return *ptr;
}
virtual commodity_t& strip_annotations(const keep_details_t& what_to_keep);
virtual void write_annotations(std::ostream& out) const;
};
inline annotated_commodity_t&
as_annotated_commodity(commodity_t& commodity) {
return downcast<annotated_commodity_t>(commodity);
}
inline const annotated_commodity_t&
as_annotated_commodity(const commodity_t& commodity) {
return downcast<const annotated_commodity_t>(commodity);
}
/**
* @brief Brief
*
@ -521,65 +341,6 @@ struct compare_amount_commodities {
bool operator()(const amount_t * left, const amount_t * right) const;
};
/**
* @brief Brief
*
* Long.
*/
class commodity_pool_t : public noncopyable
{
/**
* The commodities collection in commodity_pool_t maintains pointers to all
* the commodities which have ever been created by the user, whether
* explicitly by calling the create methods of commodity_pool_t, or
* implicitly by parsing a commoditized amount.
*/
typedef std::map<string, commodity_t *> commodities_map;
public:
commodities_map commodities;
commodity_t * null_commodity;
commodity_t * default_commodity;
bool keep_base;
public:
boost::function<optional<amount_t>
(commodity_t& commodity,
const optional<datetime_t>& date,
const optional<datetime_t>& moment,
const optional<datetime_t>& last)> get_quote;
explicit commodity_pool_t();
~commodity_pool_t() {
TRACE_DTOR(commodity_pool_t);
foreach (commodities_map::value_type pair, commodities)
checked_delete(pair.second);
}
commodity_t * create(const string& symbol);
commodity_t * find(const string& name);
commodity_t * find_or_create(const string& symbol);
commodity_t * create(const string& symbol, const annotation_t& details);
commodity_t * find(const string& symbol, const annotation_t& details);
commodity_t * find_or_create(const string& symbol,
const annotation_t& details);
commodity_t * create(commodity_t& comm,
const annotation_t& details,
const string& mapping_key);
commodity_t * find_or_create(commodity_t& comm,
const annotation_t& details);
commodity_t * parse_commodity_prices(const std::string& str,
const bool add_prices = true,
const optional<datetime_t>& moment = none);
};
} // namespace ledger
#endif // _COMMODITY_H

View file

@ -39,6 +39,7 @@
#endif
#include "item.h"
#include "journal.h"
#include "pool.h"
namespace ledger {
@ -416,19 +417,18 @@ void global_scope_t::normalize_report_options(const string& verb)
report_t& rep(report());
// jww (2009-02-09): These globals are a hack, but hard to avoid.
item_t::use_effective_date = rep.HANDLED(effective);
rep.session.commodity_pool->keep_base = rep.HANDLED(base);
commodity_t::download_quotes = rep.session.HANDLED(download);
item_t::use_effective_date = rep.HANDLED(effective);
rep.session.commodity_pool->keep_base = rep.HANDLED(base);
rep.session.commodity_pool->download_quotes = rep.session.HANDLED(download);
if (rep.session.HANDLED(price_exp_))
commodity_t::download_leeway =
rep.session.commodity_pool->download_leeway =
rep.session.HANDLER(price_exp_).value.as_long();
if (rep.session.HANDLED(price_db_))
commodity_t::price_db = rep.session.HANDLER(price_db_).str();
rep.session.commodity_pool->price_db = rep.session.HANDLER(price_db_).str();
else
commodity_t::price_db = none;
rep.session.commodity_pool->price_db = none;
if (rep.HANDLED(date_format_)) {
output_datetime_format = rep.HANDLER(date_format_).str() + " %H:%M:%S";

View file

@ -34,6 +34,7 @@
#include "op.h"
#include "scope.h"
#include "commodity.h"
#include "pool.h"
namespace ledger {

409
src/pool.cc Normal file
View file

@ -0,0 +1,409 @@
/*
* Copyright (c) 2003-2009, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of New Artisans LLC nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <system.hh>
#include "amount.h"
#include "commodity.h"
#include "annotate.h"
#include "pool.h"
namespace ledger {
commodity_pool_t::commodity_pool_t()
: default_commodity(NULL), keep_base(false),
download_leeway(86400), download_quotes(false)
{
TRACE_CTOR(commodity_pool_t, "");
null_commodity = create("");
null_commodity->add_flags(COMMODITY_BUILTIN | COMMODITY_NOMARKET);
}
commodity_t * commodity_pool_t::create(const string& symbol)
{
shared_ptr<commodity_t::base_t>
base_commodity(new commodity_t::base_t(symbol));
std::auto_ptr<commodity_t> commodity(new commodity_t(this, base_commodity));
DEBUG("amounts.commodities", "Creating base commodity " << symbol);
// Create the "qualified symbol" version of this commodity's symbol
if (commodity_t::symbol_needs_quotes(symbol)) {
commodity->qualified_symbol = "\"";
*commodity->qualified_symbol += symbol;
*commodity->qualified_symbol += "\"";
}
DEBUG("amounts.commodities",
"Creating commodity '" << commodity->symbol() << "'");
std::pair<commodities_map::iterator, bool> result
= commodities.insert(commodities_map::value_type(commodity->mapping_key(),
commodity.get()));
assert(result.second);
return commodity.release();
}
commodity_t * commodity_pool_t::find_or_create(const string& symbol)
{
DEBUG("amounts.commodities", "Find-or-create commodity " << symbol);
commodity_t * commodity = find(symbol);
if (commodity)
return commodity;
return create(symbol);
}
commodity_t * commodity_pool_t::find(const string& symbol)
{
DEBUG("amounts.commodities", "Find commodity " << symbol);
commodities_map::const_iterator i = commodities.find(symbol);
if (i != commodities.end())
return (*i).second;
return NULL;
}
commodity_t *
commodity_pool_t::create(const string& symbol, const annotation_t& details)
{
commodity_t * new_comm = create(symbol);
if (! new_comm)
return NULL;
if (details)
return find_or_create(*new_comm, details);
else
return new_comm;
}
namespace {
string make_qualified_name(const commodity_t& comm,
const annotation_t& details)
{
assert(details);
if (details.price && details.price->sign() < 0)
throw_(amount_error, _("A commodity's price may not be negative"));
std::ostringstream name;
comm.print(name);
details.print(name, comm.parent().keep_base);
DEBUG("amounts.commodities", "make_qualified_name for "
<< *comm.qualified_symbol << std::endl << details);
DEBUG("amounts.commodities", "qualified_name is " << name.str());
return name.str();
}
}
commodity_t *
commodity_pool_t::find(const string& symbol, const annotation_t& details)
{
commodity_t * comm = find(symbol);
if (! comm)
return NULL;
if (details) {
string name = make_qualified_name(*comm, details);
if (commodity_t * ann_comm = find(name)) {
assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details);
return ann_comm;
}
return NULL;
} else {
return comm;
}
}
commodity_t *
commodity_pool_t::find_or_create(const string& symbol,
const annotation_t& details)
{
commodity_t * comm = find(symbol);
if (! comm)
return NULL;
if (details)
return find_or_create(*comm, details);
else
return comm;
}
commodity_t *
commodity_pool_t::create(commodity_t& comm,
const annotation_t& details,
const string& mapping_key)
{
assert(comm);
assert(details);
assert(! mapping_key.empty());
std::auto_ptr<commodity_t> commodity
(new annotated_commodity_t(&comm, details));
commodity->qualified_symbol = comm.symbol();
assert(! commodity->qualified_symbol->empty());
DEBUG("amounts.commodities", "Creating annotated commodity "
<< "symbol " << commodity->symbol()
<< " key " << mapping_key << std::endl << details);
// Add the fully annotated name to the map, so that this symbol may
// quickly be found again.
commodity->mapping_key_ = mapping_key;
std::pair<commodities_map::iterator, bool> result
= commodities.insert(commodities_map::value_type(mapping_key,
commodity.get()));
assert(result.second);
return commodity.release();
}
commodity_t * commodity_pool_t::find_or_create(commodity_t& comm,
const annotation_t& details)
{
assert(comm);
assert(details);
string name = make_qualified_name(comm, details);
assert(! name.empty());
if (commodity_t * ann_comm = find(name)) {
assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details);
return ann_comm;
}
return create(comm, details, name);
}
optional<price_point_t> commodity_pool_t::parse_price_directive(char * line)
{
char * date_field_ptr = line;
char * time_field_ptr = next_element(date_field_ptr);
if (! time_field_ptr) return none;
string date_field = date_field_ptr;
char * symbol_and_price;
datetime_t datetime;
if (std::isdigit(time_field_ptr[0])) {
symbol_and_price = next_element(time_field_ptr);
if (! symbol_and_price) return none;
datetime = parse_datetime(date_field + " " + time_field_ptr);
} else {
symbol_and_price = time_field_ptr;
datetime = parse_datetime(date_field);
}
string symbol;
commodity_t::parse_symbol(symbol_and_price, symbol);
price_point_t point;
point.when = datetime;
point.price.parse(symbol_and_price);
VERIFY(point.price.valid());
if (commodity_t * commodity =
amount_t::current_pool->find_or_create(symbol)) {
commodity->add_price(point.when, point.price, true);
commodity->add_flags(COMMODITY_KNOWN);
return point;
}
return none;
}
#if 0
optional<price_point_t>
commodity_t::download_quote(const optional<commodity_t&>& commodity) const
{
DEBUG("commodity.download", "downloading quote for symbol " << symbol());
#if defined(DEBUG_ON)
if (commodity)
DEBUG("commodity.download",
" in terms of commodity " << commodity->symbol());
#endif
char buf[256];
buf[0] = '\0';
string getquote_cmd("getquote \"");
getquote_cmd += symbol();
getquote_cmd += "\" \"";
if (commodity)
getquote_cmd += commodity->symbol();
getquote_cmd += "\"";
DEBUG("commodity.download", "invoking command: " << getquote_cmd);
bool success = true;
if (FILE * fp = popen(getquote_cmd.c_str(), "r")) {
if (std::feof(fp) || ! std::fgets(buf, 255, fp))
success = false;
if (pclose(fp) != 0)
success = false;
} else {
success = false;
}
if (success && buf[0]) {
if (char * p = std::strchr(buf, '\n')) *p = '\0';
DEBUG("commodity.download", "downloaded quote: " << buf);
if (optional<price_point_t> point = parse_commodity_price(buf)) {
if (price_db) {
#if defined(__GNUG__) && __GNUG__ < 3
ofstream database(*price_db, ios::out | ios::app);
#else
ofstream database(*price_db, std::ios_base::out | std::ios_base::app);
#endif
database << "P " << format_datetime(point->when, string("%Y/%m/%d %H:%M:%S"))
<< " " << symbol() << " " << point->price << std::endl;
}
return point;
}
} else {
throw_(std::runtime_error,
_("Failed to download price for '%1' (command: \"getquote %2 %3\")")
<< symbol() << symbol() << (commodity ? commodity->symbol() : "''"));
}
return none;
}
#endif
void commodity_pool_t::exchange(commodity_t& commodity,
const amount_t& per_unit_cost,
const datetime_t& moment)
{
DEBUG("commodity.prices.add", "exchanging commodity " << commodity
<< " at per unit cost " << per_unit_cost << " on " << moment);
commodity_t& base_commodity
(commodity.annotated ?
as_annotated_commodity(commodity).referent() : commodity);
base_commodity.add_price(moment, per_unit_cost);
}
cost_breakdown_t
commodity_pool_t::exchange(const amount_t& amount,
const amount_t& cost,
const bool is_per_unit,
const optional<datetime_t>& moment,
const optional<string>& tag)
{
DEBUG("commodity.prices.add", "exchange: " << amount << " for " << cost);
DEBUG("commodity.prices.add", "exchange: is-per-unit = " << is_per_unit);
#if defined(DEBUG_ON)
if (moment)
DEBUG("commodity.prices.add", "exchange: moment = " << *moment);
if (tag)
DEBUG("commodity.prices.add", "exchange: tag = " << *tag);
#endif
commodity_t& commodity(amount.commodity());
annotation_t * current_annotation = NULL;
if (commodity.annotated)
current_annotation = &as_annotated_commodity(commodity).details;
amount_t per_unit_cost =
(is_per_unit || amount.is_realzero() ? cost : cost / amount).abs();
DEBUG("commodity.prices.add", "exchange: per-unit-cost = " << per_unit_cost);
if (! per_unit_cost.is_realzero())
exchange(commodity, per_unit_cost, moment ? *moment : CURRENT_TIME());
cost_breakdown_t breakdown;
breakdown.final_cost = ! is_per_unit ? cost : cost * amount;
DEBUG("commodity.prices.add",
"exchange: final-cost = " << breakdown.final_cost);
if (current_annotation && current_annotation->price)
breakdown.basis_cost
= (*current_annotation->price * amount).unrounded();
else
breakdown.basis_cost = breakdown.final_cost;
DEBUG("commodity.prices.add",
"exchange: basis-cost = " << breakdown.basis_cost);
annotation_t annotation(per_unit_cost, moment ?
moment->date() : optional<date_t>(), tag);
annotation.add_flags(ANNOTATION_PRICE_CALCULATED);
if (moment)
annotation.add_flags(ANNOTATION_DATE_CALCULATED);
if (tag)
annotation.add_flags(ANNOTATION_TAG_CALCULATED);
breakdown.amount = amount_t(amount, annotation);
DEBUG("commodity.prices.add",
"exchange: amount = " << breakdown.amount);
return breakdown;
}
commodity_t *
commodity_pool_t::parse_price_expression(const std::string& str,
const bool add_prices,
const optional<datetime_t>& moment)
{
scoped_array<char> buf(new char[str.length() + 1]);
std::strcpy(buf.get(), str.c_str());
char * price = std::strchr(buf.get(), '=');
if (price)
*price++ = '\0';
if (commodity_t * commodity = find_or_create(trim_ws(buf.get()))) {
if (price && add_prices) {
for (char * p = std::strtok(price, ";");
p;
p = std::strtok(NULL, ";")) {
commodity->add_price(moment ? *moment : CURRENT_TIME(), amount_t(p));
}
}
return commodity;
}
return NULL;
}
} // namespace ledger

141
src/pool.h Normal file
View file

@ -0,0 +1,141 @@
/*
* Copyright (c) 2003-2009, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of New Artisans LLC nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @addtogroup math
*/
/**
* @file pool.h
* @author John Wiegley
*
* @ingroup math
*
* @brief Types for managing commodity pools
*
* Long.
*/
#ifndef _POOL_H
#define _POOL_H
namespace ledger {
/**
* @brief Brief
*
* Long.
*/
struct cost_breakdown_t
{
amount_t amount;
amount_t final_cost;
amount_t basis_cost;
};
/**
* @brief Brief
*
* Long.
*/
class commodity_pool_t : public noncopyable
{
/**
* The commodities collection in commodity_pool_t maintains pointers to all
* the commodities which have ever been created by the user, whether
* explicitly by calling the create methods of commodity_pool_t, or
* implicitly by parsing a commoditized amount.
*/
typedef std::map<string, commodity_t *> commodities_map;
public:
commodities_map commodities;
commodity_t * null_commodity;
commodity_t * default_commodity;
bool keep_base; // --base
optional<path> price_db; // --price-db=
long download_leeway; // --leeway=
bool download_quotes; // --download
public:
function<optional<price_point_t>
(const optional<commodity_t&>& commodity)> get_commodity_quote;
explicit commodity_pool_t();
~commodity_pool_t() {
TRACE_DTOR(commodity_pool_t);
foreach (commodities_map::value_type pair, commodities)
checked_delete(pair.second);
}
commodity_t * create(const string& symbol);
commodity_t * find(const string& name);
commodity_t * find_or_create(const string& symbol);
commodity_t * create(const string& symbol, const annotation_t& details);
commodity_t * find(const string& symbol, const annotation_t& details);
commodity_t * find_or_create(const string& symbol,
const annotation_t& details);
commodity_t * create(commodity_t& comm,
const annotation_t& details,
const string& mapping_key);
commodity_t * find_or_create(commodity_t& comm,
const annotation_t& details);
// Exchange one commodity for another, while recording the factored price.
void exchange(commodity_t& commodity,
const amount_t& per_unit_cost,
const datetime_t& moment);
cost_breakdown_t exchange(const amount_t& amount,
const amount_t& cost,
const bool is_per_unit = false,
const optional<datetime_t>& moment = none,
const optional<string>& tag = none);
// Parse commodity prices from a textual representation
optional<price_point_t> parse_price_directive(char * line);
commodity_t *
parse_price_expression(const std::string& str,
const bool add_prices = true,
const optional<datetime_t>& moment = none);
};
} // namespace ledger
#endif // _POOL_H

View file

@ -48,6 +48,7 @@
#include "expr.h"
#include "commodity.h"
#include "annotate.h"
namespace ledger {

View file

@ -35,6 +35,8 @@
#include "pyutils.h"
#include "pyfstream.h"
#include "commodity.h"
#include "annotate.h"
#include "pool.h"
namespace ledger {

View file

@ -34,6 +34,7 @@
#include "pyinterp.h"
#include "pyutils.h"
#include "commodity.h"
#include "annotate.h"
namespace ledger {

View file

@ -52,6 +52,7 @@
#include "stream.h"
#include "option.h"
#include "commodity.h"
#include "annotate.h"
#include "format.h"
namespace ledger {

View file

@ -33,6 +33,7 @@
#include "session.h"
#include "commodity.h"
#include "pool.h"
#include "xact.h"
#include "account.h"
#include "journal.h"

View file

@ -37,6 +37,7 @@
#include "account.h"
#include "option.h"
#include "pstream.h"
#include "pool.h"
#define TIMELOG_SUPPORT 1
#if defined(TIMELOG_SUPPORT)
@ -458,7 +459,7 @@ void instance_t::price_conversion_directive(char * line)
void instance_t::price_xact_directive(char * line)
{
optional<price_point_t> point =
commodity_t::parse_commodity_price(skip_ws(line + 1));
amount_t::current_pool->parse_price_directive(skip_ws(line + 1));
assert(point);
}

View file

@ -33,6 +33,8 @@
#include "value.h"
#include "commodity.h"
#include "annotate.h"
#include "pool.h"
#include "unistring.h"
namespace ledger {
@ -1240,7 +1242,7 @@ value_t value_t::exchange_commodities(const std::string& commodities,
p;
p = std::strtok(NULL, ",")) {
if (commodity_t * commodity =
amount_t::current_pool->parse_commodity_prices(p, add_prices, moment)) {
amount_t::current_pool->parse_price_expression(p, add_prices, moment)) {
value_t result = value(false, moment, *commodity);
if (! result.is_null())
return result;

View file

@ -35,6 +35,7 @@
#include "post.h"
#include "account.h"
#include "journal.h"
#include "pool.h"
namespace ledger {
@ -269,9 +270,9 @@ bool xact_base_t::finalize()
throw_(balance_error,
_("A posting's cost must be of a different commodity than its amount"));
commodity_t::cost_breakdown_t breakdown =
commodity_t::exchange(post->amount, *post->cost, false,
datetime_t(date(), time_duration(0, 0, 0, 0)));
cost_breakdown_t breakdown =
amount_t::current_pool->exchange(post->amount, *post->cost, false,
datetime_t(date(), time_duration(0, 0, 0, 0)));
if (post->amount.is_annotated() &&
breakdown.basis_cost.commodity() ==