summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am4
-rw-r--r--src/amount.cc2
-rw-r--r--src/annotate.cc205
-rw-r--r--src/annotate.h199
-rw-r--r--src/balance.cc1
-rw-r--r--src/commodity.cc563
-rw-r--r--src/commodity.h239
-rw-r--r--src/global.cc14
-rw-r--r--src/op.cc1
-rw-r--r--src/pool.cc409
-rw-r--r--src/pool.h141
-rw-r--r--src/predicate.h1
-rw-r--r--src/py_amount.cc2
-rw-r--r--src/py_value.cc1
-rw-r--r--src/report.h1
-rw-r--r--src/session.cc1
-rw-r--r--src/textual.cc3
-rw-r--r--src/value.cc4
-rw-r--r--src/xact.cc7
19 files changed, 1004 insertions, 794 deletions
diff --git a/Makefile.am b/Makefile.am
index 3c7ad453..b7b19c7f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -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 \
\
diff --git a/src/amount.cc b/src/amount.cc
index 66934947..c9b02c65 100644
--- a/src/amount.cc
+++ b/src/amount.cc
@@ -33,6 +33,8 @@
#include "amount.h"
#include "commodity.h"
+#include "annotate.h"
+#include "pool.h"
namespace ledger {
diff --git a/src/annotate.cc b/src/annotate.cc
new file mode 100644
index 00000000..1ea39b5d
--- /dev/null
+++ b/src/annotate.cc
@@ -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
diff --git a/src/annotate.h b/src/annotate.h
new file mode 100644
index 00000000..d98f7ef6
--- /dev/null
+++ b/src/annotate.h
@@ -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
diff --git a/src/balance.cc b/src/balance.cc
index 8acb6378..07657287 100644
--- a/src/balance.cc
+++ b/src/balance.cc
@@ -33,6 +33,7 @@
#include "balance.h"
#include "commodity.h"
+#include "pool.h"
#include "unistring.h" // for justify()
namespace ledger {
diff --git a/src/commodity.cc b/src/commodity.cc
index 81c9b7e6..db7fabd6 100644
--- a/src/commodity.cc
+++ b/src/commodity.cc
@@ -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
diff --git a/src/commodity.h b/src/commodity.h
index c678293e..4bd5ee82 100644
--- a/src/commodity.h
+++ b/src/commodity.h
@@ -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) {
@@ -365,221 +337,10 @@ inline std::ostream& operator<<(std::ostream& out, const commodity_t& comm) {
*
* 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
- *
- * Long.
- */
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
diff --git a/src/global.cc b/src/global.cc
index 5d5e5836..26ee9ad7 100644
--- a/src/global.cc
+++ b/src/global.cc
@@ -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";
diff --git a/src/op.cc b/src/op.cc
index f1b2fb6b..559db616 100644
--- a/src/op.cc
+++ b/src/op.cc
@@ -34,6 +34,7 @@
#include "op.h"
#include "scope.h"
#include "commodity.h"
+#include "pool.h"
namespace ledger {
diff --git a/src/pool.cc b/src/pool.cc
new file mode 100644
index 00000000..3ced7c6f
--- /dev/null
+++ b/src/pool.cc
@@ -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
diff --git a/src/pool.h b/src/pool.h
new file mode 100644
index 00000000..17781bf1
--- /dev/null
+++ b/src/pool.h
@@ -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
diff --git a/src/predicate.h b/src/predicate.h
index 47eba04b..3e9fc6b1 100644
--- a/src/predicate.h
+++ b/src/predicate.h
@@ -48,6 +48,7 @@
#include "expr.h"
#include "commodity.h"
+#include "annotate.h"
namespace ledger {
diff --git a/src/py_amount.cc b/src/py_amount.cc
index a5a34a0b..68fd8698 100644
--- a/src/py_amount.cc
+++ b/src/py_amount.cc
@@ -35,6 +35,8 @@
#include "pyutils.h"
#include "pyfstream.h"
#include "commodity.h"
+#include "annotate.h"
+#include "pool.h"
namespace ledger {
diff --git a/src/py_value.cc b/src/py_value.cc
index c7847d00..8e579104 100644
--- a/src/py_value.cc
+++ b/src/py_value.cc
@@ -34,6 +34,7 @@
#include "pyinterp.h"
#include "pyutils.h"
#include "commodity.h"
+#include "annotate.h"
namespace ledger {
diff --git a/src/report.h b/src/report.h
index 147620f8..862be9fa 100644
--- a/src/report.h
+++ b/src/report.h
@@ -52,6 +52,7 @@
#include "stream.h"
#include "option.h"
#include "commodity.h"
+#include "annotate.h"
#include "format.h"
namespace ledger {
diff --git a/src/session.cc b/src/session.cc
index d5cc3b38..f7a8655b 100644
--- a/src/session.cc
+++ b/src/session.cc
@@ -33,6 +33,7 @@
#include "session.h"
#include "commodity.h"
+#include "pool.h"
#include "xact.h"
#include "account.h"
#include "journal.h"
diff --git a/src/textual.cc b/src/textual.cc
index 6f96ba99..35fa0028 100644
--- a/src/textual.cc
+++ b/src/textual.cc
@@ -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);
}
diff --git a/src/value.cc b/src/value.cc
index df1731cd..54d4bfc2 100644
--- a/src/value.cc
+++ b/src/value.cc
@@ -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;
diff --git a/src/xact.cc b/src/xact.cc
index 5c95b781..bd8a5955 100644
--- a/src/xact.cc
+++ b/src/xact.cc
@@ -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() ==