diff options
author | John Wiegley <johnw@newartisans.com> | 2009-11-19 21:53:02 -0500 |
---|---|---|
committer | John Wiegley <johnw@newartisans.com> | 2009-11-19 22:24:24 -0500 |
commit | 4e30fcdf4094a0c450cbe1918c2e12dd19eb58f2 (patch) | |
tree | e4bce67522d52cd7be03544501191d761dce7799 | |
parent | 6e31328bd62d15e8da4238394abd8de65539cfa2 (diff) | |
download | fork-ledger-4e30fcdf4094a0c450cbe1918c2e12dd19eb58f2.tar.gz fork-ledger-4e30fcdf4094a0c450cbe1918c2e12dd19eb58f2.tar.bz2 fork-ledger-4e30fcdf4094a0c450cbe1918c2e12dd19eb58f2.zip |
Many improvements to Ledger's Python bindings
-rw-r--r-- | python/demo.py | 120 | ||||
-rw-r--r-- | src/amount.cc | 54 | ||||
-rw-r--r-- | src/amount.h | 6 | ||||
-rw-r--r-- | src/annotate.cc | 4 | ||||
-rw-r--r-- | src/balance.cc | 6 | ||||
-rw-r--r-- | src/commodity.cc | 10 | ||||
-rw-r--r-- | src/commodity.h | 2 | ||||
-rw-r--r-- | src/journal.cc | 16 | ||||
-rw-r--r-- | src/journal.h | 3 | ||||
-rw-r--r-- | src/op.cc | 2 | ||||
-rw-r--r-- | src/pool.cc | 5 | ||||
-rw-r--r-- | src/pool.h | 5 | ||||
-rw-r--r-- | src/py_amount.cc | 76 | ||||
-rw-r--r-- | src/py_balance.cc | 10 | ||||
-rw-r--r-- | src/py_commodity.cc | 199 | ||||
-rw-r--r-- | src/py_journal.cc | 8 | ||||
-rw-r--r-- | src/py_value.cc | 39 | ||||
-rw-r--r-- | src/pyutils.h | 20 | ||||
-rw-r--r-- | src/quotes.cc | 8 | ||||
-rw-r--r-- | src/report.cc | 17 | ||||
-rw-r--r-- | src/session.cc | 4 | ||||
-rw-r--r-- | src/textual.cc | 6 | ||||
-rw-r--r-- | src/value.cc | 7 | ||||
-rw-r--r-- | src/value.h | 2 | ||||
-rw-r--r-- | src/xact.cc | 13 | ||||
-rwxr-xr-x | test/convert.py | 2 | ||||
-rw-r--r-- | test/regress/25A099C9.test | 12 |
27 files changed, 449 insertions, 207 deletions
diff --git a/python/demo.py b/python/demo.py new file mode 100644 index 00000000..788610d7 --- /dev/null +++ b/python/demo.py @@ -0,0 +1,120 @@ +import sys + +import ledger + +print "Welcome to the Ledger.Python demo!" + +def assertEqual(pat, candidate): + if pat != candidate: + print "FAILED: %s != %s" % (pat, candidate) + sys.exit(1) + +# COMMODITIES + +pool = ledger.commodity_pool + +usd = pool.find_or_create('$') +eur = pool.find_or_create('EUR') +xcd = pool.find_or_create('XCD') + +assertEqual('$', usd.symbol) +assertEqual('$', pool['$'].symbol) + +assert not pool.find('CAD') +assert not pool.has_key('CAD') +assert not 'CAD' in pool + +# There are a few built-in commodities: null, %, h, m and s +assertEqual([u'', u'$', u'%', u'EUR', u'XCD', + u'h', u'm', u's'], sorted(pool.keys())) + +for symbol in pool.iterkeys(): pass +for commodity in pool.itervalues(): pass + +# jww (2009-11-19): Not working: missing conversion from std::pair +#for symbol, commodity in pool.iteritems(): pass +#for symbol, commodity in pool: pass + +# This creates a price exchange entry, trading EUR for $0.77 each at the +# current time. +pool.exchange(eur, ledger.Amount('$0.77')) + +# AMOUNTS & BALANCES + +# When two amounts are multipied or divided, the result carries the commodity +# of the first term. So, 1 EUR / $0.77 == roughly 1.2987 EUR +amt = ledger.Amount('$100.12') +market = ((ledger.Amount('1 EUR') / ledger.Amount('$0.77')) * amt) + +# An amount's "precision" is a notional thing only. Since Ledger uses +# rational numbers throughout, and only renders to decimal form for printing +# to the user, the meaning of amt.precision should not be relied on as +# meaningful. It only controls how much precision unrounded numbers (those +# for which keep_precision is True, and thus that ignore display_precision) +# are rendered into strings. This is the case, btw, for all uncommoditized +# amounts. +assert not amt.keep_precision +assertEqual(2, amt.precision) +assertEqual(2, amt.display_precision) + +assertEqual('$-100.12', str(amt.negated())) # negate the amount +assertEqual('$0.01', str(amt.inverted())) # reverse NUM/DEM +assertEqual('$100.12', str(amt.rounded())) # round it to display precision +assertEqual('$100.12', str(amt.truncated())) # truncate to display precision +assertEqual('$100.00', str(amt.floored())) # floor it to nearest integral +assertEqual(market, amt.value(eur)) # find present market value +assertEqual('$100.12', str(abs(amt))) # absolute value +assertEqual('$100.12', str(amt)) # render to a string +assertEqual('100.12', amt.quantity_string()) # render quantity to a string +assertEqual('100.12', str(amt.number())) # strip away commodity +assertEqual(1, amt.sign()) # -1, 0 or 1 +assert amt.is_nonzero() # True if display amount nonzero +assert not amt.is_zero() # True if display amount is zero +assert not amt.is_realzero() # True only if value is 0/0 +assert not amt.is_null() # True if uninitialized + +assertEqual(100.12, amt.to_double()) +assert amt.fits_in_long() +assertEqual(100, amt.to_long()) + +amt2 = ledger.Amount('$100.12 {140 EUR}') + +assert amt2.has_annotation() +assertEqual(amt, amt2.strip_annotations(ledger.KeepDetails())) + +# jww (2009-11-19): Not working: missing conversion from optional<amount_t> +#assertEqual(ledger.Amount('20 EUR'), amt.annotation.price) + +# VALUES + +val = ledger.Value('$100.00') + +assert val.is_amount() +assertEqual('$', val.to_amount().commodity.symbol) + +# JOURNALS + +#journal.find_account('') +#journal.find_or_create_account('') + +# ACCOUNTS + +#account.name +#account.fullname() +#account.amount +#account.total + +# TRANSACTIONS + +#txn.payee + +# POSTINGS + +#post.account + +# REPORTING + +#journal.collect('-M food') +#journal.collect_accounts('^assets ^liab ^equity') + +print 'Demo completed successfully.' diff --git a/src/amount.cc b/src/amount.cc index 82b93931..eddbca18 100644 --- a/src/amount.cc +++ b/src/amount.cc @@ -106,8 +106,6 @@ private: #endif // HAVE_BOOST_SERIALIZATION }; -shared_ptr<commodity_pool_t> amount_t::current_pool; - bool amount_t::is_initialized = false; namespace { @@ -203,7 +201,7 @@ namespace { } } -void amount_t::initialize(shared_ptr<commodity_pool_t> pool) +void amount_t::initialize() { if (! is_initialized) { mpz_init(temp); @@ -211,26 +209,35 @@ void amount_t::initialize(shared_ptr<commodity_pool_t> pool) mpfr_init(tempf); mpfr_init(tempfb); + commodity_pool_t::current_pool.reset(new commodity_pool_t); + + // Add time commodity conversions, so that timelog's may be parsed + // in terms of seconds, but reported as minutes or hours. + if (commodity_t * commodity = commodity_pool_t::current_pool->create("s")) + commodity->add_flags(COMMODITY_BUILTIN | COMMODITY_NOMARKET); + else + assert(false); + + // Add a "percentile" commodity + if (commodity_t * commodity = commodity_pool_t::current_pool->create("%")) + commodity->add_flags(COMMODITY_BUILTIN | COMMODITY_NOMARKET); + else + assert(false); + is_initialized = true; } - current_pool = pool; -} - -void amount_t::initialize() -{ - initialize(shared_ptr<commodity_pool_t>(new commodity_pool_t)); } void amount_t::shutdown() { - current_pool.reset(); - if (is_initialized) { mpz_clear(temp); mpq_clear(tempq); mpfr_clear(tempf); mpfr_clear(tempfb); + commodity_pool_t::current_pool.reset(); + is_initialized = false; } } @@ -670,7 +677,7 @@ amount_t::value(const bool primary_only, if (in_terms_of && commodity() == *in_terms_of) { return *this; } - else if (is_annotated() && annotation().price && + else if (has_annotation() && annotation().price && annotation().has_flags(ANNOTATION_PRICE_FIXATED)) { return (*annotation().price * number()).rounded(); } @@ -696,7 +703,7 @@ amount_t::value(const bool primary_only, amount_t amount_t::price() const { - if (is_annotated() && annotation().price) { + if (has_annotation() && annotation().price) { amount_t temp(*annotation().price); temp *= *this; DEBUG("amount.price", "Returning price of " << *this << " = " << temp); @@ -776,7 +783,8 @@ bool amount_t::fits_in_long() const commodity_t& amount_t::commodity() const { - return has_commodity() ? *commodity_ : *current_pool->null_commodity; + return (has_commodity() ? + *commodity_ : *commodity_pool_t::current_pool->null_commodity); } bool amount_t::has_commodity() const @@ -794,7 +802,7 @@ void amount_t::annotate(const annotation_t& details) else if (! has_commodity()) return; // ignore attempt to annotate a "bare commodity - if (commodity().is_annotated()) { + if (commodity().has_annotation()) { this_ann = &as_annotated_commodity(commodity()); this_base = &this_ann->referent(); } else { @@ -816,15 +824,15 @@ void amount_t::annotate(const annotation_t& details) DEBUG("amounts.commodities", "Annotated amount is " << *this); } -bool amount_t::is_annotated() const +bool amount_t::has_annotation() const { if (! quantity) throw_(amount_error, _("Cannot determine if an uninitialized amount's commodity is annotated")); - assert(! has_commodity() || ! commodity().is_annotated() || + assert(! has_commodity() || ! commodity().has_annotation() || as_annotated_commodity(commodity()).details); - return has_commodity() && commodity().is_annotated(); + return has_commodity() && commodity().has_annotation(); } annotation_t& amount_t::annotation() @@ -833,7 +841,7 @@ annotation_t& amount_t::annotation() throw_(amount_error, _("Cannot return commodity annotation details of an uninitialized amount")); - if (! commodity().is_annotated()) + if (! commodity().has_annotation()) throw_(amount_error, _("Request for annotation details from an unannotated amount")); @@ -963,15 +971,16 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags) if (symbol.empty()) { commodity_ = NULL; } else { - commodity_ = current_pool->find(symbol); + commodity_ = commodity_pool_t::current_pool->find(symbol); if (! commodity_) { - commodity_ = current_pool->create(symbol); + commodity_ = commodity_pool_t::current_pool->create(symbol); newly_created = true; } assert(commodity_); if (details) - commodity_ = current_pool->find_or_create(*commodity_, details); + commodity_ = + commodity_pool_t::current_pool->find_or_create(*commodity_, details); } // Quickly scan through and verify the correctness of the amount's use of @@ -1206,7 +1215,6 @@ void to_xml(std::ostream& out, const amount_t& amt, bool commodity_details) template<class Archive> void amount_t::serialize(Archive& ar, const unsigned int /* version */) { - ar & current_pool; ar & is_initialized; ar & quantity; ar & commodity_; diff --git a/src/amount.h b/src/amount.h index c75370e3..a8c08905 100644 --- a/src/amount.h +++ b/src/amount.h @@ -97,12 +97,8 @@ class amount_t ordered_field_operators<amount_t, long> > > > { public: - /** Indicates which commodity pool should be used. */ - static shared_ptr<commodity_pool_t> current_pool; - /** Ready the amount subsystem for use. @note Normally called by session_t::initialize(). */ - static void initialize(shared_ptr<commodity_pool_t> pool); static void initialize(); /** Shutdown the amount subsystem and free all resources. @note Normally called by session_t::shutdown(). */ @@ -577,7 +573,7 @@ public: been stripped. */ void annotate(const annotation_t& details); - bool is_annotated() const; + bool has_annotation() const; annotation_t& annotation(); const annotation_t& annotation() const { diff --git a/src/annotate.cc b/src/annotate.cc index bd5a8ef8..146a7afd 100644 --- a/src/annotate.cc +++ b/src/annotate.cc @@ -135,13 +135,13 @@ void annotation_t::print(std::ostream& out, bool keep_base) const bool keep_details_t::keep_all(const commodity_t& comm) const { - return (! comm.is_annotated() || + return (! comm.has_annotation() || (keep_price && keep_date && keep_tag && ! only_actuals)); } bool keep_details_t::keep_any(const commodity_t& comm) const { - return comm.is_annotated() && (keep_price || keep_date || keep_tag); + return comm.has_annotation() && (keep_price || keep_date || keep_tag); } bool annotated_commodity_t::operator==(const commodity_t& comm) const diff --git a/src/balance.cc b/src/balance.cc index 59eb4d92..4ff51ffc 100644 --- a/src/balance.cc +++ b/src/balance.cc @@ -43,21 +43,21 @@ balance_t::balance_t(const double val) { TRACE_CTOR(balance_t, "const double"); amounts.insert - (amounts_map::value_type(amount_t::current_pool->null_commodity, val)); + (amounts_map::value_type(commodity_pool_t::current_pool->null_commodity, val)); } balance_t::balance_t(const unsigned long val) { TRACE_CTOR(balance_t, "const unsigned long"); amounts.insert - (amounts_map::value_type(amount_t::current_pool->null_commodity, val)); + (amounts_map::value_type(commodity_pool_t::current_pool->null_commodity, val)); } balance_t::balance_t(const long val) { TRACE_CTOR(balance_t, "const long"); amounts.insert - (amounts_map::value_type(amount_t::current_pool->null_commodity, val)); + (amounts_map::value_type(commodity_pool_t::current_pool->null_commodity, val)); } balance_t& balance_t::operator+=(const balance_t& bal) diff --git a/src/commodity.cc b/src/commodity.cc index b76c7896..79ed77fe 100644 --- a/src/commodity.cc +++ b/src/commodity.cc @@ -600,11 +600,11 @@ bool compare_amount_commodities::operator()(const amount_t * left, if (cmp != 0) return cmp < 0; - if (! leftcomm.is_annotated()) { - return rightcomm.is_annotated(); + if (! leftcomm.has_annotation()) { + return rightcomm.has_annotation(); } - else if (! rightcomm.is_annotated()) { - return ! leftcomm.is_annotated(); + else if (! rightcomm.has_annotation()) { + return ! leftcomm.has_annotation(); } else { annotated_commodity_t& aleftcomm(static_cast<annotated_commodity_t&>(leftcomm)); @@ -675,7 +675,7 @@ void to_xml(std::ostream& out, const commodity_t& comm, } if (commodity_details) { - if (comm.is_annotated()) + if (comm.has_annotation()) to_xml(out, as_annotated_commodity(comm).details); if (comm.varied_history()) { diff --git a/src/commodity.h b/src/commodity.h index 42cc6d8f..d5f18844 100644 --- a/src/commodity.h +++ b/src/commodity.h @@ -251,7 +251,7 @@ public: return *this; } - bool is_annotated() const { + bool has_annotation() const { return annotated; } diff --git a/src/journal.cc b/src/journal.cc index 2366ce30..6ebccd66 100644 --- a/src/journal.cc +++ b/src/journal.cc @@ -77,7 +77,6 @@ journal_t::~journal_t() checked_delete(xact); checked_delete(master); - commodity_pool.reset(); } void journal_t::initialize() @@ -85,21 +84,6 @@ void journal_t::initialize() master = new account_t; bucket = NULL; was_loaded = false; - - commodity_pool.reset(new commodity_pool_t); - - // Add time commodity conversions, so that timelog's may be parsed - // in terms of seconds, but reported as minutes or hours. - if (commodity_t * commodity = commodity_pool->create("s")) - commodity->add_flags(COMMODITY_BUILTIN | COMMODITY_NOMARKET); - else - assert(false); - - // Add a "percentile" commodity - if (commodity_t * commodity = commodity_pool->create("%")) - commodity->add_flags(COMMODITY_BUILTIN | COMMODITY_NOMARKET); - else - assert(false); } void journal_t::add_account(account_t * acct) diff --git a/src/journal.h b/src/journal.h index f7124736..8d59e3b4 100644 --- a/src/journal.h +++ b/src/journal.h @@ -47,7 +47,6 @@ namespace ledger { -class commodity_pool_t; class xact_base_t; class xact_t; class auto_xact_t; @@ -112,8 +111,6 @@ public: std::list<fileinfo_t> sources; bool was_loaded; - shared_ptr<commodity_pool_t> commodity_pool; - journal_t(); journal_t(const path& pathname); journal_t(const string& str); @@ -624,7 +624,7 @@ bool expr_t::op_t::print(std::ostream& out, const context_t& context) const } if (! symbol.empty()) { - if (amount_t::current_pool->find(symbol)) + if (commodity_pool_t::current_pool->find(symbol)) out << '@'; out << symbol; } diff --git a/src/pool.cc b/src/pool.cc index b08c8fad..9e06613f 100644 --- a/src/pool.cc +++ b/src/pool.cc @@ -39,6 +39,8 @@ namespace ledger { +shared_ptr<commodity_pool_t> commodity_pool_t::current_pool; + commodity_pool_t::commodity_pool_t() : default_commodity(NULL), keep_base(false), quote_leeway(86400), get_quotes(false), @@ -318,8 +320,7 @@ optional<price_point_t> commodity_pool_t::parse_price_directive(char * line) VERIFY(point.price.valid()); DEBUG("commodity.download", "Looking up symbol: " << symbol); - if (commodity_t * commodity = - amount_t::current_pool->find_or_create(symbol)) { + if (commodity_t * commodity = find_or_create(symbol)) { DEBUG("commodity.download", "Adding price for " << symbol << ": " << point.when << " " << point.price); commodity->add_price(point.when, point.price, true); @@ -57,6 +57,7 @@ struct cost_breakdown_t class commodity_pool_t : public noncopyable { +public: /** * The commodities collection in commodity_pool_t maintains pointers to all * the commodities which have ever been created by the user, whether @@ -65,7 +66,6 @@ class commodity_pool_t : public noncopyable */ typedef std::map<string, commodity_t *> commodities_map; -public: commodities_map commodities; commodity_t * null_commodity; commodity_t * default_commodity; @@ -76,6 +76,8 @@ public: long quote_leeway; // --leeway= bool get_quotes; // --download + static shared_ptr<commodity_pool_t> current_pool; + function<optional<price_point_t> (commodity_t& commodity, const optional<commodity_t&>& in_terms_of)> get_commodity_quote; @@ -136,6 +138,7 @@ private: template<class Archive> void serialize(Archive& ar, const unsigned int /* version */) { + ar & current_pool; ar & commodities; ar & null_commodity; ar & default_commodity; diff --git a/src/py_amount.cc b/src/py_amount.cc index b44f3716..2307c454 100644 --- a/src/py_amount.cc +++ b/src/py_amount.cc @@ -44,27 +44,14 @@ using namespace boost::python; namespace { - boost::optional<amount_t> py_value_0(const amount_t& amount) { - return amount.value(); - } - boost::optional<amount_t> py_value_1(const amount_t& amount, - const bool primary_only) { - return amount.value(primary_only); - } - boost::optional<amount_t> - py_value_2(const amount_t& amount, - const bool primary_only, - const boost::optional<datetime_t>& moment) { - return amount.value(primary_only, moment); + py_value_1(const amount_t& amount, commodity_t& in_terms_of) { + return amount.value(false, CURRENT_TIME(), in_terms_of); } - boost::optional<amount_t> - py_value_3(const amount_t& amount, - const bool primary_only, - const boost::optional<datetime_t>& moment, - const boost::optional<commodity_t&>& in_terms_of) { - return amount.value(primary_only, moment, in_terms_of); + py_value_2(const amount_t& amount, commodity_t& in_terms_of, + datetime_t& moment) { + return amount.value(false, moment, in_terms_of); } void py_parse_2(amount_t& amount, object in, unsigned char flags) { @@ -97,8 +84,15 @@ namespace { } } - void py_amount_initialize() { - amount_t::initialize(); + annotation_t& py_amount_annotation(amount_t& amount) { + return amount.annotation(); + } + + amount_t py_strip_annotations_0(amount_t& amount) { + return amount.strip_annotations(keep_details_t()); + } + amount_t py_strip_annotations_1(amount_t& amount, const keep_details_t& keep) { + return amount.strip_annotations(keep); } } // unnamed namespace @@ -113,11 +107,7 @@ EXC_TRANSLATOR(amount_error) void export_amount() { class_< amount_t > ("Amount") - .add_static_property("current_pool", - make_getter(&amount_t::current_pool, - return_internal_reference<>())) - - .def("initialize", py_amount_initialize) // only for the PyUnitTests + .def("initialize", &amount_t::initialize) // only for the PyUnitTests .staticmethod("initialize") .def("shutdown", &amount_t::shutdown) .staticmethod("shutdown") @@ -196,11 +186,11 @@ internal precision.")) .def(self / long()) .def(long() / self) - .def("precision", &amount_t::precision) - .def("keep_precision", &amount_t::keep_precision) - .def("set_keep_precision", &amount_t::set_keep_precision, args("keep"), - _("Set whether an amount should always display at full precision.")) - .def("display_precision", &amount_t::display_precision) + .add_property("precision", &amount_t::precision) + .add_property("display_precision", &amount_t::display_precision) + .add_property("keep_precision", + &amount_t::keep_precision, + &amount_t::set_keep_precision) .def("negated", &amount_t::negated) .def("in_place_negate", &amount_t::in_place_negate, @@ -236,10 +226,8 @@ internal precision.")) .def("in_place_unreduce", &amount_t::in_place_unreduce, return_internal_reference<>()) - .def("value", py_value_0) - .def("value", py_value_1, args("primary_only")) - .def("value", py_value_2, args("primary_only", "moment")) - .def("value", py_value_3, args("primary_only", "moment", "in_terms_of")) + .def("value", py_value_1, args("moment")) + .def("value", py_value_2, args("in_terms_of", "moment")) .def("price", &amount_t::price) @@ -262,21 +250,23 @@ internal precision.")) .def("__repr__", &amount_t::to_fullstring) .def("quantity_string", &amount_t::quantity_string) - .def("commodity", &amount_t::commodity, - return_internal_reference<>()) + .add_property("commodity", + make_function(&amount_t::commodity, + return_value_policy<reference_existing_object>()), + make_function(&amount_t::set_commodity, + with_custodian_and_ward<1, 2>())) .def("has_commodity", &amount_t::has_commodity) - .def("set_commodity", &amount_t::set_commodity, - with_custodian_and_ward<1, 2>()) .def("clear_commodity", &amount_t::clear_commodity) .def("number", &amount_t::number) .def("annotate", &amount_t::annotate) - .def("is_annotated", &amount_t::is_annotated) -#if 0 - .def("annotation", &amount_t::annotation) -#endif - .def("strip_annotations", &amount_t::strip_annotations) + .def("has_annotation", &amount_t::has_annotation) + .add_property("annotation", + make_function(py_amount_annotation, + return_internal_reference<>())) + .def("strip_annotations", py_strip_annotations_0) + .def("strip_annotations", py_strip_annotations_1) .def("parse", py_parse_1) .def("parse", py_parse_2) diff --git a/src/py_balance.cc b/src/py_balance.cc index 23a2ff73..5aed2b43 100644 --- a/src/py_balance.cc +++ b/src/py_balance.cc @@ -108,6 +108,13 @@ namespace { return (*elem).second; } + balance_t py_strip_annotations_0(balance_t& balance) { + return balance.strip_annotations(keep_details_t()); + } + balance_t py_strip_annotations_1(balance_t& balance, const keep_details_t& keep) { + return balance.strip_annotations(keep); + } + } // unnamed namespace #define EXC_TRANSLATOR(type) \ @@ -215,7 +222,8 @@ void export_balance() .def("number", &balance_t::number) - .def("strip_annotations", &balance_t::strip_annotations) + .def("strip_annotations", py_strip_annotations_0) + .def("strip_annotations", py_strip_annotations_1) .def("valid", &balance_t::valid) ; diff --git a/src/py_commodity.cc b/src/py_commodity.cc index 08af8f62..dfaf7f5b 100644 --- a/src/py_commodity.cc +++ b/src/py_commodity.cc @@ -32,6 +32,7 @@ #include <system.hh> #include "pyinterp.h" +#include "pyutils.h" #include "commodity.h" #include "annotate.h" #include "pool.h" @@ -81,6 +82,12 @@ namespace { // Exchange one commodity for another, while recording the factored price. + void py_exchange_2(commodity_pool_t& pool, + commodity_t& commodity, + const amount_t& per_unit_cost) + { + pool.exchange(commodity, per_unit_cost, CURRENT_TIME()); + } void py_exchange_3(commodity_pool_t& pool, commodity_t& commodity, const amount_t& per_unit_cost, @@ -99,6 +106,77 @@ namespace { return pool.exchange(amount, cost, is_per_unit, moment, tag); } + commodity_t * py_pool_getitem(commodity_pool_t& pool, const string& symbol) + { + commodity_pool_t::commodities_map::iterator i = + pool.commodities.find(symbol); + if (i == pool.commodities.end()) { + PyErr_SetString(PyExc_ValueError, + (string("Could not find commodity ") + symbol).c_str()); + throw boost::python::error_already_set(); + } + return (*i).second; + } + + python::list py_pool_keys(commodity_pool_t& pool) { + python::list keys; + BOOST_REVERSE_FOREACH + (const commodity_pool_t::commodities_map::value_type& pair, + pool.commodities) { + keys.insert(0, pair.first); + } + return keys; + } + + bool py_pool_contains(commodity_pool_t& pool, const string& symbol) { + return pool.commodities.find(symbol) != pool.commodities.end(); + } + + commodity_pool_t::commodities_map::iterator + py_pool_commodities_begin(commodity_pool_t& pool) { + return pool.commodities.begin(); + } + commodity_pool_t::commodities_map::iterator + py_pool_commodities_end(commodity_pool_t& pool) { + return pool.commodities.end(); + } + + typedef transform_iterator + <function<string(commodity_pool_t::commodities_map::value_type&)>, + commodity_pool_t::commodities_map::iterator> + commodities_map_firsts_iterator; + commodities_map_firsts_iterator + + py_pool_commodities_keys_begin(commodity_pool_t& pool) { + return make_transform_iterator + (pool.commodities.begin(), + bind(&commodity_pool_t::commodities_map::value_type::first, _1)); + } + commodities_map_firsts_iterator + py_pool_commodities_keys_end(commodity_pool_t& pool) { + return make_transform_iterator + (pool.commodities.end(), + bind(&commodity_pool_t::commodities_map::value_type::first, _1)); + } + + typedef transform_iterator + <function<commodity_t *(commodity_pool_t::commodities_map::value_type&)>, + commodity_pool_t::commodities_map::iterator> + commodities_map_seconds_iterator; + + commodities_map_seconds_iterator + py_pool_commodities_values_begin(commodity_pool_t& pool) { + return make_transform_iterator + (pool.commodities.begin(), + bind(&commodity_pool_t::commodities_map::value_type::second, _1)); + } + commodities_map_seconds_iterator + py_pool_commodities_values_end(commodity_pool_t& pool) { + return make_transform_iterator + (pool.commodities.end(), + bind(&commodity_pool_t::commodities_map::value_type::second, _1)); + } + void py_add_price_2(commodity_t& commodity, const datetime_t& date, const amount_t& price) { commodity.add_price(date, price); @@ -123,16 +201,38 @@ namespace { return details.keep_any(comm); } + commodity_t& py_commodity_referent(commodity_t& comm) { + return comm.referent(); + } + commodity_t& py_annotated_commodity_referent(annotated_commodity_t& comm) { + return comm.referent(); + } + + commodity_t& py_strip_annotations_0(commodity_t& comm) { + return comm.strip_annotations(keep_details_t()); + } + commodity_t& py_strip_annotations_1(commodity_t& comm, + const keep_details_t& keep) { + return comm.strip_annotations(keep); + } + + commodity_t& py_strip_ann_annotations_0(annotated_commodity_t& comm) { + return comm.strip_annotations(keep_details_t()); + } + commodity_t& py_strip_ann_annotations_1(annotated_commodity_t& comm, + const keep_details_t& keep) { + return comm.strip_annotations(keep); + } + } // unnamed namespace void export_commodity() { - class_< commodity_pool_t, boost::noncopyable > ("CommodityPool", no_init) + class_< commodity_pool_t, shared_ptr<commodity_pool_t>, + boost::noncopyable > ("CommodityPool", no_init) .add_property("null_commodity", make_getter(&commodity_pool_t::null_commodity, - return_internal_reference<>()), - make_setter(&commodity_pool_t::null_commodity, - with_custodian_and_ward<1, 2>())) + return_internal_reference<>())) .add_property("default_commodity", make_getter(&commodity_pool_t::default_commodity, return_internal_reference<>()), @@ -157,25 +257,46 @@ void export_commodity() .def("make_qualified_name", &commodity_pool_t::make_qualified_name) - .def("create", py_create_1, return_internal_reference<>()) - .def("create", py_create_2, return_internal_reference<>()) + .def("create", py_create_1, + return_value_policy<reference_existing_object>()) + .def("create", py_create_2, + return_value_policy<reference_existing_object>()) .def("find_or_create", py_find_or_create_1, - return_internal_reference<>()) + return_value_policy<reference_existing_object>()) .def("find_or_create", py_find_or_create_2, - return_internal_reference<>()) + return_value_policy<reference_existing_object>()) - .def("find", py_find_1, return_internal_reference<>()) - .def("find", py_find_2, return_internal_reference<>()) + .def("find", py_find_1, return_value_policy<reference_existing_object>()) + .def("find", py_find_2, return_value_policy<reference_existing_object>()) + .def("exchange", py_exchange_2, with_custodian_and_ward<1, 2>()) .def("exchange", py_exchange_3, with_custodian_and_ward<1, 2>()) .def("exchange", py_exchange_5) .def("parse_price_directive", &commodity_pool_t::parse_price_directive) .def("parse_price_expression", &commodity_pool_t::parse_price_expression, - return_internal_reference<>()) + return_value_policy<reference_existing_object>()) + + .def("__getitem__", py_pool_getitem, + return_value_policy<reference_existing_object>()) + .def("keys", py_pool_keys) + .def("has_key", py_pool_contains) + .def("__contains__", py_pool_contains) + .def("__iter__", range<return_value_policy<reference_existing_object> > + (py_pool_commodities_begin, py_pool_commodities_end)) + .def("iteritems", range<return_value_policy<reference_existing_object> > + (py_pool_commodities_begin, py_pool_commodities_end)) + .def("iterkeys", range<>(py_pool_commodities_keys_begin, + py_pool_commodities_keys_end)) + .def("itervalues", range<return_value_policy<reference_existing_object> > + (py_pool_commodities_values_begin, py_pool_commodities_values_end)) ; + map_value_type_converter<commodity_pool_t::commodities_map>(); + + scope().attr("commodity_pool") = commodity_pool_t::current_pool; + scope().attr("COMMODITY_STYLE_DEFAULTS") = COMMODITY_STYLE_DEFAULTS; scope().attr("COMMODITY_STYLE_SUFFIXED") = COMMODITY_STYLE_SUFFIXED; scope().attr("COMMODITY_STYLE_SEPARATED") = COMMODITY_STYLE_SEPARATED; @@ -209,33 +330,30 @@ void export_commodity() .def("symbol_needs_quotes", &commodity_t::symbol_needs_quotes) .staticmethod("symbol_needs_quotes") -#if 0 - .def("referent", &commodity_t::referent, - return_internal_reference<>()) -#endif + .add_property("referent", + make_function(py_commodity_referent, + return_value_policy<reference_existing_object>())) - .def("is_annotated", &commodity_t::is_annotated) - .def("strip_annotations", &commodity_t::strip_annotations, - return_internal_reference<>()) + .def("has_annotation", &commodity_t::has_annotation) + .def("strip_annotations", py_strip_annotations_0, + return_value_policy<reference_existing_object>()) + .def("strip_annotations", py_strip_annotations_1, + return_value_policy<reference_existing_object>()) .def("write_annotations", &commodity_t::write_annotations) .def("pool", &commodity_t::pool, - return_internal_reference<>()) - - .def("base_symbol", &commodity_t::base_symbol) - .def("symbol", &commodity_t::symbol) - .def("mapping_key", &commodity_t::mapping_key) - - .def("name", &commodity_t::name) - .def("set_name", &commodity_t::set_name) - .def("note", &commodity_t::note) - .def("set_note", &commodity_t::set_note) - .def("precision", &commodity_t::precision) - .def("set_precision", &commodity_t::set_precision) - .def("smaller", &commodity_t::smaller) - .def("set_smaller", &commodity_t::set_smaller) - .def("larger", &commodity_t::larger) - .def("set_larger", &commodity_t::set_larger) + return_value_policy<reference_existing_object>()) + + .add_property("base_symbol", &commodity_t::base_symbol) + .add_property("symbol", &commodity_t::symbol) + .add_property("mapping_key", &commodity_t::mapping_key) + + .add_property("name", &commodity_t::name, &commodity_t::set_name) + .add_property("note", &commodity_t::note, &commodity_t::set_note) + .add_property("precision", &commodity_t::precision, + &commodity_t::set_precision) + .add_property("smaller", &commodity_t::smaller, &commodity_t::set_smaller) + .add_property("larger", &commodity_t::larger, &commodity_t::set_larger) .def("add_price", py_add_price_2) .def("add_price", py_add_price_3) @@ -306,13 +424,14 @@ void export_commodity() .def(self == self) .def(self == other<commodity_t>()) -#if 0 - .def("referent", &annotated_commodity_t::referent, - return_internal_reference<>()) -#endif + .add_property("referent", + make_function(py_annotated_commodity_referent, + return_value_policy<reference_existing_object>())) - .def("strip_annotations", &annotated_commodity_t::strip_annotations, - return_internal_reference<>()) + .def("strip_annotations", py_strip_ann_annotations_0, + return_value_policy<reference_existing_object>()) + .def("strip_annotations", py_strip_ann_annotations_1, + return_value_policy<reference_existing_object>()) .def("write_annotations", &annotated_commodity_t::write_annotations) ; } diff --git a/src/py_journal.cc b/src/py_journal.cc index 7e9f8a1b..5be9cbe1 100644 --- a/src/py_journal.cc +++ b/src/py_journal.cc @@ -52,10 +52,6 @@ namespace { return *journal.master; } - commodity_pool_t& py_commodity_pool(journal_t& journal) { - return *journal.commodity_pool; - } - long xacts_len(journal_t& journal) { return journal.xacts.size(); @@ -276,10 +272,6 @@ void export_journal() with_custodian_and_ward_postcall<1, 0> >()), make_setter(&journal_t::bucket)) .add_property("was_loaded", make_getter(&journal_t::was_loaded)) - .add_property("commodity_pool", - make_getter(&journal_t::commodity_pool, - return_internal_reference<1, - with_custodian_and_ward_postcall<1, 0> >())) .def("add_account", &journal_t::add_account) .def("remove_account", &journal_t::remove_account) diff --git a/src/py_value.cc b/src/py_value.cc index ee039519..98f3f079 100644 --- a/src/py_value.cc +++ b/src/py_value.cc @@ -47,7 +47,8 @@ BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(set_string_overloads, set_string, 0, 2) namespace { - PyObject * py_base_type(value_t& value) { + PyObject * py_base_type(value_t& value) + { if (value.is_boolean()) { return (PyObject *)&PyBool_Type; } @@ -63,16 +64,6 @@ namespace { } } - expr_t py_value_getattr(const value_t& value, const string& name) - { - if (value.is_scope()) { - if (scope_t * scope = value.as_scope()) - return expr_t(scope->lookup(symbol_t::FUNCTION, name), scope); - } - throw_(value_error, _("Cannot lookup attributes in %1") << value.label()); - return expr_t(); - } - string py_dump(const value_t& value) { std::ostringstream buf; value.dump(buf); @@ -85,10 +76,20 @@ namespace { return buf.str(); } - void py_set_string(value_t& amount, const string& str) { - return amount.set_string(str); + void py_set_string(value_t& value, const string& str) { + return value.set_string(str); } + annotation_t& py_value_annotation(value_t& value) { + return value.annotation(); + } + + value_t py_strip_annotations_0(value_t& value) { + return value.strip_annotations(keep_details_t()); + } + value_t py_strip_annotations_1(value_t& value, const keep_details_t& keep) { + return value.strip_annotations(keep); + } } // unnamed namespace #define EXC_TRANSLATOR(type) \ @@ -306,16 +307,16 @@ void export_value() .def("number", &value_t::number) .def("annotate", &value_t::annotate) - .def("is_annotated", &value_t::is_annotated) -#if 0 - .def("annotation", &value_t::annotation) -#endif - .def("strip_annotations", &value_t::strip_annotations) + .def("has_annotation", &value_t::has_annotation) + .add_property("annotation", + make_function(py_value_annotation, + return_internal_reference<>())) + .def("strip_annotations", py_strip_annotations_0) + .def("strip_annotations", py_strip_annotations_1) #if 0 .def("__getitem__", &value_t::operator[]) #endif - .def("__getattr__", py_value_getattr) .def("push_back", &value_t::push_back) .def("pop_back", &value_t::pop_back) .def("size", &value_t::size) diff --git a/src/pyutils.h b/src/pyutils.h index a9e968e0..d8a46527 100644 --- a/src/pyutils.h +++ b/src/pyutils.h @@ -106,6 +106,26 @@ struct register_optional_to_python : public boost::noncopyable } }; +template <typename T1, typename T2> +struct PairToTupleConverter +{ + static PyObject * convert(const std::pair<T1, T2>& pair) { + return boost::python::incref + (boost::python::make_tuple(pair.first, pair.second).ptr()); + } +}; + +template <typename MapType> +struct map_value_type_converter +{ + map_value_type_converter() { + boost::python::to_python_converter + <typename MapType::value_type, + PairToTupleConverter<const typename MapType::key_type, + typename MapType::mapped_type> >(); + } +}; + namespace boost { namespace python { // Use expr to create the PyObject corresponding to x diff --git a/src/quotes.cc b/src/quotes.cc index 7f41e4ff..ffe2142a 100644 --- a/src/quotes.cc +++ b/src/quotes.cc @@ -76,13 +76,13 @@ commodity_quote_from_script(commodity_t& commodity, DEBUG("commodity.download", "downloaded quote: " << buf); if (optional<price_point_t> point = - amount_t::current_pool->parse_price_directive(buf)) { - if (amount_t::current_pool->price_db) { + commodity_pool_t::current_pool->parse_price_directive(buf)) { + if (commodity_pool_t::current_pool->price_db) { #if defined(__GNUG__) && __GNUG__ < 3 - ofstream database(*amount_t::current_pool->price_db, + ofstream database(*commodity_pool_t::current_pool->price_db, ios::out | ios::app); #else - ofstream database(*amount_t::current_pool->price_db, + ofstream database(*commodity_pool_t::current_pool->price_db, std::ios_base::out | std::ios_base::app); #endif database << "P " diff --git a/src/report.cc b/src/report.cc index 7da44f8c..4c157312 100644 --- a/src/report.cc +++ b/src/report.cc @@ -69,18 +69,17 @@ void report_t::normalize_options(const string& verb) item_t::use_effective_date = (HANDLED(effective) && ! HANDLED(actual_dates)); - session.journal->commodity_pool->keep_base = HANDLED(base); - session.journal->commodity_pool->get_quotes = session.HANDLED(download); + commodity_pool_t::current_pool->keep_base = HANDLED(base); + commodity_pool_t::current_pool->get_quotes = session.HANDLED(download); if (session.HANDLED(price_exp_)) - session.journal->commodity_pool->quote_leeway = + commodity_pool_t::current_pool->quote_leeway = session.HANDLER(price_exp_).value.as_long(); if (session.HANDLED(price_db_)) - session.journal->commodity_pool->price_db = - session.HANDLER(price_db_).str(); + commodity_pool_t::current_pool->price_db = session.HANDLER(price_db_).str(); else - session.journal->commodity_pool->price_db = none; + commodity_pool_t::current_pool->price_db = none; if (HANDLED(date_format_)) set_date_format(HANDLER(date_format_).str().c_str()); @@ -522,7 +521,7 @@ value_t report_t::fn_price(call_scope_t& scope) value_t report_t::fn_lot_date(call_scope_t& scope) { interactive_t args(scope, "v"); - if (args.value_at(0).is_annotated()) { + if (args.value_at(0).has_annotation()) { const annotation_t& details(args.value_at(0).annotation()); if (details.date) return *details.date; @@ -533,7 +532,7 @@ value_t report_t::fn_lot_date(call_scope_t& scope) value_t report_t::fn_lot_price(call_scope_t& scope) { interactive_t args(scope, "v"); - if (args.value_at(0).is_annotated()) { + if (args.value_at(0).has_annotation()) { const annotation_t& details(args.value_at(0).annotation()); if (details.price) return *details.price; @@ -544,7 +543,7 @@ value_t report_t::fn_lot_price(call_scope_t& scope) value_t report_t::fn_lot_tag(call_scope_t& scope) { interactive_t args(scope, "v"); - if (args.value_at(0).is_annotated()) { + if (args.value_at(0).has_annotation()) { const annotation_t& details(args.value_at(0).annotation()); if (details.tag) return string_value(*details.tag); diff --git a/src/session.cc b/src/session.cc index 0d6a6829..1882e554 100644 --- a/src/session.cc +++ b/src/session.cc @@ -45,7 +45,7 @@ void set_session_context(session_t * session) { if (session) { times_initialize(); - amount_t::initialize(session->journal->commodity_pool); + amount_t::initialize(); amount_t::parse_conversion("1.0m", "60s"); amount_t::parse_conversion("1.0h", "60m"); @@ -179,7 +179,7 @@ void session_t::close_journal_files() amount_t::shutdown(); journal.reset(new journal_t); - amount_t::initialize(journal->commodity_pool); + amount_t::initialize(); } option_t<session_t> * session_t::lookup_option(const char * p) diff --git a/src/textual.cc b/src/textual.cc index aec7dbda..071e111d 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -490,7 +490,7 @@ void instance_t::default_commodity_directive(char * line) { amount_t amt(skip_ws(line + 1)); VERIFY(amt.valid()); - amount_t::current_pool->default_commodity = &amt.commodity(); + commodity_pool_t::current_pool->default_commodity = &amt.commodity(); amt.commodity().add_flags(COMMODITY_KNOWN); } @@ -511,7 +511,7 @@ void instance_t::price_conversion_directive(char * line) void instance_t::price_xact_directive(char * line) { optional<price_point_t> point = - amount_t::current_pool->parse_price_directive(skip_ws(line + 1)); + commodity_pool_t::current_pool->parse_price_directive(skip_ws(line + 1)); if (! point) throw parse_error(_("Pricing entry failed to parse")); } @@ -523,7 +523,7 @@ void instance_t::nomarket_directive(char * line) commodity_t::parse_symbol(p, symbol); if (commodity_t * commodity = - amount_t::current_pool->find_or_create(symbol)) + commodity_pool_t::current_pool->find_or_create(symbol)) commodity->add_flags(COMMODITY_NOMARKET | COMMODITY_KNOWN); } diff --git a/src/value.cc b/src/value.cc index f4df3329..e2e748f4 100644 --- a/src/value.cc +++ b/src/value.cc @@ -1353,7 +1353,8 @@ value_t value_t::exchange_commodities(const std::string& commodities, p; p = std::strtok(NULL, ",")) { if (commodity_t * commodity = - amount_t::current_pool->parse_price_expression(p, add_prices, moment)) { + commodity_pool_t::current_pool->parse_price_expression(p, add_prices, + moment)) { value_t result = value(false, moment, *commodity); if (! result.is_null()) return result; @@ -1523,10 +1524,10 @@ void value_t::annotate(const annotation_t& details) throw_(value_error, _("Cannot annotate %1") << label()); } -bool value_t::is_annotated() const +bool value_t::has_annotation() const { if (is_amount()) - return as_amount().is_annotated(); + return as_amount().has_annotation(); else throw_(value_error, _("Cannot determine whether %1 is annotated") << label()); diff --git a/src/value.h b/src/value.h index 2a420cd3..ffbb89e8 100644 --- a/src/value.h +++ b/src/value.h @@ -774,7 +774,7 @@ public: * Annotated commodity methods. */ void annotate(const annotation_t& details); - bool is_annotated() const; + bool has_annotation() const; annotation_t& annotation(); const annotation_t& annotation() const { diff --git a/src/xact.cc b/src/xact.cc index f2694976..623c5772 100644 --- a/src/xact.cc +++ b/src/xact.cc @@ -122,7 +122,7 @@ bool xact_base_t::finalize() amount_t& p(post->cost ? *post->cost : post->amount); if (! p.is_null()) { DEBUG("xact.finalize", "post must balance = " << p.reduced()); - if (! post->cost && post->amount.is_annotated() && + if (! post->cost && post->amount.has_annotation() && post->amount.annotation().price) { // If the amount has no cost, but is annotated with a per-unit // price, use the price times the amount as the cost @@ -221,7 +221,7 @@ bool xact_base_t::finalize() foreach (post_t * post, posts) { if (! post->amount.is_null()) { - if (post->amount.is_annotated()) + if (post->amount.has_annotation()) top_post = post; else if (! top_post) top_post = post; @@ -260,7 +260,7 @@ bool xact_base_t::finalize() foreach (post_t * post, posts) { if (post != top_post && post->must_balance() && ! post->amount.is_null() && - post->amount.is_annotated() && + post->amount.has_annotation() && post->amount.annotation().price) { amount_t temp = *post->amount.annotation().price * post->amount; if (total_cost.is_null()) { @@ -309,10 +309,11 @@ bool xact_base_t::finalize() _("A posting's cost must be of a different commodity than its amount")); cost_breakdown_t breakdown = - amount_t::current_pool->exchange(post->amount, *post->cost, false, - datetime_t(date(), time_duration(0, 0, 0, 0))); + commodity_pool_t::current_pool->exchange + (post->amount, *post->cost, false, + datetime_t(date(), time_duration(0, 0, 0, 0))); - if (post->amount.is_annotated() && + if (post->amount.has_annotation() && breakdown.basis_cost.commodity() == breakdown.final_cost.commodity()) { if (amount_t gain_loss = (breakdown.basis_cost - diff --git a/test/convert.py b/test/convert.py index 0c64fde4..05a62b7e 100755 --- a/test/convert.py +++ b/test/convert.py @@ -158,6 +158,8 @@ for line in fd.readlines(): line = re.sub('false', 'False', line) line = re.sub('CURRENT_TIME\(\)', 'datetime.now()', line) line = re.sub('CURRENT_DATE\(\)', 'date.today()', line) + line = re.sub('commodity\(\)', 'commodity', line) + line = re.sub('precision\(\)', 'precision', line) line = re.sub('([0-9]+)[FL]', '\\1', line) line = re.sub('([0-9]+)UL', '\\1L', line) line = re.sub(';', '', line) diff --git a/test/regress/25A099C9.test b/test/regress/25A099C9.test index 604939d8..dec51008 100644 --- a/test/regress/25A099C9.test +++ b/test/regress/25A099C9.test @@ -4,16 +4,16 @@ >>>2 While parsing file "$sourcepath/src/amount.h", line 67: Error: No quantity specified for amount -While parsing file "$sourcepath/src/amount.h", line 721: +While parsing file "$sourcepath/src/amount.h", line 717: Error: Invalid date/time: line amount_t amoun -While parsing file "$sourcepath/src/amount.h", line 727: +While parsing file "$sourcepath/src/amount.h", line 723: Error: Invalid date/time: line string amount_ -While parsing file "$sourcepath/src/amount.h", line 733: +While parsing file "$sourcepath/src/amount.h", line 729: Error: Invalid date/time: line string amount_ -While parsing file "$sourcepath/src/amount.h", line 739: +While parsing file "$sourcepath/src/amount.h", line 735: Error: Invalid date/time: line string amount_ -While parsing file "$sourcepath/src/amount.h", line 745: +While parsing file "$sourcepath/src/amount.h", line 741: Error: Invalid date/time: line std::ostream& -While parsing file "$sourcepath/src/amount.h", line 752: +While parsing file "$sourcepath/src/amount.h", line 748: Error: Invalid date/time: line std::istream& === 7 |