diff options
-rw-r--r-- | Makefile.am | 4 | ||||
-rw-r--r-- | src/amount.cc | 2 | ||||
-rw-r--r-- | src/annotate.cc | 205 | ||||
-rw-r--r-- | src/annotate.h | 199 | ||||
-rw-r--r-- | src/balance.cc | 1 | ||||
-rw-r--r-- | src/commodity.cc | 563 | ||||
-rw-r--r-- | src/commodity.h | 239 | ||||
-rw-r--r-- | src/global.cc | 14 | ||||
-rw-r--r-- | src/op.cc | 1 | ||||
-rw-r--r-- | src/pool.cc | 409 | ||||
-rw-r--r-- | src/pool.h | 141 | ||||
-rw-r--r-- | src/predicate.h | 1 | ||||
-rw-r--r-- | src/py_amount.cc | 2 | ||||
-rw-r--r-- | src/py_value.cc | 1 | ||||
-rw-r--r-- | src/report.h | 1 | ||||
-rw-r--r-- | src/session.cc | 1 | ||||
-rw-r--r-- | src/textual.cc | 3 | ||||
-rw-r--r-- | src/value.cc | 4 | ||||
-rw-r--r-- | src/xact.cc | 7 |
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"; @@ -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() == |