From f29fc1eb12376f1bb447f8e397fb98daa7ac3327 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 29 Feb 2012 22:13:07 -0600 Subject: Added skeletons for Python unit tests --- tools/Makefile.am | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'tools/Makefile.am') diff --git a/tools/Makefile.am b/tools/Makefile.am index 5bb41cc0..fe0681e5 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -321,7 +321,7 @@ all_py_tests_sources = \ test/python/%.py: test/unit/%.cc test/convert.py $(PYTHON) $(srcdir)/test/convert.py $< $@ -test/python/UnitTests.py: $(all_py_tests_sources) +test/python/ConvertedTests.py: $(all_py_tests_sources) @echo "from unittest import TextTestRunner, TestSuite" > $@ @for file in $$(ls $(srcdir)/test/unit/*.cc); do \ base=$$(basename $$file); \ @@ -346,7 +346,7 @@ ESC_distdir=`echo "$(distdir)" | sed 's/\//\\\\\//g'` # jww (2007-05-10): This rule will not be triggered on systems that # define an EXEEXT. -PyUnitTests: test/PyUnitTests.py test/python/UnitTests.py +PyUnitTests: test/PyUnitTests.py test/python/ConvertedTests.py @cat $(srcdir)/test/PyUnitTests.py \ | sed "s/%python%/$(ESC_python)/" \ | sed "s/%srcdir%/$(ESC_srcdir)/g" \ -- cgit v1.2.3 From 9ec9cdf41e5176f7fcf06da5f75593d9ba3d4028 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Thu, 1 Mar 2012 05:50:07 -0600 Subject: Started writing Python unit tests --- src/error.h | 6 +++ src/global.h | 2 +- src/main.cc | 7 ++-- src/py_amount.cc | 2 +- src/py_commodity.cc | 40 +++++++++----------- src/py_journal.cc | 93 ++++++++++++++++++++++------------------------ src/py_post.cc | 2 +- src/py_session.cc | 76 +++++++++++++++++++++++++++++++++++++ src/py_xact.cc | 8 ++++ src/pyinterp.cc | 2 + src/session.cc | 39 +++++++++++++++++-- src/session.h | 4 +- src/system.hh.in | 2 + src/textual.cc | 2 +- src/utils.cc | 3 +- test/python/JournalTest.py | 25 ++++++++----- tools/Makefile.am | 1 + 17 files changed, 220 insertions(+), 94 deletions(-) create mode 100644 src/py_session.cc (limited to 'tools/Makefile.am') diff --git a/src/error.h b/src/error.h index 7630f017..86d9de76 100644 --- a/src/error.h +++ b/src/error.h @@ -101,6 +101,12 @@ string source_context(const path& file, virtual ~name() throw() {} \ } +struct error_count { + std::size_t count; + explicit error_count(std::size_t _count) : count(_count) {} + const char * what() const { return ""; } +}; + } // namespace ledger #endif // _ERROR_H diff --git a/src/global.h b/src/global.h index 28bffc3a..2cb7842e 100644 --- a/src/global.h +++ b/src/global.h @@ -155,7 +155,7 @@ See LICENSE file included with the distribution for details and disclaimer."); OPTION_(global_scope_t, version, DO() { // -v parent->show_version_info(std::cout); - throw int(0); // exit immediately + throw error_count(0); // exit immediately }); }; diff --git a/src/main.cc b/src/main.cc index 2202a5de..aafbdbcb 100644 --- a/src/main.cc +++ b/src/main.cc @@ -191,9 +191,10 @@ int main(int argc, char * argv[], char * envp[]) std::cerr << "Exception during initialization: " << err.what() << std::endl; } - catch (int _status) { - status = _status; // used for a "quick" exit, and is used only - // if help text (such as --help) was displayed + catch (const error_count& errors) { + // used for a "quick" exit, and is used only if help text (such as + // --help) was displayed + status = static_cast(errors.count); } // If memory verification is being performed (which can be very slow), clean diff --git a/src/py_amount.cc b/src/py_amount.cc index f10595e8..25ec8e26 100644 --- a/src/py_amount.cc +++ b/src/py_amount.cc @@ -263,7 +263,7 @@ internal precision.")) .add_property("commodity", make_function(&amount_t::commodity, - return_value_policy()), + return_internal_reference<>()), make_function(&amount_t::set_commodity, with_custodian_and_ward<1, 2>())) .def("has_commodity", &amount_t::has_commodity) diff --git a/src/py_commodity.cc b/src/py_commodity.cc index ffa903f4..b5230850 100644 --- a/src/py_commodity.cc +++ b/src/py_commodity.cc @@ -269,18 +269,14 @@ void export_commodity() .def("make_qualified_name", &commodity_pool_t::make_qualified_name) - .def("create", py_create_1, - return_value_policy()) - .def("create", py_create_2, - return_value_policy()) + .def("create", py_create_1, return_internal_reference<>()) + .def("create", py_create_2, return_internal_reference<>()) - .def("find_or_create", py_find_or_create_1, - return_value_policy()) - .def("find_or_create", py_find_or_create_2, - return_value_policy()) + .def("find_or_create", py_find_or_create_1, return_internal_reference<>()) + .def("find_or_create", py_find_or_create_2, return_internal_reference<>()) - .def("find", py_find_1, return_value_policy()) - .def("find", py_find_2, return_value_policy()) + .def("find", py_find_1, return_internal_reference<>()) + .def("find", py_find_2, return_internal_reference<>()) .def("exchange", py_exchange_2, with_custodian_and_ward<1, 2>()) .def("exchange", py_exchange_3, with_custodian_and_ward<1, 2>()) @@ -288,23 +284,23 @@ void export_commodity() .def("parse_price_directive", &commodity_pool_t::parse_price_directive) .def("parse_price_expression", &commodity_pool_t::parse_price_expression, - return_value_policy()) + return_internal_reference<>()) .def("__getitem__", py_pool_getitem, - return_value_policy()) + return_internal_reference<>()) .def("keys", py_pool_keys) .def("has_key", py_pool_contains) .def("__contains__", py_pool_contains) .def("__iter__", - python::range > + python::range > (py_pool_commodities_begin, py_pool_commodities_end)) .def("iteritems", - python::range > + python::range > (py_pool_commodities_begin, py_pool_commodities_end)) .def("iterkeys", python::range<>(py_pool_commodities_keys_begin, py_pool_commodities_keys_end)) .def("itervalues", - python::range > + python::range > (py_pool_commodities_values_begin, py_pool_commodities_values_end)) ; @@ -349,17 +345,17 @@ void export_commodity() .add_property("referent", make_function(py_commodity_referent, - return_value_policy())) + return_internal_reference<>())) .def("has_annotation", &commodity_t::has_annotation) .def("strip_annotations", py_strip_annotations_0, - return_value_policy()) + return_internal_reference<>()) .def("strip_annotations", py_strip_annotations_1, - return_value_policy()) + return_internal_reference<>()) .def("write_annotations", &commodity_t::write_annotations) .def("pool", &commodity_t::pool, - return_value_policy()) + return_internal_reference<>()) .add_property("base_symbol", &commodity_t::base_symbol) .add_property("symbol", &commodity_t::symbol) @@ -441,12 +437,12 @@ void export_commodity() .add_property("referent", make_function(py_annotated_commodity_referent, - return_value_policy())) + return_internal_reference<>())) .def("strip_annotations", py_strip_ann_annotations_0, - return_value_policy()) + return_internal_reference<>()) .def("strip_annotations", py_strip_ann_annotations_1, - return_value_policy()) + return_internal_reference<>()) .def("write_annotations", &annotated_commodity_t::write_annotations) ; } diff --git a/src/py_journal.cc b/src/py_journal.cc index a72b8528..550fb14e 100644 --- a/src/py_journal.cc +++ b/src/py_journal.cc @@ -144,10 +144,10 @@ namespace { struct collector_wrapper { - journal_t& journal; - report_t report; - collect_posts * posts_collector; - post_handler_ptr chain; + journal_t& journal; + report_t report; + + post_handler_ptr posts_collector; collector_wrapper(journal_t& _journal, report_t& base) : journal(_journal), report(base), @@ -157,31 +157,32 @@ namespace { } std::size_t length() const { - return posts_collector->length(); + return dynamic_cast(posts_collector.get())->length(); } std::vector::iterator begin() { - return posts_collector->begin(); + return dynamic_cast(posts_collector.get())->begin(); } std::vector::iterator end() { - return posts_collector->end(); + return dynamic_cast(posts_collector.get())->end(); } }; - shared_ptr - py_collect(journal_t& journal, const string& query) + shared_ptr py_query(journal_t& journal, + const string& query) { if (journal.has_xdata()) { PyErr_SetString(PyExc_RuntimeError, - _("Cannot have multiple journal collections open at once")); + _("Cannot have more than one active journal query")); throw_error_already_set(); } report_t& current_report(downcast(*scope_t::default_scope)); - shared_ptr coll(new collector_wrapper(journal, - current_report)); - unique_ptr save_journal(current_report.session.journal.release()); - current_report.session.journal.reset(&journal); + shared_ptr + coll(new collector_wrapper(journal, current_report)); + + unique_ptr save_journal(coll->report.session.journal.release()); + coll->report.session.journal.reset(&coll->journal); try { strings_list remaining = @@ -191,60 +192,48 @@ namespace { value_t args; foreach (const string& arg, remaining) args.push_back(string_value(arg)); - coll->report.parse_query_args(args, "@Journal.collect"); + coll->report.parse_query_args(args, "@Journal.query"); - journal_posts_iterator walker(coll->journal); - coll->chain = - chain_post_handlers(post_handler_ptr(coll->posts_collector), - coll->report); - pass_down_posts(coll->chain, walker); + coll->report.posts_report(coll->posts_collector); } catch (...) { - current_report.session.journal.release(); - current_report.session.journal.reset(save_journal.release()); + coll->report.session.journal.release(); + coll->report.session.journal.reset(save_journal.release()); throw; } - current_report.session.journal.release(); - current_report.session.journal.reset(save_journal.release()); + coll->report.session.journal.release(); + coll->report.session.journal.reset(save_journal.release()); return coll; } post_t * posts_getitem(collector_wrapper& collector, long i) { - post_t * post = - collector.posts_collector->posts[static_cast(i)]; - std::cerr << typeid(post).name() << std::endl; - std::cerr << typeid(*post).name() << std::endl; - std::cerr << typeid(post->account).name() << std::endl; - std::cerr << typeid(*post->account).name() << std::endl; - return post; + return dynamic_cast(collector.posts_collector.get()) + ->posts[static_cast(i)]; } } // unnamed namespace +#define EXC_TRANSLATOR(type) \ + void exc_translate_ ## type(const type& err) { \ + PyErr_SetString(PyExc_RuntimeError, err.what()); \ + } + +EXC_TRANSLATOR(parse_error) +EXC_TRANSLATOR(error_count) + void export_journal() { class_< item_handler, shared_ptr >, boost::noncopyable >("PostHandler") ; - class_< collect_posts, bases >, - shared_ptr, boost::noncopyable >("PostCollector") - .def("__len__", &collect_posts::length) - .def("__iter__", python::range > > - (&collect_posts::begin, &collect_posts::end)) - ; - class_< collector_wrapper, shared_ptr, boost::noncopyable >("PostCollectorWrapper", no_init) .def("__len__", &collector_wrapper::length) - .def("__getitem__", posts_getitem, return_internal_reference<1, - with_custodian_and_ward_postcall<0, 1> >()) - .def("__iter__", - python::range > > + .def("__getitem__", posts_getitem, return_internal_reference<>()) + .def("__iter__", python::range > (&collector_wrapper::begin, &collector_wrapper::end)) ; @@ -286,13 +275,13 @@ void export_journal() .def("find_account", py_find_account_1, return_internal_reference<1, - with_custodian_and_ward_postcall<0, 1> >()) + with_custodian_and_ward_postcall<1, 0> >()) .def("find_account", py_find_account_2, return_internal_reference<1, - with_custodian_and_ward_postcall<0, 1> >()) + with_custodian_and_ward_postcall<1, 0> >()) .def("find_account_re", &journal_t::find_account_re, return_internal_reference<1, - with_custodian_and_ward_postcall<0, 1> >()) + with_custodian_and_ward_postcall<1, 0> >()) .def("add_xact", &journal_t::add_xact) .def("remove_xact", &journal_t::remove_xact) @@ -301,7 +290,7 @@ void export_journal() #if 0 .def("__getitem__", xacts_getitem, return_internal_reference<1, - with_custodian_and_ward_postcall<0, 1> >()) + with_custodian_and_ward_postcall<1, 0> >()) #endif .def("__iter__", python::range > @@ -320,10 +309,16 @@ void export_journal() .def("has_xdata", &journal_t::has_xdata) .def("clear_xdata", &journal_t::clear_xdata) - .def("collect", py_collect, with_custodian_and_ward_postcall<0, 1>()) + .def("query", py_query) .def("valid", &journal_t::valid) ; + +#define EXC_TRANSLATE(type) \ + register_exception_translator(&exc_translate_ ## type); + + EXC_TRANSLATE(parse_error); + EXC_TRANSLATE(error_count); } } // namespace ledger diff --git a/src/py_post.cc b/src/py_post.cc index bd599604..2789082e 100644 --- a/src/py_post.cc +++ b/src/py_post.cc @@ -116,7 +116,7 @@ void export_post() make_setter(&post_t::xdata_t::datetime)) .add_property("account", make_getter(&post_t::xdata_t::account, - return_value_policy()), + return_internal_reference<>()), make_setter(&post_t::xdata_t::account, with_custodian_and_ward<1, 2>())) .add_property("sort_values", diff --git a/src/py_session.cc b/src/py_session.cc new file mode 100644 index 00000000..f411d5e1 --- /dev/null +++ b/src/py_session.cc @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2003-2012, 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 + +#include "pyinterp.h" +#include "pyutils.h" +#include "session.h" + +namespace ledger { + +using namespace boost::python; + +namespace { + journal_t * py_read_journal(const string& pathname) + { + return python_session->read_journal(path(pathname)); + } + + journal_t * py_read_journal_from_string(const string& data) + { + return python_session->read_journal_from_string(data); + } +} + +void export_session() +{ + class_< session_t, boost::noncopyable > ("Session") + .def("read_journal", &session_t::read_journal, + return_internal_reference<>()) + .def("read_journal_from_string", &session_t::read_journal_from_string, + return_internal_reference<>()) + .def("read_journal_files", &session_t::read_journal_files, + return_internal_reference<>()) + .def("close_journal_files", &session_t::close_journal_files) + ; + + scope().attr("session") = + object(ptr(static_cast(python_session.get()))); + scope().attr("read_journal") = + python::make_function(&py_read_journal, + return_internal_reference<>()); + scope().attr("read_journal_from_string") = + python::make_function(&py_read_journal_from_string, + return_internal_reference<>()); +} + +} // namespace ledger diff --git a/src/py_xact.cc b/src/py_xact.cc index 97d5df47..3d792c7b 100644 --- a/src/py_xact.cc +++ b/src/py_xact.cc @@ -76,6 +76,12 @@ namespace { return **elem; } + string py_xact_to_string(xact_t&) + { + // jww (2012-03-01): TODO + return empty_string; + } + } // unnamed namespace using namespace boost::python; @@ -110,6 +116,8 @@ void export_xact() .def("id", &xact_t::id) .def("seq", &xact_t::seq) + .def("__str__", py_xact_to_string) + .add_property("code", make_getter(&xact_t::code), make_setter(&xact_t::code)) diff --git a/src/pyinterp.cc b/src/pyinterp.cc index de9c94cb..4dbb7134 100644 --- a/src/pyinterp.cc +++ b/src/pyinterp.cc @@ -51,6 +51,7 @@ void export_commodity(); void export_expr(); void export_format(); void export_item(); +void export_session(); void export_journal(); void export_post(); void export_times(); @@ -72,6 +73,7 @@ void initialize_for_python() export_item(); export_post(); export_xact(); + export_session(); export_journal(); } diff --git a/src/session.cc b/src/session.cc index e07026b6..db01fbf6 100644 --- a/src/session.cc +++ b/src/session.cc @@ -89,8 +89,10 @@ std::size_t session_t::read_data(const string& master_account) std::size_t xact_count = 0; - account_t * acct = journal->master; - if (! master_account.empty()) + account_t * acct; + if (master_account.empty()) + acct = journal->master; + else acct = journal->find_account(master_account); optional price_db_path; @@ -185,7 +187,7 @@ std::size_t session_t::read_data(const string& master_account) return journal->xacts.size(); } -void session_t::read_journal_files() +journal_t * session_t::read_journal_files() { INFO_START(journal, "Read journal file"); @@ -203,6 +205,37 @@ void session_t::read_journal_files() #if defined(DEBUG_ON) INFO("Found " << count << " transactions"); #endif + + return journal.get(); +} + +journal_t * session_t::read_journal(const path& pathname) +{ + HANDLER(file_).data_files.clear(); + HANDLER(file_).data_files.push_back(pathname); + + return read_journal_files(); +} + +journal_t * session_t::read_journal_from_string(const string& data) +{ + HANDLER(file_).data_files.clear(); + + shared_ptr stream(new std::istringstream(data)); + parsing_context.push(stream); + + parsing_context.get_current().journal = journal.get(); + parsing_context.get_current().master = journal->master; + try { + journal->read(parsing_context); + } + catch (...) { + parsing_context.pop(); + throw; + } + parsing_context.pop(); + + return journal.get(); } void session_t::close_journal_files() diff --git a/src/session.h b/src/session.h index 54b9912a..38062b78 100644 --- a/src/session.h +++ b/src/session.h @@ -75,9 +75,11 @@ public: flush_on_next_data_file = truth; } + journal_t * read_journal(const path& pathname); + journal_t * read_journal_from_string(const string& data); std::size_t read_data(const string& master_account = ""); - void read_journal_files(); + journal_t * read_journal_files(); void close_journal_files(); value_t fn_account(call_scope_t& scope); diff --git a/src/system.hh.in b/src/system.hh.in index e14166b2..3aa60f71 100644 --- a/src/system.hh.in +++ b/src/system.hh.in @@ -257,4 +257,6 @@ void serialize(Archive& ar, istream_pos_type& pos, const unsigned int) #include #include +#include + #endif // HAVE_BOOST_PYTHON diff --git a/src/textual.cc b/src/textual.cc index 97c80e4f..2f09c063 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -1746,7 +1746,7 @@ std::size_t journal_t::read_textual(parse_context_stack_t& context_stack) TRACE_FINISH(parsing_total, 1); if (context_stack.get_current().errors > 0) - throw static_cast(context_stack.get_current().errors); + throw error_count(context_stack.get_current().errors); return context_stack.get_current().count; } diff --git a/src/utils.cc b/src/utils.cc index 09526267..eb1d8009 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -612,8 +612,7 @@ optional _log_category_re; struct __maybe_enable_debugging { __maybe_enable_debugging() { - const char * p = std::getenv("LEDGER_DEBUG"); - if (p != NULL) { + if (const char * p = std::getenv("LEDGER_DEBUG")) { _log_level = LOG_DEBUG; _log_category = p; } diff --git a/test/python/JournalTest.py b/test/python/JournalTest.py index 66447f87..e65c671d 100644 --- a/test/python/JournalTest.py +++ b/test/python/JournalTest.py @@ -1,22 +1,27 @@ # -*- coding: utf-8 -*- import unittest -import exceptions -import operator from ledger import * -from StringIO import * -from datetime import * class JournalTestCase(unittest.TestCase): - def setUp(self): - pass - def tearDown(self): - pass + session.close_journal_files() + + def testBasicRead(self): + journal = read_journal_from_string(""" +2012-03-01 KFC + Expenses:Food $21.34 + Assets:Cash +""") + self.assertEqual(type(journal), Journal) + + for xact in journal: + self.assertEqual(xact.payee, "KFC") - def test_(self): - pass + for post in journal.query("food"): + self.assertEqual(str(post.account), "Expenses:Food") + self.assertEqual(post.amount, Amount("$21.34")) def suite(): return unittest.TestLoader().loadTestsFromTestCase(JournalTestCase) diff --git a/tools/Makefile.am b/tools/Makefile.am index fe0681e5..4fdd8393 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -226,6 +226,7 @@ libledger_python_la_SOURCES = \ src/py_expr.cc \ src/py_format.cc \ src/py_item.cc \ + src/py_session.cc \ src/py_journal.cc \ src/py_post.cc \ src/py_times.cc \ -- cgit v1.2.3 From f6c087cfe48e6410db61a9367ce7c718a490af77 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Thu, 1 Mar 2012 17:32:51 -0600 Subject: Added a new 'python' directive --- src/py_value.cc | 2 +- src/pyinterp.cc | 77 +++++++++++++++++++++++++++------------ src/pyinterp.h | 5 ++- src/textual.cc | 62 +++++++++++++++++++++++++++++++ test/LedgerHarness.py | 4 ++ test/RegressTests.py | 4 +- test/baseline/dir-python_py.test | 26 +++++++++++++ test/baseline/feat-import_py.test | 23 ++++++++++++ test/baseline/featimport.py | 4 ++ tools/Makefile.am | 16 +++++--- 10 files changed, 190 insertions(+), 33 deletions(-) create mode 100644 test/baseline/dir-python_py.test create mode 100644 test/baseline/feat-import_py.test create mode 100644 test/baseline/featimport.py (limited to 'tools/Makefile.am') diff --git a/src/py_value.cc b/src/py_value.cc index 3b67c4c6..949f2a49 100644 --- a/src/py_value.cc +++ b/src/py_value.cc @@ -147,7 +147,7 @@ void export_value() .def(init()) .def(init()) .def(init()) - // jww (2009-11-02): Need to support conversion eof value_t::sequence_t + // jww (2009-11-02): Need to support conversion of value_t::sequence_t //.def(init()) .def(init()) diff --git a/src/pyinterp.cc b/src/pyinterp.cc index adcd0167..dc6fb4f7 100644 --- a/src/pyinterp.cc +++ b/src/pyinterp.cc @@ -32,6 +32,7 @@ #include #include "pyinterp.h" +#include "pyutils.h" #include "account.h" #include "xact.h" #include "post.h" @@ -103,7 +104,7 @@ void python_interpreter_t::initialize() hack_system_paths(); - object main_module = python::import("__main__"); + main_module = python::import("__main__"); if (! main_module) throw_(std::runtime_error, _("Python failed to initialize (couldn't find __main__)")); @@ -446,28 +447,56 @@ expr_t::ptr_op_t python_interpreter_t::lookup(const symbol_t::kind_t kind, } namespace { - void append_value(list& lst, const value_t& value) + object convert_value_to_python(const value_t& val) { - if (value.is_scope()) { - const scope_t * scope = value.as_scope(); - if (const post_t * post = dynamic_cast(scope)) - lst.append(ptr(post)); - else if (const xact_t * xact = dynamic_cast(scope)) - lst.append(ptr(xact)); - else if (const account_t * account = - dynamic_cast(scope)) - lst.append(ptr(account)); - else if (const period_xact_t * period_xact = - dynamic_cast(scope)) - lst.append(ptr(period_xact)); - else if (const auto_xact_t * auto_xact = - dynamic_cast(scope)) - lst.append(ptr(auto_xact)); - else - throw_(std::logic_error, - _("Cannot downcast scoped object to specific type")); - } else { - lst.append(value); + switch (val.type()) { + case value_t::VOID: // a null value (i.e., uninitialized) + return object(); + case value_t::BOOLEAN: // a boolean + return object(val.to_boolean()); + case value_t::DATETIME: // a date and time (Boost posix_time) + return object(val.to_datetime()); + case value_t::DATE: // a date (Boost gregorian::date) + return object(val.to_date()); + case value_t::INTEGER: // a signed integer value + return object(val.to_long()); + case value_t::AMOUNT: // a ledger::amount_t + return object(val.as_amount()); + case value_t::BALANCE: // a ledger::balance_t + return object(val.as_balance()); + case value_t::STRING: // a string object + return object(handle<>(borrowed(str_to_py_unicode(val.as_string())))); + case value_t::MASK: // a regular expression mask + return object(handle<>(borrowed(str_to_py_unicode(val.as_mask().str())))); + case value_t::SEQUENCE: { // a vector of value_t objects + list arglist; + foreach (const value_t& elem, val.as_sequence()) + arglist.append(elem); + return arglist; + } + case value_t::SCOPE: // a pointer to a scope + if (const scope_t * scope = val.as_scope()) { + if (const post_t * post = dynamic_cast(scope)) + return object(ptr(post)); + else if (const xact_t * xact = dynamic_cast(scope)) + return object(ptr(xact)); + else if (const account_t * account = + dynamic_cast(scope)) + return object(ptr(account)); + else if (const period_xact_t * period_xact = + dynamic_cast(scope)) + return object(ptr(period_xact)); + else if (const auto_xact_t * auto_xact = + dynamic_cast(scope)) + return object(ptr(auto_xact)); + else + throw_(std::logic_error, + _("Cannot downcast scoped object to specific type")); + } + return object(); + case value_t::ANY: // a pointer to an arbitrary object + assert("Attempted to convert an Value.ANY object to Python" == NULL); + return object(); } } } @@ -491,9 +520,9 @@ value_t python_interpreter_t::functor_t::operator()(call_scope_t& args) // rather than a sequence of arguments? if (args.value().is_sequence()) foreach (const value_t& value, args.value().as_sequence()) - append_value(arglist, value); + arglist.append(convert_value_to_python(value)); else - append_value(arglist, args.value()); + arglist.append(convert_value_to_python(args.value())); if (PyObject * val = PyObject_CallObject(func.ptr(), python::tuple(arglist).ptr())) { diff --git a/src/pyinterp.h b/src/pyinterp.h index ae8dd9c2..c3397840 100644 --- a/src/pyinterp.h +++ b/src/pyinterp.h @@ -41,8 +41,9 @@ namespace ledger { class python_interpreter_t : public session_t { public: - python::dict main_nspace; - bool is_initialized; + python::object main_module; + python::dict main_nspace; + bool is_initialized; python_interpreter_t() : session_t(), main_nspace(), is_initialized(false) { diff --git a/src/textual.cc b/src/textual.cc index 4a384866..51b5849b 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -40,6 +40,9 @@ #include "query.h" #include "pstream.h" #include "pool.h" +#if defined(HAVE_BOOST_PYTHON) +#include "pyinterp.h" +#endif #define TIMELOG_SUPPORT 1 #if defined(TIMELOG_SUPPORT) @@ -104,6 +107,10 @@ namespace { return (in.good() && ! in.eof() && (in.peek() == ' ' || in.peek() == '\t')); } + bool peek_blank_line() { + return (in.good() && ! in.eof() && + (in.peek() == '\n' || in.peek() == '\r')); + } void read_next_directive(); @@ -157,6 +164,10 @@ namespace { void assert_directive(char * line); void check_directive(char * line); +#if defined(HAVE_BOOST_PYTHON) + void python_directive(char * line); +#endif + post_t * parse_post(char * line, std::streamsize len, account_t * account, @@ -1094,6 +1105,48 @@ void instance_t::comment_directive(char * line) } } +#if defined(HAVE_BOOST_PYTHON) +void instance_t::python_directive(char * line) +{ + std::ostringstream script; + + if (line) + script << skip_ws(line) << '\n'; + + std::size_t indent = 0; + + while (peek_whitespace_line() || peek_blank_line()) { + if (read_line(line) > 0) { + if (! indent) { + const char * p = line; + while (*p && std::isspace(*p)) { + ++indent; + ++p; + } + } + + const char * p = line; + for (std::size_t i = 0; i < indent; i++) { + if (std::isspace(*p)) + ++p; + else + break; + } + + if (*p) + script << p << '\n'; + } + } + + if (! python_session->is_initialized) + python_session->initialize(); + + python_session->main_nspace["journal"] = + python::object(python::ptr(context.journal)); + python_session->eval(script.str(), python_interpreter_t::PY_EVAL_MULTI); +} +#endif // HAVE_BOOST_PYTHON + bool instance_t::general_directive(char * line) { char buf[8192]; @@ -1178,6 +1231,15 @@ bool instance_t::general_directive(char * line) payee_directive(arg); return true; } + else if (std::strcmp(p, "python") == 0) { +#if defined(HAVE_BOOST_PYTHON) + python_directive(arg); +#else + throw_(parse_error, + _("'python' directive seen, but Python support is missing")); +#endif + return true; + } break; case 't': diff --git a/test/LedgerHarness.py b/test/LedgerHarness.py index c0dbe368..7b4dfa83 100755 --- a/test/LedgerHarness.py +++ b/test/LedgerHarness.py @@ -34,6 +34,7 @@ class LedgerHarness: failed = 0 verify = False gmalloc = False + python = False def __init__(self, argv): if not os.path.isfile(argv[1]): @@ -49,6 +50,9 @@ class LedgerHarness: self.failed = 0 self.verify = '--verify' in argv self.gmalloc = '--gmalloc' in argv + self.python = '--python' in argv + + os.chdir(self.sourcepath) def run(self, command, verify=None, gmalloc=None, columns=True): env = os.environ.copy() diff --git a/test/RegressTests.py b/test/RegressTests.py index 28a6c709..def202e4 100755 --- a/test/RegressTests.py +++ b/test/RegressTests.py @@ -179,7 +179,9 @@ if __name__ == '__main__': if os.path.isdir(tests): tests = [os.path.join(tests, x) - for x in os.listdir(tests) if x.endswith('.test')] + for x in os.listdir(tests) + if (x.endswith('.test') and + (not '_py.test' in x or harness.python))] if pool: pool.map(do_test, tests, 1) else: diff --git a/test/baseline/dir-python_py.test b/test/baseline/dir-python_py.test new file mode 100644 index 00000000..e4681075 --- /dev/null +++ b/test/baseline/dir-python_py.test @@ -0,0 +1,26 @@ +python + import os + def check_path(path_value): + return os.path.isfile(path_value) + +tag PATH + check check_path(value) + +2012-02-29 KFC + ; PATH: test/baseline/feat-import_py.test + Expenses:Food $20 + Assets:Cash + +2012-02-29 KFC + ; PATH: test/baseline/feat-import_noexist.test + Expenses:Food $20 + Assets:Cash + +test reg +12-Feb-29 KFC Expenses:Food $20 $20 + Assets:Cash $-20 0 +12-Feb-29 KFC Expenses:Food $20 $20 + Assets:Cash $-20 0 +__ERROR__ +Warning: "$sourcepath/test/baseline/dir-python_py.test", line 17: Metadata check failed for (PATH: test/baseline/feat-import_noexist.test): check_path(value) +end test diff --git a/test/baseline/feat-import_py.test b/test/baseline/feat-import_py.test new file mode 100644 index 00000000..6bd77586 --- /dev/null +++ b/test/baseline/feat-import_py.test @@ -0,0 +1,23 @@ +--import featimport.py + +tag PATH + check check_path(value) + +2012-02-29 KFC + ; PATH: test/baseline/feat-import_py.test + Expenses:Food $20 + Assets:Cash + +2012-02-29 KFC + ; PATH: test/baseline/feat-import_noexist.test + Expenses:Food $20 + Assets:Cash + +test reg +12-Feb-29 KFC Expenses:Food $20 $20 + Assets:Cash $-20 0 +12-Feb-29 KFC Expenses:Food $20 $20 + Assets:Cash $-20 0 +__ERROR__ +Warning: "$sourcepath/test/baseline/feat-import_py.test", line 14: Metadata check failed for (PATH: test/baseline/feat-import_noexist.test): check_path(value) +end test diff --git a/test/baseline/featimport.py b/test/baseline/featimport.py new file mode 100644 index 00000000..9edd9ba3 --- /dev/null +++ b/test/baseline/featimport.py @@ -0,0 +1,4 @@ +import os + +def check_path(path_value): + return os.path.isfile(str(path_value)) diff --git a/tools/Makefile.am b/tools/Makefile.am index 4fdd8393..bff1af1a 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -358,8 +358,14 @@ RegressTests_SOURCES = test/RegressTests.py EXTRA_DIST += test/regress test/convert.py test/LedgerHarness.py +if HAVE_BOOST_PYTHON +TEST_PYTHON_FLAGS = --python +else +TEST_PYTHON_FLAGS = +endif + RegressTests: $(srcdir)/test/RegressTests.py - echo "$(PYTHON) $(srcdir)/test/RegressTests.py -j$(JOBS) $(top_builddir)/ledger$(EXEEXT) $(srcdir) $(srcdir)/test/regress \"\$$@\"" > $@ + echo "$(PYTHON) $(srcdir)/test/RegressTests.py -j$(JOBS) $(top_builddir)/ledger$(EXEEXT) $(srcdir) $(srcdir)/test/regress $(TEST_PYTHON_FLAGS) \"\$$@\"" > $@ chmod 755 $@ BaselineTests_SOURCES = test/RegressTests.py @@ -367,7 +373,7 @@ BaselineTests_SOURCES = test/RegressTests.py EXTRA_DIST += test/baseline BaselineTests: $(srcdir)/test/RegressTests.py - echo "$(PYTHON) $(srcdir)/test/RegressTests.py -j$(JOBS) $(top_builddir)/ledger$(EXEEXT) $(srcdir) $(srcdir)/test/baseline \"\$$@\"" > $@ + echo "$(PYTHON) $(srcdir)/test/RegressTests.py -j$(JOBS) $(top_builddir)/ledger$(EXEEXT) $(srcdir) $(srcdir)/test/baseline $(TEST_PYTHON_FLAGS) \"\$$@\"" > $@ chmod 755 $@ ManualTests_SOURCES = test/RegressTests.py @@ -375,7 +381,7 @@ ManualTests_SOURCES = test/RegressTests.py EXTRA_DIST += test/manual ManualTests: $(srcdir)/test/RegressTests.py - echo "$(PYTHON) $(srcdir)/test/RegressTests.py -j$(JOBS) $(top_builddir)/ledger$(EXEEXT) $(srcdir) $(srcdir)/test/manual \"\$$@\"" > $@ + echo "$(PYTHON) $(srcdir)/test/RegressTests.py -j$(JOBS) $(top_builddir)/ledger$(EXEEXT) $(srcdir) $(srcdir)/test/manual $(TEST_PYTHON_FLAGS) \"\$$@\"" > $@ chmod 755 $@ ConfirmTests_SOURCES = test/ConfirmTests.py @@ -390,13 +396,13 @@ test/input/mondo.dat: test/input/standard.dat done ConfirmTests: $(srcdir)/test/ConfirmTests.py - echo "$(PYTHON) $(srcdir)/test/ConfirmTests.py $(top_builddir)/ledger$(EXEEXT) $(srcdir) $(srcdir)/test/input \"\$$@\"" > $@ + echo "$(PYTHON) $(srcdir)/test/ConfirmTests.py $(top_builddir)/ledger$(EXEEXT) $(srcdir) $(srcdir)/test/input $(TEST_PYTHON_FLAGS) \"\$$@\"" > $@ chmod 755 $@ GenerateTests_SOURCES = test/GenerateTests.py GenerateTests: $(srcdir)/test/GenerateTests.py - echo "$(PYTHON) $(srcdir)/test/GenerateTests.py -j$(JOBS) $(top_builddir)/ledger$(EXEEXT) $(srcdir) 1 ${1:-20} \"\$$@\"" > $@ + echo "$(PYTHON) $(srcdir)/test/GenerateTests.py -j$(JOBS) $(top_builddir)/ledger$(EXEEXT) $(srcdir) 1 ${1:-20} $(TEST_PYTHON_FLAGS) \"\$$@\"" > $@ chmod 755 $@ CheckTests_SOURCES = test/CheckTests.py -- cgit v1.2.3 From 4cfa4bca08932ff0cbe0f1ed97d989dbf55a449b Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Thu, 1 Mar 2012 21:39:23 -0600 Subject: Makefile fix --- tools/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tools/Makefile.am') diff --git a/tools/Makefile.am b/tools/Makefile.am index bff1af1a..09021b1f 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -402,7 +402,7 @@ ConfirmTests: $(srcdir)/test/ConfirmTests.py GenerateTests_SOURCES = test/GenerateTests.py GenerateTests: $(srcdir)/test/GenerateTests.py - echo "$(PYTHON) $(srcdir)/test/GenerateTests.py -j$(JOBS) $(top_builddir)/ledger$(EXEEXT) $(srcdir) 1 ${1:-20} $(TEST_PYTHON_FLAGS) \"\$$@\"" > $@ + echo "$(PYTHON) $(srcdir)/test/GenerateTests.py -j$(JOBS) $(top_builddir)/ledger$(EXEEXT) $(srcdir) 1 ${1:-20} \"\$$@\"" > $@ chmod 755 $@ CheckTests_SOURCES = test/CheckTests.py -- cgit v1.2.3 From 48ab6ad1dbab100bb8abd87029a0ca5bc501a3db Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sun, 4 Mar 2012 03:35:06 -0600 Subject: Switched to using Boost.Graph for commodity pricing --- src/commodity.cc | 442 +++++------------------------------------------------- src/commodity.h | 114 +------------- src/filters.cc | 3 + src/history.cc | 196 ++++++++++++++++++++++++ src/history.h | 184 +++++++++++++++++++++++ src/iterators.cc | 3 + src/pool.cc | 88 ++--------- src/pool.h | 23 ++- src/report.cc | 10 +- src/system.hh.in | 15 ++ tools/Makefile.am | 2 + 11 files changed, 476 insertions(+), 604 deletions(-) create mode 100644 src/history.cc create mode 100644 src/history.h (limited to 'tools/Makefile.am') diff --git a/src/commodity.cc b/src/commodity.cc index 643d0d1e..c0ccae11 100644 --- a/src/commodity.cc +++ b/src/commodity.cc @@ -40,410 +40,68 @@ namespace ledger { bool commodity_t::decimal_comma_by_default = false; -void commodity_t::history_t::add_price(commodity_t& source, - const datetime_t& date, - const amount_t& price, - const bool reflexive) +void commodity_t::add_price(const datetime_t& date, const amount_t& price, + const bool reflexive) { - DEBUG("commodity.prices.add", "add_price to " << source - << (reflexive ? " (secondary)" : " (primary)") - << " : " << date << ", " << price); + if (! reflexive) + add_flags(COMMODITY_PRIMARY); + pool().commodity_price_history.add_price(*this, date, price); - history_map::iterator i = prices.find(date); - if (i != prices.end()) { - (*i).second = price; - } else { - std::pair result - = prices.insert(history_map::value_type(date, price)); - assert(result.second); - } - - if (reflexive) { - amount_t inverse = price.inverted(); - inverse.set_commodity(const_cast(source)); - price.commodity().add_price(date, inverse, false); - } else { - DEBUG("commodity.prices.add", - "marking commodity " << source.symbol() << " as primary"); - source.add_flags(COMMODITY_PRIMARY); - } -} - -bool commodity_t::history_t::remove_price(const datetime_t& date) -{ - DEBUG("commodity.prices.add", "remove_price: " << date); - - history_map::size_type n = prices.erase(date); - if (n > 0) - return true; - return false; -} - -void commodity_t::varied_history_t:: - add_price(commodity_t& source, - const datetime_t& date, - const amount_t& price, - const bool reflexive) -{ - optional hist = history(price.commodity()); - if (! hist) { - std::pair result - = histories.insert(history_by_commodity_map::value_type - (&price.commodity(), history_t())); - assert(result.second); - - hist = (*result.first).second; - } - assert(hist); - - hist->add_price(source, date, price, reflexive); + DEBUG("commodity.prices.find", "Price added, clearing price_map"); + base->price_map.clear(); // a price was added, invalid the map } -bool commodity_t::varied_history_t::remove_price(const datetime_t& date, - commodity_t& comm) +void commodity_t::remove_price(const datetime_t& date, commodity_t& commodity) { - DEBUG("commodity.prices.add", "varied_remove_price: " << date << ", " << comm); + pool().commodity_price_history.remove_price(*this, commodity, date); - if (optional hist = history(comm)) - return hist->remove_price(date); - return false; + DEBUG("commodity.prices.find", "Price removed, clearing price_map"); + base->price_map.clear(); // a price was added, invalid the map } optional -commodity_t::history_t::find_price(const optional& moment, - const optional& oldest -#if defined(DEBUG_ON) - , const int indent -#endif - ) const +commodity_t::find_price(const optional& target = none, + const optional& moment = none, + const optional& oldest = none) const { - price_point_t point; - bool found = false; - -#if defined(DEBUG_ON) -#define DEBUG_INDENT(cat, indent) \ - do { \ - if (SHOW_DEBUG(cat)) \ - for (int _i = 0; _i < indent; _i++) \ - ledger::_log_buffer << " "; \ - } while (false) -#else -#define DEBUG_INDENT(cat, indent) -#endif - -#if defined(DEBUG_ON) + pair = base_t::time_and_commodity_t + (base_t::optional_time_pair_t(moment, oldest), + commodity ? &(*commodity) : NULL); DEBUG_INDENT("commodity.prices.find", indent); - if (moment) - DEBUG("commodity.prices.find", "find price nearest before or on: " << *moment); - else - DEBUG("commodity.prices.find", "find any price"); + DEBUG("commodity.prices.find", "looking for memoized args: " + << (moment ? format_datetime(*moment) : "NONE") << ", " + << (oldest ? format_datetime(*oldest) : "NONE") << ", " + << (commodity ? commodity->symbol() : "NONE")); - if (oldest) { + base_t::memoized_price_map::iterator i = base->price_map.find(*pair); + if (i != base->price_map.end()) { DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "but no older than: " << *oldest); - } -#endif - - if (prices.size() == 0) { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "there are no prices in this history"); - return none; + DEBUG("commodity.prices.find", "found! returning: " + << ((*i).second ? (*i).second->price : amount_t(0L))); + return (*i).second; } - if (! moment) { - history_map::const_reverse_iterator r = prices.rbegin(); - point.when = (*r).first; - point.price = (*r).second; - found = true; - - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "using most recent price"); - } else { - history_map::const_iterator i = prices.upper_bound(*moment); - if (i == prices.end()) { - history_map::const_reverse_iterator r = prices.rbegin(); - point.when = (*r).first; - point.price = (*r).second; - found = true; + optional point = + pool().commodity_price_history.find_price + (*this, commodity, moment ? *moment : epoch, oldest); + if (pair) { + if (base->price_map.size() > base_t::max_price_map_size) { DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "using last price"); - } else { - point.when = (*i).first; - if (*moment < point.when) { - if (i != prices.begin()) { - --i; - point.when = (*i).first; - point.price = (*i).second; - found = true; - } - } else { - point.price = (*i).second; - found = true; - } - - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "using found price"); - } - } - - if (! found) { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "could not find a price"); - return none; - } - else if (moment && point.when > *moment) { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "price is too young "); - return none; - } - else if (oldest && point.when < *oldest) { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "price is too old "); - return none; - } - else { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", - "returning price: " << point.when << ", " << point.price); - return point; - } -} - -optional -commodity_t::varied_history_t::find_price(const commodity_t& source, - const optional& commodity, - const optional& moment, - const optional& oldest -#if defined(DEBUG_ON) - , const int indent -#endif - ) const -{ - optional point; - optional limit = oldest; - -#if defined(VERIFY_ON) - if (commodity) { - VERIFY(source != *commodity); - VERIFY(! commodity->has_annotation()); - VERIFY(source.referent() != commodity->referent()); - } -#endif - -#if defined(DEBUG_ON) - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "varied_find_price for: " << source); - - DEBUG_INDENT("commodity.prices.find", indent); - if (commodity) - DEBUG("commodity.prices.find", "looking for: commodity '" << *commodity << "'"); - else - DEBUG("commodity.prices.find", "looking for: any commodity"); - - if (moment) { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "time index: " << *moment); - } - - if (oldest) { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "only consider prices younger than: " << *oldest); - } -#endif - - // Either we couldn't find a history for the target commodity, or we - // couldn't find a price. In either case, search all histories known - // to this commodity for a price which we can calculate in terms of - // the goal commodity. - price_point_t best; - bool found = false; - - foreach (const history_by_commodity_map::value_type& hist, histories) { - commodity_t& comm(*hist.first); - if (comm == source) - continue; - - DEBUG_INDENT("commodity.prices.find", indent + 1); - DEBUG("commodity.prices.find", - "searching for price via commodity '" << comm << "'"); - - point = hist.second.find_price(moment, limit -#if defined(DEBUG_ON) - , indent + 2 -#endif - ); - assert(! point || point->price.commodity() == comm); - - if (point) { - optional xlat; - - if (commodity && comm != *commodity) { - DEBUG_INDENT("commodity.prices.find", indent + 1); - DEBUG("commodity.prices.find", "looking for translation price"); - - xlat = comm.find_price(commodity, moment, limit, true -#if defined(DEBUG_ON) - , indent + 2 -#endif - ); - if (xlat) { - DEBUG_INDENT("commodity.prices.find", indent + 1); - DEBUG("commodity.prices.find", "found translated price " - << xlat->price << " from " << xlat->when); - - point->price = xlat->price * point->price; - if (xlat->when < point->when) { - point->when = xlat->when; - - DEBUG_INDENT("commodity.prices.find", indent + 1); - DEBUG("commodity.prices.find", - "adjusting date of result back to " << point->when); - } - } else { - DEBUG_INDENT("commodity.prices.find", indent + 1); - DEBUG("commodity.prices.find", "saw no translated price there"); - continue; - } - } - - assert(! commodity || point->price.commodity() == *commodity); - - DEBUG_INDENT("commodity.prices.find", indent + 1); DEBUG("commodity.prices.find", - "saw a price there: " << point->price << " from " << point->when); - - if (! limit || point->when > *limit) { - limit = point->when; - best = *point; - found = true; + "price map has grown too large, clearing it by half"); - DEBUG_INDENT("commodity.prices.find", indent + 1); - DEBUG("commodity.prices.find", - "search limit adjusted to " << *limit); - } - } else { - DEBUG_INDENT("commodity.prices.find", indent + 1); - DEBUG("commodity.prices.find", "saw no price there"); + for (std::size_t i = 0; i < base_t::max_price_map_size >> 1; i++) + base->price_map.erase(base->price_map.begin()); } - } - if (found) { DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.download", - "found price " << best.price << " from " << best.when); - return best; - } - return none; -} - -optional -commodity_t::varied_history_t::history(const optional& commodity) -{ - commodity_t * comm = NULL; - if (! commodity) { - if (histories.size() > 1) - return none; - comm = (*histories.begin()).first; - } else { - comm = &(*commodity); - } - - history_by_commodity_map::iterator i = histories.find(comm); - if (i != histories.end()) - return (*i).second; - - return none; -} - -optional -commodity_t::find_price(const optional& commodity, - const optional& moment, - const optional& oldest, - const bool nested -#if defined(DEBUG_ON) - , const int indent -#endif - ) const -{ - if (! has_flags(COMMODITY_WALKED) && base->varied_history) { - optional pair; -#if defined(VERIFY_ON) - optional checkpoint; - bool found = false; -#endif - - if (! nested) { - pair = base_t::time_and_commodity_t - (base_t::optional_time_pair_t(moment, oldest), - commodity ? &(*commodity) : NULL); - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "looking for memoized args: " - << (moment ? format_datetime(*moment) : "NONE") << ", " - << (oldest ? format_datetime(*oldest) : "NONE") << ", " - << (commodity ? commodity->symbol() : "NONE")); - - base_t::memoized_price_map::iterator i = base->price_map.find(*pair); - if (i != base->price_map.end()) { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "found! returning: " - << ((*i).second ? (*i).second->price : amount_t(0L))); -#if defined(VERIFY_ON) - IF_VERIFY() { - found = true; - checkpoint = (*i).second; - } else -#endif // defined(VERIFY_ON) - return (*i).second; - } - } - - optional point; - - const_cast(*this).add_flags(COMMODITY_WALKED); - try { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "manually finding price..."); - - point = base->varied_history->find_price(*this, commodity, - moment, oldest -#if defined(DEBUG_ON) - , indent -#endif - ); - } - catch (...) { - const_cast(*this).drop_flags(COMMODITY_WALKED); - throw; - } - const_cast(*this).drop_flags(COMMODITY_WALKED); - -#if defined(VERIFY_ON) - if (DO_VERIFY() && found) { - VERIFY(checkpoint == point); - return checkpoint; - } -#endif // defined(VERIFY_ON) - - if (! nested && pair) { - if (base->price_map.size() > base_t::max_price_map_size) { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", - "price map has grown too large, clearing it by half"); - - for (std::size_t i = 0; i < base_t::max_price_map_size >> 1; i++) - base->price_map.erase(base->price_map.begin()); - } - - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", - "remembered: " << (point ? point->price : amount_t(0L))); - base->price_map.insert - (base_t::memoized_price_map::value_type(*pair, point)); - } - return point; + DEBUG("commodity.prices.find", + "remembered: " << (point ? point->price : amount_t(0L))); + base->price_map.insert + (base_t::memoized_price_map::value_type(*pair, point)); } - return none; + return point; } optional @@ -767,28 +425,6 @@ void to_xml(std::ostream& out, const commodity_t& comm, if (commodity_details) { if (comm.has_annotation()) to_xml(out, as_annotated_commodity(comm).details); - - if (comm.varied_history()) { - push_xml y(out, "varied-history"); - - foreach (const commodity_t::history_by_commodity_map::value_type& pair, - comm.varied_history()->histories) { - { - push_xml z(out, "symbol"); - out << y.guard(pair.first->symbol()); - } - { - push_xml z(out, "history"); - - foreach (const commodity_t::history_map::value_type& inner_pair, - pair.second.prices) { - push_xml w(out, "price-point"); - to_xml(out, inner_pair.first); - to_xml(out, inner_pair.second); - } - } - } - } } } diff --git a/src/commodity.h b/src/commodity.h index 68f788e3..1505fe24 100644 --- a/src/commodity.h +++ b/src/commodity.h @@ -85,78 +85,6 @@ class commodity_t : public delegates_flags, public equality_comparable1 { -public: - typedef std::map history_map; - - struct history_t - { - history_map prices; - - void add_price(commodity_t& source, - const datetime_t& date, - const amount_t& price, - const bool reflexive = true); - bool remove_price(const datetime_t& date); - - optional - find_price(const optional& moment = none, - const optional& oldest = none -#if defined(DEBUG_ON) - , const int indent = 0 -#endif - ) const; - -#if defined(HAVE_BOOST_SERIALIZATION) - private: - /** Serialization. */ - - friend class boost::serialization::access; - - template - void serialize(Archive& ar, const unsigned int /* version */) { - ar & prices; - } -#endif // HAVE_BOOST_SERIALIZATION - }; - - typedef std::map history_by_commodity_map; - - struct varied_history_t - { - history_by_commodity_map histories; - - void add_price(commodity_t& source, - const datetime_t& date, - const amount_t& price, - const bool reflexive = true); - bool remove_price(const datetime_t& date, commodity_t& commodity); - - optional - find_price(const commodity_t& source, - const optional& commodity = none, - const optional& moment = none, - const optional& oldest = none -#if defined(DEBUG_ON) - , const int indent = 0 -#endif - ) const; - - optional - history(const optional& commodity = none); - -#if defined(HAVE_BOOST_SERIALIZATION) - private: - /** Serialization. */ - - friend class boost::serialization::access; - - template - void serialize(Archive& ar, const unsigned int /* version */) { - ar & histories; - } -#endif // HAVE_BOOST_SERIALIZATION - }; - protected: friend class commodity_pool_t; friend class annotated_commodity_t; @@ -182,7 +110,6 @@ protected: amount_t::precision_t precision; optional name; optional note; - optional varied_history; optional smaller; optional larger; @@ -228,7 +155,6 @@ protected: ar & precision; ar & name; ar & note; - ar & varied_history; ar & smaller; ar & larger; } @@ -335,48 +261,14 @@ public: base->larger = arg; } - optional varied_history() { - if (base->varied_history) - return *base->varied_history; - return none; - } - optional varied_history() const { - if (base->varied_history) - return *base->varied_history; - return none; - } - - optional history(const optional& commodity); - - // These methods provide a transparent pass-through to the underlying - // base->varied_history object. - void add_price(const datetime_t& date, const amount_t& price, - const bool reflexive = true) { - if (! base->varied_history) - base->varied_history = varied_history_t(); - base->varied_history->add_price(*this, date, price, reflexive); - DEBUG("commodity.prices.find", "Price added, clearing price_map"); - base->price_map.clear(); // a price was added, invalid the map - } - bool remove_price(const datetime_t& date, commodity_t& commodity) { - if (base->varied_history) { - base->varied_history->remove_price(date, commodity); - DEBUG("commodity.prices.find", "Price removed, clearing price_map"); - base->price_map.clear(); // a price was added, invalid the map - } - return false; - } + const bool reflexive = true); + void remove_price(const datetime_t& date, commodity_t& commodity); optional find_price(const optional& commodity = none, const optional& moment = none, - const optional& oldest = none, - const bool nested = false -#if defined(DEBUG_ON) - , const int indent = 0 -#endif - ) const; + const optional& oldest = none) const; optional check_for_updated_price(const optional& point, diff --git a/src/filters.cc b/src/filters.cc index 72ce9c32..0c6222d7 100644 --- a/src/filters.cc +++ b/src/filters.cc @@ -754,6 +754,8 @@ void changed_value_posts::output_intermediate_prices(post_t& post, // fall through... case value_t::BALANCE: { +#if 0 + // jww (2012-03-04): TODO commodity_t::history_map all_prices; foreach (const balance_t::amounts_map::value_type& amt_comm, @@ -797,6 +799,7 @@ void changed_value_posts::output_intermediate_prices(post_t& post, output_revaluation(post, price.first); last_total = repriced_total; } +#endif break; } default: diff --git a/src/history.cc b/src/history.cc new file mode 100644 index 00000000..44d19f5a --- /dev/null +++ b/src/history.cc @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2003-2012, 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 + +#include "history.h" + +template +struct f_max : public std::binary_function { + T operator()(const T& x, const T& y) const { + return std::max(x, y); + } +}; + +namespace ledger { + +void commodity_history_t::add_commodity(const commodity_t& comm) +{ + const vertex_descriptor vert = add_vertex(&comm, price_graph); + put(indexmap, vert, reinterpret_cast(&comm)); +} + +void commodity_history_t::add_price(const commodity_t& source, + const datetime_t& when, + const amount_t& price) +{ + vertex_descriptor sv = + vertex(reinterpret_cast(&source), price_graph); + vertex_descriptor tv = + vertex(reinterpret_cast(&price.commodity()), price_graph); + + std::pair e1 = add_edge(sv, tv, 0, price_graph); + price_map_t& prices(get(ratiomap, e1.first)); + + std::pair result = + prices.insert(price_map_t::value_type(when, price)); + if (! result.second) { + // There is already an entry for this moment, so update it + (*result.first).second = price; + } +} + +void commodity_history_t::remove_price(const commodity_t& source, + const commodity_t& target, + const datetime_t& date) +{ + vertex_descriptor sv = + vertex(reinterpret_cast(&source), price_graph); + vertex_descriptor tv = + vertex(reinterpret_cast(&target), price_graph); + + std::pair e1 = add_edge(sv, tv, 0, price_graph); + price_map_t& prices(get(ratiomap, e1.first)); + + // jww (2012-03-04): If it fails, should we give a warning? + prices.erase(date); +} + +optional +commodity_history_t::find_price(const commodity_t& source, + const datetime_t& moment, + const optional& oldest, + const optional& target) +{ + vertex_descriptor sv = + vertex(reinterpret_cast(&source), price_graph); + vertex_descriptor tv = + vertex(reinterpret_cast(&*target), price_graph); + + // Filter out edges which came into being after the reference time + + FGraph fg(price_graph, + recent_edge_weight + (get(edge_weight, price_graph), pricemap, ratiomap, + moment, oldest)); + + std::vector predecessors(num_vertices(fg)); + std::vector distances(num_vertices(fg)); + + PredecessorMap predecessorMap(&predecessors[0]); + DistanceMap distanceMap(&distances[0]); + + dijkstra_shortest_paths(fg, /* start= */ sv, + predecessor_map(predecessorMap) + .distance_map(distanceMap) + .distance_combine(f_max())); + + // Extract the shortest path and performance the calculations + datetime_t least_recent = moment; + amount_t price; + + vertex_descriptor v = tv; + for (vertex_descriptor u = predecessorMap[v]; + u != v; + v = u, u = predecessorMap[v]) + { + std::pair edgePair = edge(u, v, fg); + Graph::edge_descriptor edge = edgePair.first; + + const price_point_t& point(get(pricemap, edge)); + + if (price.is_null()) { + least_recent = point.when; + price = point.price; + } + else if (point.when < least_recent) + least_recent = point.when; + + // jww (2012-03-04): TODO + //price *= point.price; + } + + return price_point_t(least_recent, price); +} + +#if 0 + print_vertices(fg, f_commmap); + print_edges(fg, f_commmap); + print_graph(fg, f_commmap); + + graph_traits::vertex_iterator f_vi, f_vend; + for(tie(f_vi, f_vend) = vertices(fg); f_vi != f_vend; ++f_vi) + std::cerr << get(f_commmap, *f_vi) << " is in the filtered graph" + << std::endl; + + for (tie(f_vi, f_vend) = vertices(fg); f_vi != f_vend; ++f_vi) { + std::cerr << "distance(" << get(f_commmap, *f_vi) << ") = " + << distanceMap[*f_vi] << ", "; + std::cerr << "parent(" << get(f_commmap, *f_vi) << ") = " + << get(f_commmap, predecessorMap[*f_vi]) + << std::endl; + } + + // Write shortest path + FCommMap f_commmap = get(vertex_comm, fg); + + std::cerr << "Shortest path from CAD to EUR:" << std::endl; + for (PathType::reverse_iterator pathIterator = path.rbegin(); + pathIterator != path.rend(); + ++pathIterator) + { + std::cerr << get(f_commmap, source(*pathIterator, fg)) + << " -> " << get(f_commmap, target(*pathIterator, fg)) + << " = " << get(edge_weight, fg, *pathIterator) + << std::endl; + } + std::cerr << std::endl; + + std::cerr << "Distance: " << distanceMap[vd4] << std::endl; +#endif + +#if 0 + #include + + // Writing graph to file + { + std::ofstream f("test.dot"); + + dynamic_properties p; + p.property("label", get(edge_weight, g)); + p.property("weight", get(edge_weight, g)); + p.property("node_id", get(vertex_comm, g)); + write_graphviz(f,g,p); + f.close(); + } +#endif + +} // namespace ledger diff --git a/src/history.h b/src/history.h new file mode 100644 index 00000000..486602dd --- /dev/null +++ b/src/history.h @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2003-2012, 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 history.h + * @author John Wiegley + * + * @ingroup math + * + * @brief Types for managing commodity historys + * + * Long. + */ +#ifndef _HISTORY_H +#define _HISTORY_H + +#include "amount.h" +#include "commodity.h" + +namespace boost { + enum edge_price_point_t { edge_price_point }; + enum edge_price_ratio_t { edge_price_ratio }; + BOOST_INSTALL_PROPERTY(edge, price_point); + BOOST_INSTALL_PROPERTY(edge, price_ratio); +} + +namespace ledger { + +typedef std::map price_map_t; + +template +class recent_edge_weight +{ +public: + EdgeWeightMap weight; + PricePointMap price_point; + PriceRatioMap ratios; + + datetime_t reftime; + optional oldest; + + recent_edge_weight() { } + recent_edge_weight(EdgeWeightMap _weight, + PricePointMap _price_point, + PriceRatioMap _ratios, + datetime_t _reftime, + const optional& _oldest = none) + : weight(_weight), price_point(_price_point), ratios(_ratios), + reftime(_reftime), oldest(_oldest) { } + + template + bool operator()(const Edge& e) const { + const price_map_t& prices(get(ratios, e)); + price_map_t::const_iterator low = prices.upper_bound(reftime); + if (prices.empty() || + (low != prices.end() && low == prices.begin())) { + return false; + } else { + if (low == prices.end()) + --low; + assert(((*low).first <= reftime)); + + if (oldest && (*low).first <= *oldest) + return false; + + long secs = (reftime - (*low).first).total_seconds(); + assert(secs >= 0); + + put(weight, e, secs); + put(price_point, e, price_point_t((*low).first, (*low).second)); + + return true; + } + } +}; + +class commodity_history_t : public noncopyable +{ +public: + typedef adjacency_list + >, + + // All edges are weights computed as the absolute difference between + // the reference time of a search and a known price point. A + // filtered_graph is used to select the recent price point to the + // reference time before performing the search. + property > >, + + // Graph itself has a std::string name + property + > Graph; + + Graph price_graph; + + typedef graph_traits::vertex_descriptor vertex_descriptor; + typedef graph_traits::edge_descriptor edge_descriptor; + + typedef property_map::type IndexMap; + typedef property_map::type NameMap; + + typedef iterator_property_map PredecessorMap; + typedef iterator_property_map DistanceMap; + + typedef property_map::type EdgeWeightMap; + typedef property_map::type PricePointMap; + typedef property_map::type PriceRatioMap; + + IndexMap indexmap; + PricePointMap pricemap; + PriceRatioMap ratiomap; + + typedef filtered_graph > FGraph; + typedef property_map::type FNameMap; + + commodity_history_t() + : indexmap(get(vertex_index, price_graph)), + pricemap(get(edge_price_point, price_graph)), + ratiomap(get(edge_price_ratio, price_graph)) {} + + void add_commodity(const commodity_t& comm); + + void add_price(const commodity_t& source, + const datetime_t& when, + const amount_t& price); + void remove_price(const commodity_t& source, + const commodity_t& target, + const datetime_t& date); + + optional + find_price(const commodity_t& source, + const datetime_t& moment, + const optional& oldest = none, + const optional& commodity = none); +}; + +} // namespace ledger + +#endif // _HISTORY_H diff --git a/src/iterators.cc b/src/iterators.cc index 72e0481c..b7ed011e 100644 --- a/src/iterators.cc +++ b/src/iterators.cc @@ -90,6 +90,8 @@ void posts_commodities_iterator::reset(journal_t& journal) std::map xacts_by_commodity; +#if 0 + // jww (2012-03-04): TODO foreach (commodity_t * comm, commodities) { if (optional history = comm->varied_history()) { @@ -136,6 +138,7 @@ void posts_commodities_iterator::reset(journal_t& journal) } } } +#endif xacts.reset(xact_temps.begin(), xact_temps.end()); diff --git a/src/pool.cc b/src/pool.cc index ba408fc5..67cfe3d1 100644 --- a/src/pool.cc +++ b/src/pool.cc @@ -35,6 +35,7 @@ #include "commodity.h" #include "annotate.h" #include "pool.h" +#include "history.h" #include "quotes.h" namespace ledger { @@ -74,15 +75,15 @@ commodity_t * commodity_pool_t::create(const string& symbol) commodity.get())); assert(result.second); + commodity_price_history.add_commodity(*commodity.get()); + return commodity.release(); } commodity_t * commodity_pool_t::find_or_create(const string& symbol) { DEBUG("pool.commodities", "Find-or-create commodity " << symbol); - - commodity_t * commodity = find(symbol); - if (commodity) + if (commodity_t * commodity = find(symbol)) return commodity; return create(symbol); } @@ -222,6 +223,15 @@ commodity_t * commodity_pool_t::find_or_create(commodity_t& comm, return create(comm, details, name); } +optional +commodity_pool_t::find_price(const commodity_t& source, + const optional& commodity, + const optional& moment, + const optional& oldest) const +{ + return commodity_price_history.find_price(source, commodity, moment, oldest); +} + void commodity_pool_t::exchange(commodity_t& commodity, const amount_t& per_unit_cost, const datetime_t& moment) @@ -382,76 +392,4 @@ commodity_pool_t::parse_price_expression(const std::string& str, return NULL; } -void commodity_pool_t::print_pricemap(std::ostream& out, - const keep_details_t& keep, - const optional& moment) -{ - typedef std::map comm_map_t; - - comm_map_t comm_map; - - foreach (const commodities_map::value_type& comm_pair, commodities) { - commodity_t * comm(&comm_pair.second->strip_annotations(keep)); - comm_map.insert(comm_map_t::value_type(comm, NULL)); - } - - out << "digraph commodities {\n"; - - foreach (const comm_map_t::value_type& comm_pair, comm_map) { - commodity_t * comm(comm_pair.first); - if (comm->has_flags(COMMODITY_BUILTIN)) - continue; - - out << " "; - if (commodity_t::symbol_needs_quotes(comm->symbol())) - out << comm->symbol() << ";\n"; - else - out << "\"" << comm->symbol() << "\";\n"; - - if (! comm->has_flags(COMMODITY_NOMARKET) && - (! commodity_pool_t::current_pool->default_commodity || - comm != commodity_pool_t::current_pool->default_commodity)) { - if (optional vhist = - comm->varied_history()) { - foreach (const commodity_t::history_by_commodity_map::value_type& pair, - vhist->histories) { - datetime_t most_recent; - amount_t most_recent_amt; - foreach (const commodity_t::history_map::value_type& inner_pair, - pair.second.prices) { - if ((most_recent.is_not_a_date_time() || - inner_pair.first > most_recent) && - (! moment || inner_pair.first <= moment)) { - most_recent = inner_pair.first; - most_recent_amt = inner_pair.second; - } - } - - if (! most_recent.is_not_a_date_time()) { - out << " "; - if (commodity_t::symbol_needs_quotes(comm->symbol())) - out << comm->symbol(); - else - out << "\"" << comm->symbol() << "\""; - - out << " -> "; - - if (commodity_t::symbol_needs_quotes(pair.first->symbol())) - out << pair.first->symbol(); - else - out << "\"" << pair.first->symbol() << "\""; - - out << " [label=\"" - << most_recent_amt.number() << "\\n" - << format_date(most_recent.date(), FMT_WRITTEN) - << "\" fontcolor=\"#008e28\"];\n"; - } - } - } - } - } - - out << "}\n"; -} - } // namespace ledger diff --git a/src/pool.h b/src/pool.h index 87b315f9..709f5c71 100644 --- a/src/pool.h +++ b/src/pool.h @@ -46,6 +46,8 @@ #ifndef _POOL_H #define _POOL_H +#include "history.h" + namespace ledger { struct cost_breakdown_t @@ -66,15 +68,16 @@ public: */ typedef std::map commodities_map; - commodities_map commodities; - commodity_t * null_commodity; - commodity_t * default_commodity; + commodities_map commodities; + commodity_history_t commodity_price_history; + commodity_t * null_commodity; + commodity_t * default_commodity; - bool keep_base; // --base + bool keep_base; // --base - optional price_db; // --price-db= - long quote_leeway; // --leeway= - bool get_quotes; // --download + optional price_db; // --price-db= + long quote_leeway; // --leeway= + bool get_quotes; // --download static shared_ptr current_pool; @@ -131,12 +134,6 @@ public: const bool add_prices = true, const optional& moment = none); - // Output the commodity price map for a given date as a DOT file - - void print_pricemap(std::ostream& out, - const keep_details_t& keep, - const optional& moment = none); - #if defined(HAVE_BOOST_SERIALIZATION) private: /** Serialization. */ diff --git a/src/report.cc b/src/report.cc index 647df3d2..689028d0 100644 --- a/src/report.cc +++ b/src/report.cc @@ -878,11 +878,12 @@ value_t report_t::echo_command(call_scope_t& args) value_t report_t::pricemap_command(call_scope_t& args) { std::ostream& out(output_stream); - +#if 0 + // jww (2012-03-04): TODO commodity_pool_t::current_pool->print_pricemap (out, what_to_keep(), args.has(0) ? optional(datetime_t(parse_date(args.get(0)))) : none); - +#endif return true; } @@ -913,6 +914,11 @@ option_t * report_t::lookup_option(const char * p) case 'G': OPT_CH(gain); break; +#if 0 + case 'H': + OPT_CH(historical); + break; +#endif case 'I': OPT_CH(price); break; diff --git a/src/system.hh.in b/src/system.hh.in index 3aa60f71..8f684486 100644 --- a/src/system.hh.in +++ b/src/system.hh.in @@ -138,37 +138,52 @@ typedef std::ostream::pos_type ostream_pos_type; #include #include #include + #include #include #include + #include #include #include #include #include + #if !(defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__) #include #endif #include + +#include +#include +#include +#include + #include + #include #include #define BOOST_IOSTREAMS_USE_DEPRECATED 1 #include + #include #include + #include #include #include #include + #include #include #include #include + #if defined(HAVE_BOOST_REGEX_UNICODE) #include #else #include + #endif // HAVE_BOOST_REGEX_UNICODE #include #include diff --git a/tools/Makefile.am b/tools/Makefile.am index 09021b1f..671db294 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -30,6 +30,7 @@ libledger_util_la_LDFLAGS = -release $(LIBVERSION) libledger_math_la_SOURCES = \ src/balance.cc \ src/quotes.cc \ + src/history.cc \ src/pool.cc \ src/annotate.cc \ src/commodity.cc \ @@ -104,6 +105,7 @@ pkginclude_HEADERS = \ src/amount.h \ src/commodity.h \ src/annotate.h \ + src/history.h \ src/pool.h \ src/quotes.h \ src/balance.h \ -- cgit v1.2.3 From 8ae2fb87211b2c1a0159480ea6908db2afa20189 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Mon, 5 Mar 2012 23:01:41 -0600 Subject: Add support for valuation expressions on commodities --- src/annotate.cc | 62 +++++++++++++++++++++++++++++++++++++++++++---------- src/annotate.h | 46 ++++++++++++++++++++++++++------------- src/expr.h | 3 +++ src/predicate.cc | 40 ---------------------------------- test/unit/t_expr.cc | 2 +- tools/Makefile.am | 41 +++++++++++------------------------ 6 files changed, 98 insertions(+), 96 deletions(-) delete mode 100644 src/predicate.cc (limited to 'tools/Makefile.am') diff --git a/src/annotate.cc b/src/annotate.cc index 1e243beb..b1db6bd5 100644 --- a/src/annotate.cc +++ b/src/annotate.cc @@ -33,6 +33,7 @@ #include "amount.h" #include "commodity.h" +#include "expr.h" #include "annotate.h" #include "pool.h" @@ -47,6 +48,9 @@ bool annotation_t::operator<(const annotation_t& rhs) const if (! tag && rhs.tag) return true; if (tag && ! rhs.tag) return false; + if (! value_expr && rhs.value_expr) return true; + if (value_expr && ! rhs.value_expr) return false; + if (price) { if (price->commodity().symbol() < rhs.price->commodity().symbol()) return true; @@ -63,6 +67,10 @@ bool annotation_t::operator<(const annotation_t& rhs) const if (*tag < *rhs.tag) return true; if (*tag > *rhs.tag) return false; } + if (value_expr) { + if (value_expr->text() < rhs.value_expr->text()) return true; + if (value_expr->text() > rhs.value_expr->text()) return false; + } return false; } @@ -112,17 +120,41 @@ void annotation_t::parse(std::istream& in) 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")); + c = static_cast(in.peek()); + if (c == '(') { + if (value_expr) + throw_(amount_error, + _("Commodity specifies more than one valuation expresion")); - tag = buf; + in.get(c); + READ_INTO(in, buf, 255, c, c != ')'); + if (c == ')') { + in.get(c); + c = static_cast(in.peek()); + if (c == ')') + in.get(c); + else + throw_(amount_error, + _("Commodity valuation expression lacks closing parentheses")); + } else { + throw_(amount_error, + _("Commodity valuation expression lacks closing parentheses")); + } + + value_expr = expr_t(buf); + } else { + if (tag) + throw_(amount_error, _("Commodity specifies more than one tag")); + + 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(); @@ -156,6 +188,10 @@ void annotation_t::print(std::ostream& out, bool keep_base, if (tag && (! no_computed_annotations || ! has_flags(ANNOTATION_TAG_CALCULATED))) out << " (" << *tag << ')'; + + if (value_expr && (! no_computed_annotations || + ! has_flags(ANNOTATION_VALUE_EXPR_CALCULATED))) + out << " ((" << *value_expr << "))"; } bool keep_details_t::keep_all(const commodity_t& comm) const @@ -220,12 +256,14 @@ annotated_commodity_t::strip_annotations(const keep_details_t& what_to_keep) if ((keep_price && details.price) || (keep_date && details.date) || - (keep_tag && details.tag)) + (keep_tag && details.tag) || + details.value_expr) { new_comm = pool().find_or_create (referent(), annotation_t(keep_price ? details.price : none, keep_date ? details.date : none, - keep_tag ? details.tag : none)); + keep_tag ? details.tag : none, + details.value_expr)); // Transfer over any relevant annotation flags, as they still apply. if (new_comm->annotated) { @@ -238,6 +276,8 @@ annotated_commodity_t::strip_annotations(const keep_details_t& what_to_keep) new_details.add_flags(details.flags() & ANNOTATION_DATE_CALCULATED); if (keep_tag) new_details.add_flags(details.flags() & ANNOTATION_TAG_CALCULATED); + if (details.value_expr) + new_details.add_flags(details.flags() & ANNOTATION_VALUE_EXPR_CALCULATED); } return *new_comm; diff --git a/src/annotate.h b/src/annotate.h index 29294e88..eb87a1b2 100644 --- a/src/annotate.h +++ b/src/annotate.h @@ -46,29 +46,38 @@ #ifndef _ANNOTATE_H #define _ANNOTATE_H +#include "expr.h" + namespace ledger { struct annotation_t : public supports_flags<>, public equality_comparable { -#define ANNOTATION_PRICE_CALCULATED 0x01 -#define ANNOTATION_PRICE_FIXATED 0x02 -#define ANNOTATION_DATE_CALCULATED 0x04 -#define ANNOTATION_TAG_CALCULATED 0x08 +#define ANNOTATION_PRICE_CALCULATED 0x01 +#define ANNOTATION_PRICE_FIXATED 0x02 +#define ANNOTATION_DATE_CALCULATED 0x04 +#define ANNOTATION_TAG_CALCULATED 0x08 +#define ANNOTATION_VALUE_EXPR_CALCULATED 0x10 optional price; optional date; optional tag; - - explicit annotation_t(const optional& _price = none, - const optional& _date = none, - const optional& _tag = none) - : supports_flags<>(), price(_price), date(_date), tag(_tag) { - TRACE_CTOR(annotation_t, "const optional& + date_t + string"); + optional value_expr; + + explicit annotation_t(const optional& _price = none, + const optional& _date = none, + const optional& _tag = none, + const optional& _value_expr = none) + : supports_flags<>(), price(_price), date(_date), tag(_tag), + value_expr(_value_expr) { + TRACE_CTOR(annotation_t, + "const optional& + date_t + string + expr_t"); } annotation_t(const annotation_t& other) : supports_flags<>(other.flags()), - price(other.price), date(other.date), tag(other.tag) { + price(other.price), date(other.date), tag(other.tag), + value_expr(other.value_expr) + { TRACE_CTOR(annotation_t, "copy"); } ~annotation_t() { @@ -76,14 +85,15 @@ struct annotation_t : public supports_flags<>, } operator bool() const { - return price || date || tag; + return price || date || tag || value_expr; } bool operator<(const annotation_t& rhs) const; bool operator==(const annotation_t& rhs) const { - return (price == rhs.price && - date == rhs.date && - tag == rhs.tag); + return (price == rhs.price && + date == rhs.date && + tag == rhs.tag && + value_expr == rhs.value_expr); } void parse(std::istream& in); @@ -133,6 +143,12 @@ inline void to_xml(std::ostream& out, const annotation_t& details) push_xml y(out, "tag"); out << y.guard(*details.tag); } + + if (details.value_expr) + { + push_xml y(out, "value-expr"); + out << y.guard(details.value_expr->text()); + } } struct keep_details_t diff --git a/src/expr.h b/src/expr.h index e082efa5..ab3487fe 100644 --- a/src/expr.h +++ b/src/expr.h @@ -58,6 +58,9 @@ public: typedef intrusive_ptr ptr_op_t; typedef intrusive_ptr const_ptr_op_t; + friend void intrusive_ptr_add_ref(const op_t * op); + friend void intrusive_ptr_release(const op_t * op); + enum check_expr_kind_t { EXPR_GENERAL, EXPR_ASSERTION, diff --git a/src/predicate.cc b/src/predicate.cc deleted file mode 100644 index 58d6c752..00000000 --- a/src/predicate.cc +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2003-2012, 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 - -#include "predicate.h" -#include "query.h" -#include "op.h" - -namespace ledger { - -} // namespace ledger diff --git a/test/unit/t_expr.cc b/test/unit/t_expr.cc index f882f3a1..c10ee029 100644 --- a/test/unit/t_expr.cc +++ b/test/unit/t_expr.cc @@ -1,5 +1,5 @@ #define BOOST_TEST_DYN_LINK -#define BOOST_TEST_MODULE expr +//#define BOOST_TEST_MODULE expr #include #include diff --git a/tools/Makefile.am b/tools/Makefile.am index 671db294..a598966a 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -8,7 +8,6 @@ DISTCLEANFILES = .timestamp lib_LTLIBRARIES = \ libledger_report.la \ libledger_data.la \ - libledger_expr.la \ libledger_math.la \ libledger_util.la @@ -28,6 +27,14 @@ libledger_util_la_CPPFLAGS = $(lib_cppflags) libledger_util_la_LDFLAGS = -release $(LIBVERSION) libledger_math_la_SOURCES = \ + src/format.cc \ + src/query.cc \ + src/scope.cc \ + src/expr.cc \ + src/op.cc \ + src/parser.cc \ + src/token.cc \ + src/value.cc \ src/balance.cc \ src/quotes.cc \ src/history.cc \ @@ -39,22 +46,8 @@ libledger_math_la_SOURCES = \ libledger_math_la_CPPFLAGS = $(lib_cppflags) libledger_math_la_LDFLAGS = -release $(LIBVERSION) -libledger_expr_la_SOURCES = \ - src/option.cc \ - src/format.cc \ - src/query.cc \ - src/predicate.cc \ - src/scope.cc \ - src/expr.cc \ - src/op.cc \ - src/parser.cc \ - src/token.cc \ - src/value.cc - -libledger_expr_la_CPPFLAGS = $(lib_cppflags) -libledger_expr_la_LDFLAGS = -release $(LIBVERSION) - libledger_data_la_SOURCES = \ + src/option.cc \ src/lookup.cc \ src/compare.cc \ src/iterators.cc \ @@ -204,7 +197,6 @@ DISTCLEANFILES += ledger.elc timeclock.elc all_sources = $(libledger_util_la_SOURCES) \ $(libledger_math_la_SOURCES) \ - $(libledger_expr_la_SOURCES) \ $(libledger_data_la_SOURCES) \ $(libledger_report_la_SOURCES) \ $(libledger_python_la_SOURCES) \ @@ -259,8 +251,7 @@ TESTS = RegressTests BaselineTests ManualTests ConfirmTests \ if HAVE_BOOST_TEST TESTS += \ UtilTests \ - MathTests \ - ExprTests + MathTests # DataTests \ # ReportTests endif @@ -285,6 +276,7 @@ UtilTests_CPPFLAGS = -I$(srcdir)/test $(lib_cppflags) UtilTests_LDADD = libledger_util.la $(TESTLIBS) MathTests_SOURCES = \ + test/unit/t_expr.cc \ test/unit/t_commodity.cc \ test/unit/t_amount.cc \ test/unit/t_balance.cc @@ -292,16 +284,10 @@ MathTests_SOURCES = \ MathTests_CPPFLAGS = -I$(srcdir)/test $(lib_cppflags) MathTests_LDADD = libledger_math.la $(UtilTests_LDADD) -ExprTests_SOURCES = \ - test/unit/t_expr.cc - -ExprTests_CPPFLAGS = -I$(srcdir)/test $(lib_cppflags) -ExprTests_LDADD = libledger_expr.la $(MathTests_LDADD) - DataTests_SOURCES = DataTests_CPPFLAGS = -I$(srcdir)/test $(lib_cppflags) -DataTests_LDADD = libledger_data.la $(ExprTests_LDADD) +DataTests_LDADD = libledger_data.la $(MathTests_LDADD) ReportTests_SOURCES = @@ -311,7 +297,6 @@ ReportTests_LDADD = libledger_report.la $(DataTests_LDADD) all_tests_sources = \ $(UtilTests_SOURCES) \ $(MathTests_SOURCES) \ - $(ExprTests_SOURCES) \ $(DataTests_SOURCES) \ $(ReportTests_SOURCES) @@ -421,8 +406,6 @@ unittests: check 2>&1 | grep -v '^GuardMalloc:' @sh $(FULLCHECK) $(top_builddir)/MathTests$(EXEEXT) --verify \ 2>&1 | grep -v '^GuardMalloc:' - @sh $(FULLCHECK) $(top_builddir)/ExprTests$(EXEEXT) --verify \ - 2>&1 | grep -v '^GuardMalloc:' # @sh $(FULLCHECK) $(top_builddir)/DataTests$(EXEEXT) --verify \ # 2>&1 | grep -v '^GuardMalloc:' # @sh $(FULLCHECK) $(top_builddir)/ReportTests$(EXEEXT) --verify \ -- cgit v1.2.3 From 0b8d024efc71444209bdb9f66287c4dbe2db43c0 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 7 Mar 2012 11:30:45 -0600 Subject: Removed unused file series.h --- src/series.h | 135 ------------------------------------------------------ tools/Makefile.am | 1 - 2 files changed, 136 deletions(-) delete mode 100644 src/series.h (limited to 'tools/Makefile.am') diff --git a/src/series.h b/src/series.h deleted file mode 100644 index 75b98194..00000000 --- a/src/series.h +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) 2003-2012, 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 expr - */ - -/** - * @file series.h - * @author John Wiegley - * - * @ingroup expr - */ -#ifndef _SERIES_H -#define _SERIES_H - -#include "scope.h" - -namespace ledger { - -class expr_series_t -{ -protected: - scope_t * context; - -public: - optional > exprs; - expr_t default_expr; - std::string variable; - - expr_series_t(const std::string& _variable) - : context(NULL), default_expr(_variable), variable(_variable) { - TRACE_CTOR(expr_series_t, "std::string"); - } - expr_series_t(const expr_t& expr, const std::string& _variable) - : context(const_cast(expr).get_context()), - default_expr(expr), variable(_variable) { - TRACE_CTOR(expr_series_t, "expr_t, std::string"); - } - expr_series_t(const expr_series_t& other) - : context(other.context), exprs(other.exprs), - default_expr(other.default_expr), variable(other.variable) { - TRACE_CTOR(expr_series_t, "copy"); - } - virtual ~expr_series_t() { - TRACE_DTOR(expr_series_t); - } - - scope_t * get_context() { - return context; - } - void set_context(scope_t * scope) { - context = scope; - } - - bool empty() const { - return ! exprs || exprs->empty(); - } - - void push_back(const expr_t& expr) { - if (! exprs) - exprs = std::list(); - exprs->push_back(expr); - } - void pop_back() { - assert(exprs); - exprs->pop_back(); - } - - void mark_uncompiled() { - if (exprs) - foreach (expr_t& expr, *exprs) - expr.mark_uncompiled(); - else - default_expr.mark_uncompiled(); - } - - void compile(scope_t& scope) { - if (exprs) - foreach (expr_t& expr, *exprs) - expr.compile(scope); - else - default_expr.compile(scope); - } - - value_t calc(scope_t& scope) { - if (exprs) { - value_t result; - symbol_scope_t sym_scope(scope); - std::size_t len(exprs->size()); - - foreach (expr_t& expr, *exprs) { - result = expr.calc(sym_scope); - if (--len > 0) - sym_scope.define(symbol_t::FUNCTION, variable, - expr_t::op_t::wrap_value(result)); - } - return result; - } else { - return default_expr.calc(scope); - } - } -}; - -} // namespace ledger - -#endif // _SERIES_H diff --git a/tools/Makefile.am b/tools/Makefile.am index a598966a..6c01936a 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -114,7 +114,6 @@ pkginclude_HEADERS = \ src/query.h \ src/format.h \ src/option.h \ - src/series.h \ \ src/item.h \ src/post.h \ -- cgit v1.2.3 From 5f27ddaba64e896ea0c3d73744a923ec0b3e1863 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Fri, 9 Mar 2012 01:28:29 -0600 Subject: Don't do fullcheck with --gmalloc anymore --- tools/Makefile.am | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'tools/Makefile.am') diff --git a/tools/Makefile.am b/tools/Makefile.am index 6c01936a..c6295aad 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -420,10 +420,10 @@ fullcheck: unittests @$(top_builddir)/ManualTests --verify @$(top_builddir)/ConfirmTests --verify @$(top_builddir)/GenerateTests 20 --verify - @$(top_builddir)/RegressTests --gmalloc - @$(top_builddir)/BaselineTests --gmalloc - @$(top_builddir)/ManualTests --gmalloc - @$(top_builddir)/ConfirmTests --gmalloc +# @$(top_builddir)/RegressTests --gmalloc +# @$(top_builddir)/BaselineTests --gmalloc +# @$(top_builddir)/ManualTests --gmalloc +# @$(top_builddir)/ConfirmTests --gmalloc @$(top_builddir)/GenerateTests 10000 # @$(top_builddir)/GenerateTests --gmalloc -- cgit v1.2.3 From 37347bad56646f3f3e7e10ee0303e6429c29bbba Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Tue, 13 Mar 2012 10:35:08 -0500 Subject: Add experimental support for select queries --- src/report.cc | 3 + src/select.cc | 441 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/select.h | 55 +++++++ tools/Makefile.am | 2 + 4 files changed, 501 insertions(+) create mode 100644 src/select.cc create mode 100644 src/select.h (limited to 'tools/Makefile.am') diff --git a/src/report.cc b/src/report.cc index e93bd6fd..91de2eb5 100644 --- a/src/report.cc +++ b/src/report.cc @@ -41,6 +41,7 @@ #include "iterators.h" #include "filters.h" #include "precmd.h" +#include "select.h" #include "stats.h" #include "generate.h" #include "draft.h" @@ -1633,6 +1634,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return WRAP_FUNCTOR(report_statistics); else if (is_eq(p, "source")) return WRAP_FUNCTOR(source_command); + else if (is_eq(p, "select")) + return WRAP_FUNCTOR(select_command); break; case 'x': diff --git a/src/select.cc b/src/select.cc new file mode 100644 index 00000000..56bd3f2d --- /dev/null +++ b/src/select.cc @@ -0,0 +1,441 @@ +/* + * Copyright (c) 2003-2012, 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 + +#include "select.h" +#include "journal.h" +#include "account.h" +#include "report.h" +#include "output.h" +#include "print.h" +#include "chain.h" +#include "filters.h" +#include "scope.h" +#include "op.h" + +namespace ledger { + +namespace { + bool get_principal_identifiers(expr_t::ptr_op_t expr, string& ident, + bool do_transforms = false) + { + bool result = true; + + if (expr->is_ident()) { + string name(expr->as_ident()); + if (name == "date" || name == "aux_date" || name == "payee") { + if (! ident.empty() && + ! (name == "date" || name == "aux_date" || name == "payee")) + result = false; + ident = name; + } + else if (name == "account") { + if (! ident.empty() && ! (name == "account")) + result = false; + ident = name; + if (do_transforms) + expr->set_ident("display_account"); + } + else if (name == "amount") { + if (! ident.empty() && ! (name == "amount")) + result = false; + ident = name; + if (do_transforms) + expr->set_ident("display_amount"); + } + else if (name == "total") { + if (! ident.empty() && ! (name == "total")) + result = false; + ident = name; + if (do_transforms) + expr->set_ident("display_total"); + } + } + + if (expr->kind > expr_t::op_t::TERMINALS || expr->is_scope()) { + if (expr->left()) { + if (! get_principal_identifiers(expr->left(), ident, do_transforms)) + result = false; + if (expr->kind > expr_t::op_t::UNARY_OPERATORS && expr->has_right()) + if (! get_principal_identifiers(expr->right(), ident, do_transforms)) + result = false; + } + } + + return result; + } +} + +value_t select_command(call_scope_t& args) +{ + string text = "select " + join_args(args); + if (text.empty()) + throw std::logic_error(_("Usage: select TEXT")); + + report_t& report(find_scope(args)); + + // Our first step is to divide the select statement into its principal + // parts: + // + // SELECT + // FROM + // WHERE + // DISPLAY + // COLLECT + // GROUP BY + // STYLE + + boost::regex select_re + ("(select|from|where|display|collect|group\\s+by|style)\\s+" + "(.+?)" + "(?=(\\s+(from|where|display|collect|group\\s+by|style)\\s+|$))", + boost::regex::perl | boost::regex::icase); + + boost::regex from_accounts_re("from\\s+accounts\\>"); + bool accounts_report = boost::regex_search(text, from_accounts_re); + + boost::sregex_iterator m1(text.begin(), text.end(), select_re); + boost::sregex_iterator m2; + + expr_t::ptr_op_t report_functor; + std::ostringstream formatter; + + while (m1 != m2) { + const boost::match_results& match(*m1); + + string keyword(match[1]); + string arg(match[2]); + + DEBUG("select.parse", "keyword: " << keyword); + DEBUG("select.parse", "arg: " << arg); + + if (keyword == "select") { + expr_t args_expr(arg); + value_t columns(split_cons_expr(args_expr.get_op())); + bool first = true; + string thus_far = ""; + + std::size_t cols = 0; + if (report.HANDLED(columns_)) + cols = lexical_cast(report.HANDLER(columns_).value); + else if (const char * columns_env = std::getenv("COLUMNS")) + cols = lexical_cast(columns_env); + else + cols = 80; + + std::size_t date_width = + (report.HANDLED(date_width_) ? + lexical_cast(report.HANDLER(date_width_).str()) : + static_cast + (format_date(CURRENT_DATE(),FMT_PRINTED).length())); + std::size_t payee_width = + (report.HANDLED(payee_width_) ? + lexical_cast(report.HANDLER(payee_width_).str()) : + std::size_t(double(cols) * 0.263157)); + std::size_t account_width = + (report.HANDLED(account_width_) ? + lexical_cast(report.HANDLER(account_width_).str()) : + std::size_t(double(cols) * 0.302631)); + std::size_t amount_width = + (report.HANDLED(amount_width_) ? + lexical_cast(report.HANDLER(amount_width_).str()) : + std::size_t(double(cols) * 0.157894)); + std::size_t total_width = + (report.HANDLED(total_width_) ? + lexical_cast(report.HANDLER(total_width_).str()) : + amount_width); + std::size_t meta_width = + (report.HANDLED(meta_width_) ? + lexical_cast(report.HANDLER(meta_width_).str()) : + 10); + + bool saw_date = false; + bool saw_payee = false; + bool saw_account = false; + bool saw_amount = false; + bool saw_total = false; + bool saw_meta = false; + + std::size_t cols_needed = 0; + foreach (const value_t& column, columns.to_sequence()) { + string ident; + if (get_principal_identifiers(as_expr(column), ident)) { + if (ident == "date" || ident == "aux_date") { + cols_needed += date_width + 1; + saw_date = true; + } + else if (ident == "payee") { + cols_needed += payee_width + 1; + saw_payee = true; + } + else if (ident == "account") { + cols_needed += account_width + 1; + saw_account = true; + } + else if (ident == "amount") { + cols_needed += amount_width + 1; + saw_amount = true; + } + else if (ident == "total") { + cols_needed += total_width + 1; + saw_total = true; + } + else { + cols_needed += meta_width + 1; + saw_meta = true; + } + } + } + + while ((saw_account || saw_payee) && cols_needed < cols) { + if (saw_account && cols_needed < cols) { + ++account_width; + ++cols_needed; + if (cols_needed < cols) { + ++account_width; + ++cols_needed; + } + } + if (saw_payee && cols_needed < cols) { + ++payee_width; + ++cols_needed; + } + } + + while ((saw_account || saw_payee) && cols_needed > cols && + account_width > 5 && payee_width > 5) { + DEBUG("auto.columns", "adjusting account down"); + if (saw_account && cols_needed > cols) { + --account_width; + --cols_needed; + if (cols_needed > cols) { + --account_width; + --cols_needed; + } + } + if (saw_payee && cols_needed > cols) { + --payee_width; + --cols_needed; + } + DEBUG("auto.columns", "account_width now = " << account_width); + } + + if (! report.HANDLED(date_width_)) + report.HANDLER(date_width_).value = to_string(date_width); + if (! report.HANDLED(payee_width_)) + report.HANDLER(payee_width_).value = to_string(payee_width); + if (! report.HANDLED(account_width_)) + report.HANDLER(account_width_).value = to_string(account_width); + if (! report.HANDLED(amount_width_)) + report.HANDLER(amount_width_).value = to_string(amount_width); + if (! report.HANDLED(total_width_)) + report.HANDLER(total_width_).value = to_string(total_width); + + foreach (const value_t& column, columns.to_sequence()) { + if (first) + first = false; + else + formatter << ' '; + + formatter << "%("; + + string ident; + if (get_principal_identifiers(as_expr(column), ident, true)) { + if (ident == "date" || ident == "aux_date") { + formatter << "ansify_if(" + << "ansify_if(justify(format_date("; + + as_expr(column)->print(formatter); + + formatter << "), int(date_width)),"; + formatter << "green if color and date > today)," + << "bold if should_bold)"; + + if (! thus_far.empty()) + thus_far += " + "; + thus_far += "int(date_width) + 1"; + } + else if (ident == "payee") { + formatter << "ansify_if(" + << "ansify_if(justify(truncated("; + + as_expr(column)->print(formatter); + + formatter << ", int(payee_width)), int(payee_width)),"; + formatter << "bold if color and !cleared and actual)," + << "bold if should_bold)"; + + if (! thus_far.empty()) + thus_far += " + "; + thus_far += "int(payee_width) + 1"; + } + else if (ident == "account") { + formatter << "ansify_if(" + << "ansify_if("; + + if (accounts_report) { + formatter << "partial_account(options.flat), blue if color),"; + } else { + formatter << "justify(truncated("; + as_expr(column)->print(formatter); + formatter << ", int(account_width), int(abbrev_len))," + << "int(account_width)),"; + formatter << "true, color),"; + + if (! thus_far.empty()) + thus_far += " + "; + thus_far += "int(account_width) + 1"; + } + + formatter << " bold if should_bold)"; + } + else if (ident == "amount" || ident == "total") { + formatter << "ansify_if(" + << "justify(scrub("; + + as_expr(column)->print(formatter); + + formatter << "), "; + + if (ident == "amount") + formatter << "int(amount_width),"; + else + formatter << "int(total_width),"; + + if (! thus_far.empty()) + thus_far += " + "; + + if (ident == "amount") + thus_far += "int(amount_width)"; + else + thus_far += "int(total_width)"; + + if (thus_far.empty()) + formatter << "-1"; + else + formatter << thus_far; + + formatter << ", true, color)," + << " bold if should_bold)"; + + thus_far += " + 1"; + } + else { + formatter << "ansify_if(" + << "justify(truncated("; + + as_expr(column)->print(formatter); + + formatter << ", int(meta_width or 10)), int(meta_width) or 10),"; + formatter << "bold if should_bold)"; + + if (! thus_far.empty()) + thus_far += " + "; + thus_far += "(int(meta_width) or 10) + 1"; + } + } + formatter << ")"; + } + formatter << "\\n"; + } + else if (keyword == "from") { + DEBUG("select.parse", "formatter: " << formatter.str()); + + if (arg == "xacts" || arg == "txns" || arg == "transactions") { + report_functor = expr_t::op_t::wrap_functor + (reporter<>(post_handler_ptr(new print_xacts(report, + report.HANDLED(raw))), + report, string("#select"))); + } + else if (arg == "posts" || arg == "postings") { + report_functor = expr_t::op_t::wrap_functor + (reporter<>(post_handler_ptr(new format_posts(report, formatter.str())), + report, string("#select"))); + } + else if (arg == "accounts") { + report_functor = expr_t::op_t::wrap_functor + (reporter + (acct_handler_ptr(new format_accounts(report, formatter.str())), + report, string("#select"))); + } + else if (arg == "commodities") { + report_functor = expr_t::op_t::wrap_functor + (reporter + (post_handler_ptr(new format_posts(report, formatter.str())), + report, string("#select"))); + } + } + else if (keyword == "where") { +#if 0 + query_t query; + keep_details_t keeper(true, true, true); + expr_t::ptr_op_t expr = + query.parse_args(string_value(arg).to_sequence(), keeper, false, true); + report.HANDLER(limit_).on("#select", query.get_query(query_t::QUERY_LIMIT)); +#else + report.HANDLER(limit_).on("#select", arg); +#endif + } + else if (keyword == "display") { + report.HANDLER(display_).on("#select", arg); + } + else if (keyword == "collect") { + report.HANDLER(amount_).on("#select", arg); + } + else if (keyword == "group by") { + report.HANDLER(group_by_).on("#select", arg); + } + else if (keyword == "style") { + if (arg == "csv") { + } + else if (arg == "xml") { + } + else if (arg == "emacs") { + } + else if (arg == "org") { + } + } + + ++m1; + } + + if (! report_functor) { + report_functor = expr_t::op_t::wrap_functor + (reporter<>(post_handler_ptr(new format_posts(report, formatter.str())), + report, string("#select"))); + } + + call_scope_t call_args(report); + return report_functor->as_function()(call_args); +} + +} // namespace ledger diff --git a/src/select.h b/src/select.h new file mode 100644 index 00000000..54883d22 --- /dev/null +++ b/src/select.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2003-2012, 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 select + */ + +/** + * @file select.h + * @author John Wiegley + * + * @ingroup select + */ +#ifndef _SELECT_H +#define _SELECT_H + +#include "utils.h" +#include "value.h" + +namespace ledger { + +class call_scope_t; +value_t select_command(call_scope_t& args); + +} // namespace ledger + +#endif // _SELECT_H diff --git a/tools/Makefile.am b/tools/Makefile.am index c6295aad..5d299b05 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -79,6 +79,7 @@ libledger_report_la_SOURCES = \ src/chain.cc \ src/filters.cc \ src/report.cc \ + src/select.cc \ src/session.cc libledger_report_la_CPPFLAGS = $(lib_cppflags) @@ -128,6 +129,7 @@ pkginclude_HEADERS = \ src/lookup.h \ \ src/session.h \ + src/select.h \ src/report.h \ src/filters.h \ src/chain.h \ -- cgit v1.2.3 From 815501f8b50b0889b4a51020cdbbd514b4c8b975 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 17 Mar 2012 16:04:30 -0500 Subject: Package src/context.h into the final dist --- tools/Makefile.am | 1 + 1 file changed, 1 insertion(+) (limited to 'tools/Makefile.am') diff --git a/tools/Makefile.am b/tools/Makefile.am index 5d299b05..f1582e50 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -121,6 +121,7 @@ pkginclude_HEADERS = \ src/xact.h \ src/account.h \ src/journal.h \ + src/context.h \ src/temps.h \ src/archive.h \ src/timelog.h \ -- cgit v1.2.3 From 0259381bae0efeff449dcc2aa3fbe2a42b7cb15c Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Fri, 30 Mar 2012 04:46:53 -0500 Subject: Added test/python to EXTRA_DIST --- tools/Makefile.am | 1 + 1 file changed, 1 insertion(+) (limited to 'tools/Makefile.am') diff --git a/tools/Makefile.am b/tools/Makefile.am index f1582e50..232ce768 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -349,6 +349,7 @@ EXTRA_DIST += test/regress test/convert.py test/LedgerHarness.py if HAVE_BOOST_PYTHON TEST_PYTHON_FLAGS = --python +EXTRA_DIST += test/python else TEST_PYTHON_FLAGS = endif -- cgit v1.2.3 From e3ac24301990a77a296d103388a64bea716ce5cd Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sun, 8 Apr 2012 02:57:18 -0500 Subject: Started work on a "views" report document model --- acprep | 2 + src/views.cc | 259 +++++++++++++++++++++++++++++++ src/views.h | 457 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/xact.h | 4 + tools/Makefile.am | 2 + 5 files changed, 724 insertions(+) create mode 100644 src/views.cc create mode 100644 src/views.h (limited to 'tools/Makefile.am') diff --git a/acprep b/acprep index ddf7639d..72fcd642 100755 --- a/acprep +++ b/acprep @@ -1048,6 +1048,8 @@ class PrepareBuild(CommandLineApp): self.CXXFLAGS.append('-march=nocona') self.CXXFLAGS.append('-msse3') + self.CXXFLAGS.append('-DDOCUMENT_MODEL=1') + self.locate_darwin_libraries() def setup_for_system(self): diff --git a/src/views.cc b/src/views.cc new file mode 100644 index 00000000..bbd58ce2 --- /dev/null +++ b/src/views.cc @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2003-2012, 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. + */ + +#ifdef DOCUMENT_MODEL + +#include + +#include "views.h" +#include "report.h" +#include "journal.h" +#include "xact.h" +#include "post.h" +#include "account.h" + +namespace ledger { + +r_xact_ptr r_journal_t::create_xact(xact_t * xact) +{ + r_xact_ptr x = new r_xact_t(this, xact); + add_xact(x); + assert(xact->data == NULL); + xact->data = &x; + return x; +} + +void r_journal_t::add_xact(r_xact_ptr xact) +{ + xacts.push_back(xact); +} + +r_post_ptr r_journal_t::add_post(post_t * post) +{ + r_xact_ptr x; + if (post->xact->data) + x = *static_cast(post->xact->data); + else + x = create_xact(post->xact); + + r_post_ptr p = create_post(post, x, create_account(post->account)); + return p; +} + +void r_journal_t::add_post(r_post_ptr post) +{ + posts.push_back(post); +} + +r_post_ptr r_journal_t::create_post(post_t * post, r_xact_ptr xact, + r_account_ptr account) +{ + r_post_ptr p = new r_post_t(this, post, xact, account); + + add_post(p); + xact->add_post(p); + account->add_post(p); + + return p; +} + +r_post_ptr r_journal_t::create_post(r_post_ptr post, r_xact_ptr xact, + r_account_ptr account) +{ + r_post_ptr temp(new r_post_t(*post.get())); + + add_post(temp); + + temp->set_xact(xact); + xact->add_post(temp); + + temp->set_account(account); + account->add_post(temp); + + return temp; +} + +r_account_ptr r_journal_t::create_account(account_t * account) +{ + return create_account(account->fullname()); +} + +r_account_ptr r_journal_t::create_account(const std::string& name) +{ + return master_ptr->create_account(name); +} + + +const optional r_item_t::position() const +{ + return ptr()->pos; +} + +date_t r_item_t::date() const +{ + return ptr()->date(); +} + +void r_item_t::set_date(const date_t& when) +{ +} + +item_t::state_t r_item_t::state() const +{ + return ptr()->state(); +} + +void r_item_t::set_state(item_t::state_t val) +{ +} + +string r_item_t::payee() const +{ + if (optional desc = get_tag(_("Payee"))) + return desc->as_string(); + else + return empty_string; +} + +void r_item_t::set_payee(const string& name) +{ +} + +void r_item_t::define(const symbol_t::kind_t, const string& name, + expr_t::ptr_op_t def) +{ + bind_scope_t bound_scope(*scope_t::default_scope, *this); + set_tag(name, def->calc(bound_scope)); +} + +expr_t::ptr_op_t r_item_t::lookup(const symbol_t::kind_t kind, + const string& name) +{ + if (kind != symbol_t::FUNCTION) + return NULL; + + switch (name[0]) { + } + + return base_item->lookup(kind, name); +} + + +string r_xact_t::description() +{ + return ptr()->description(); +} + +void r_xact_t::add_post(r_post_ptr post) +{ + posts.push_back(post); +} + +string r_xact_t::payee() const +{ + string desc(r_item_t::payee()); + if (desc.empty()) + return ptr()->payee; + else + return desc; +} + + +string r_post_t::description() +{ + return ptr()->description(); +} + +string r_post_t::payee() const +{ + string desc(r_item_t::payee()); + if (desc.empty()) + return const_cast(this)->xact()->payee(); + else + return desc; +} + + +string r_account_t::description() +{ + return string(_("account ")) + fullname(); +} + +void r_account_t::add_post(r_post_ptr post) +{ + posts.push_back(post); +} + +r_account_ptr r_account_t::create_account(const std::string& fname) +{ + string::size_type sep = fname.find(':'); + string head, tail; + if (sep == string::npos) { + head = fname; + } else { + head = string(fname, 0, sep); + tail = string(fname, sep + 1); + } + + std::pair result = + accounts.insert(r_accounts_map::value_type + (head, new r_account_t(journal_ptr, this, name))); + + r_account_ptr acct((*result.first).second); + if (tail.empty()) + return acct; + else + return acct->create_account(tail); +} + +string r_account_t::fullname() const +{ + if (! _fullname.empty()) { + return _fullname; + } else { + r_account_ptr first = NULL; + string fname = name; + + while (! first || first->parent_ptr) { + first = first ? first->parent_ptr : parent_ptr; + if (! first->name.empty()) + fname = first->name + ":" + fname; + } + + _fullname = fname; + + return fname; + } +} + +} // namespace ledger + +#endif /* DOCUMENT_MODEL */ diff --git a/src/views.h b/src/views.h new file mode 100644 index 00000000..f9a007b7 --- /dev/null +++ b/src/views.h @@ -0,0 +1,457 @@ +/* + * Copyright (c) 2003-2012, 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 views + */ + +/** + * @file views.h + * @author John Wiegley + * + * @ingroup views + */ +#ifndef _VIEWS_H +#define _VIEWS_H + +#include "utils.h" + +#ifdef DOCUMENT_MODEL + +#include "scope.h" +#include "item.h" +#include "report.h" +#include "post.h" +#include "predicate.h" + +namespace ledger { + +class journal_t; +class xact_t; +class post_t; +class account_t; +class report_t; + +class r_base_t : public supports_flags, + public scope_t +{ +public: + r_base_t() : refc(0) { + TRACE_CTOR(r_base_t, ""); + } + r_base_t(const r_base_t& other) : refc(0) { + TRACE_CTOR(r_base_t, "copy"); + } + virtual ~r_base_t() { + TRACE_DTOR(r_base_t); + } + +protected: + /** + * `refc' holds the current reference count for each object. + */ + mutable int refc; + + /** + * Reference counting methods. The intrusive_ptr_* methods are used + * by boost::intrusive_ptr to manage the calls to acquire and release. + */ + void acquire() const { + VERIFY(refc >= 0); + refc++; + } + void release() const { + VERIFY(refc > 0); + if (--refc == 0) + checked_delete(this); + } + + friend inline void intrusive_ptr_add_ref(r_base_t * r_ptr) { + r_ptr->acquire(); + } + friend inline void intrusive_ptr_release(r_base_t * r_ptr) { + r_ptr->release(); + } +}; + +class r_journal_t; +class r_item_t; +class r_xact_t; +class r_post_t; +class r_account_t; + +typedef intrusive_ptr r_journal_ptr; +typedef intrusive_ptr r_item_ptr; +typedef intrusive_ptr r_xact_ptr; +typedef intrusive_ptr r_post_ptr; +typedef intrusive_ptr r_account_ptr; + +typedef std::list r_xacts_list; +typedef std::list r_posts_list; + +class r_journal_t : public r_base_t +{ + journal_t * base_journal; + + journal_t * ptr() { + return base_journal; + } + const journal_t * ptr() const { + return base_journal; + } + + r_account_ptr master_ptr; + r_xacts_list xacts; + r_posts_list posts; + + void set_master(r_account_ptr ptr) { + master_ptr = ptr; + } + +public: + r_journal_t(journal_t * _journal, r_account_ptr _master) + : r_base_t(), base_journal(_journal), master_ptr(_master) { + TRACE_CTOR(r_journal_t, "journal_t *, account_t *"); + } + r_journal_t(const r_journal_t& other) + : r_base_t(other), + base_journal(other.base_journal), + master_ptr(other.master_ptr), + xacts(other.xacts), + posts(other.posts) { + TRACE_CTOR(r_journal_t, "copy"); + } + virtual ~r_journal_t() { + TRACE_DTOR(r_journal_t); + } + + r_xact_ptr create_xact(xact_t * xact = NULL); + + void add_xact(r_xact_ptr xact); + + r_xacts_list::iterator xacts_begin() { + return xacts.begin(); + } + r_xacts_list::iterator xacts_end() { + return xacts.end(); + } + + r_post_ptr add_post(post_t * post); + void add_post(r_post_ptr post); + + r_post_ptr create_post(post_t * post = NULL, r_xact_ptr xact = NULL, + r_account_ptr account = NULL); + r_post_ptr create_post(r_post_ptr post, r_xact_ptr xact = NULL, + r_account_ptr account = NULL); + + r_posts_list::iterator posts_begin() { + return posts.begin(); + } + r_posts_list::iterator posts_end() { + return posts.end(); + } + + r_account_ptr create_account(account_t * account = NULL); + r_account_ptr create_account(const std::string& name); + + friend void to_xml(std::ostream& out, r_journal_ptr journal); +}; + +class r_item_t : public r_base_t +{ +protected: + item_t * base_item; + + item_t * ptr() { + return base_item; + } + const item_t * ptr() const { + return base_item; + } + + r_journal_ptr journal_ptr; + +public: + r_item_t(r_journal_ptr _journal_ptr, item_t * _item) + : r_base_t(), base_item(_item), journal_ptr(_journal_ptr) { + TRACE_CTOR(r_item_t, "r_journal_ptr, item_t *"); + } + r_item_t(const r_item_t& other) + : r_base_t(other), + base_item(other.base_item), + journal_ptr(other.journal_ptr) { + TRACE_CTOR(r_item_t, "copy"); + } + virtual ~r_item_t() { + TRACE_DTOR(r_item_t); + } + + const optional position() const; + + string id() const { + return ptr()->id(); + } + std::size_t seq() const { + return ptr()->seq(); + } + + date_t date() const; + void set_date(const date_t& when); + + item_t::state_t state() const; + void set_state(item_t::state_t val); + + string payee() const; + void set_payee(const string& name); + + optional note() const { + return ptr()->note; + } + + bool has_tag(const string& tag) const { + return ptr()->has_tag(tag); + } + bool has_tag(const mask_t& tag_mask, + const optional& value_mask = none) const { + return ptr()->has_tag(tag_mask, value_mask); + } + + optional get_tag(const string& tag) const { + return ptr()->get_tag(tag); + } + optional get_tag(const mask_t& tag_mask, + const optional& value_mask = none) const { + return ptr()->get_tag(tag_mask, value_mask); + } + + void set_tag(const string& tag, + const optional& value = none, + const bool overwrite_existing = true) { + ptr()->set_tag(tag, value, overwrite_existing); + } + + /** + * Symbol scope methods. + */ + virtual void define(const symbol_t::kind_t, const string&, + expr_t::ptr_op_t); + virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind, + const string& name); + + friend class r_journal_t; + friend void to_xml(std::ostream& out, r_item_ptr item); +}; + +class r_xact_t : public r_item_t +{ + xact_t * ptr() { + return reinterpret_cast(base_item); + } + const xact_t * ptr() const { + return reinterpret_cast(base_item); + } + + r_posts_list posts; + +public: + r_xact_t(r_journal_ptr journal_ptr, xact_t * _xact) + : r_item_t(journal_ptr, reinterpret_cast(_xact)) { + TRACE_CTOR(r_xact_t, "r_journal_ptr, xact_t *"); + } + r_xact_t(const r_xact_t& other) + : r_item_t(other), + posts(other.posts) { + TRACE_CTOR(r_xact_t, "copy"); + } + virtual ~r_xact_t() { + TRACE_DTOR(r_xact_t); + } + + virtual string description(); + + void add_post(r_post_ptr post); + +#if 0 + r_post_ptr create_post(post_t * post = NULL, r_account_ptr account = NULL); + r_post_ptr create_post(r_post_ptr post, r_account_ptr account = NULL); +#endif + + r_posts_list::iterator posts_begin() { + return posts.begin(); + } + r_posts_list::iterator posts_end() { + return posts.end(); + } + + string code() const; + string payee() const; + + friend class r_journal_t; + friend void to_xml(std::ostream& out, r_xact_ptr xact); +}; + +class r_post_t : public r_item_t +{ + post_t * ptr() { + return reinterpret_cast(base_item); + } + const post_t * ptr() const { + return reinterpret_cast(base_item); + } + + r_xact_ptr xact_ptr; + r_account_ptr account_ptr; + + void set_xact(r_xact_ptr ptr) { + xact_ptr = ptr; + } + void set_account(r_account_ptr ptr) { + account_ptr = ptr; + } + +public: + r_post_t(r_journal_ptr journal_ptr, post_t * _post, + r_xact_ptr _xact_ptr, r_account_ptr _account_ptr) + : r_item_t(journal_ptr, reinterpret_cast(_post)), + xact_ptr(_xact_ptr), account_ptr(_account_ptr) { + TRACE_CTOR(r_post_t, "r_journal_ptr, post_t *, r_xact_ptr, r_account_ptr"); + } + r_post_t(const r_post_t& other) + : r_item_t(other), + xact_ptr(other.xact_ptr), + account_ptr(other.account_ptr) { + TRACE_CTOR(r_post_t, "copy"); + } + virtual ~r_post_t() { + TRACE_DTOR(r_post_t); + } + + virtual string description(); + + string payee() const; + + r_xact_ptr xact(); + r_account_ptr account(); + + value_t amount() const; + value_t cost() const; + + std::size_t count() const; + value_t running_total() const; + + optional checkin() const; + optional checkout() const; + + friend class r_journal_t; + friend void to_xml(std::ostream& out, r_post_ptr post); +}; + +typedef std::map r_accounts_map; + +class r_account_t : public r_base_t +{ + r_journal_ptr journal_ptr; + r_account_ptr parent_ptr; + r_accounts_map accounts; + r_posts_list posts; + + string name; + + mutable string _fullname; + +public: + r_account_t(r_journal_ptr _journal_ptr, r_account_ptr _parent_ptr, + string _name) + : r_base_t(), journal_ptr(_journal_ptr), parent_ptr(_parent_ptr), + name(_name) { + TRACE_CTOR(r_account_t, "r_journal_ptr, r_account_ptr, string"); + } + r_account_t(const r_account_t& other) + : r_base_t(other), + journal_ptr(other.journal_ptr), + parent_ptr(other.parent_ptr), + accounts(other.accounts), + posts(other.posts), + name(other.name), + _fullname(other._fullname) { + TRACE_CTOR(r_account_t, "copy"); + } + virtual ~r_account_t() { + TRACE_DTOR(r_account_t); + } + + virtual string description(); + + void add_post(r_post_ptr post); + + r_posts_list::iterator posts_begin() { + return posts.begin(); + } + r_posts_list::iterator posts_end() { + return posts.end(); + } + + r_account_ptr create_account(const std::string& name); + + string fullname() const; + + /** + * Symbol scope methods. + */ + virtual void define(const symbol_t::kind_t, const string&, + expr_t::ptr_op_t) {} + virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind, + const string& fname) { + return NULL; + } + + friend class r_journal_t; + friend void to_xml(std::ostream& out, r_account_ptr account); +}; + +template +void populate_journal(r_journal_ptr journal, report_t& report, + PostsIterator iter, predicate_t& pred) +{ + while (post_t * post = *iter) { + bind_scope_t bound_scope(report, *post); + if (pred.calc(bound_scope)) + journal->add_post(post); + + iter.increment(); + } +} + +} // namespace ledger + +#endif /* DOCUMENT_MODEL */ + +#endif // _VIEWS_H diff --git a/src/xact.h b/src/xact.h index ce00242e..0000e9c6 100644 --- a/src/xact.h +++ b/src/xact.h @@ -109,6 +109,10 @@ public: optional code; string payee; +#ifdef DOCUMENT_MODEL + void * data; +#endif + xact_t() { TRACE_CTOR(xact_t, ""); } diff --git a/tools/Makefile.am b/tools/Makefile.am index 232ce768..04f64530 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -79,6 +79,7 @@ libledger_report_la_SOURCES = \ src/chain.cc \ src/filters.cc \ src/report.cc \ + src/views.cc \ src/select.cc \ src/session.cc @@ -131,6 +132,7 @@ pkginclude_HEADERS = \ \ src/session.h \ src/select.h \ + src/views.h \ src/report.h \ src/filters.h \ src/chain.h \ -- cgit v1.2.3