diff options
-rw-r--r-- | Makefile.am | 48 | ||||
-rw-r--r-- | config.cc | 12 | ||||
-rw-r--r-- | main.py | 2 | ||||
-rw-r--r-- | python.cc | 78 | ||||
-rw-r--r-- | python.h | 55 | ||||
-rw-r--r-- | sample.dat | 12 | ||||
-rwxr-xr-x | setup.py | 26 | ||||
-rwxr-xr-x | setup_amounts.py | 14 | ||||
-rwxr-xr-x | setup_ledger.py | 16 | ||||
-rw-r--r-- | textual.cc | 18 | ||||
-rw-r--r-- | valexpr.cc | 60 | ||||
-rw-r--r-- | valexpr.h | 4 | ||||
-rw-r--r-- | value.cc | 24 |
13 files changed, 296 insertions, 73 deletions
diff --git a/Makefile.am b/Makefile.am index 191017d2..098088ee 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,16 +1,18 @@ -lib_LTLIBRARIES = libamounts.la -libamounts_la_SOURCES = amount.cc balance.cc value.cc -libamounts_la_LDFLAGS = -version-info 2:0 - -lib_LTLIBRARIES += libledger.la -libledger_la_SOURCES = autoxact.cc binary.cc config.cc datetime.cc \ - format.cc journal.cc option.cc parser.cc qif.cc quotes.cc \ - textual.cc valexpr.cc walk.cc +lib_LTLIBRARIES = libledger.la +libledger_la_SOURCES = amount.cc balance.cc value.cc autoxact.cc \ + binary.cc config.cc datetime.cc format.cc journal.cc option.cc \ + parser.cc qif.cc quotes.cc textual.cc valexpr.cc walk.cc if READ_GNUCASH libledger_la_SOURCES += gnucash.cc endif +if HAVE_BOOST_PYTHON +libledger_la_SOURCES += python.cc +libledger_la_CXXFLAGS = -DUSE_BOOST_PYTHON=1 +else +libledger_la_CXXFLAGS = +endif if DEBUG -libledger_la_CXXFLAGS = -DDEBUG_LEVEL=4 +libledger_la_CXXFLAGS += -DDEBUG_LEVEL=4 libledger_la_SOURCES += debug.cc endif libledger_la_LDFLAGS = -version-info 2:0 @@ -22,7 +24,7 @@ if DEBUG ledger_CXXFLAGS = -DDEBUG_LEVEL=4 endif ledger_SOURCES = main.cc -ledger_LDADD = $(LIBOBJS) libamounts.la libledger.la +ledger_LDADD = $(LIBOBJS) libledger.la -lboost_python -L/sw/lib/python2.3/config -lpython2.3 nobase_include_HEADERS = \ amount.h \ @@ -48,17 +50,12 @@ info_TEXINFOS = ledger.texi ###################################################################### -if HAVE_PYTHON if HAVE_BOOST_PYTHON -noinst_LIBRARIES = libamounts_bpy.a -libamounts_bpy_a_SOURCES = amount.cc balance.cc value.cc -libamounts_bpy_a_CXXFLAGS = -DUSE_BOOST_PYTHON=1 - -noinst_LIBRARIES += libledger_bpy.a -libledger_bpy_a_SOURCES = autoxact.cc binary.cc config.cc datetime.cc \ - format.cc journal.cc option.cc parser.cc qif.cc quotes.cc \ - textual.cc valexpr.cc walk.cc +noinst_LIBRARIES = libledger_bpy.a +libledger_bpy_a_SOURCES = amount.cc balance.cc value.cc autoxact.cc \ + binary.cc config.cc datetime.cc format.cc journal.cc option.cc \ + parser.cc qif.cc quotes.cc textual.cc valexpr.cc walk.cc python.cc libledger_bpy_a_CXXFLAGS = -DUSE_BOOST_PYTHON=1 if READ_GNUCASH libledger_bpy_a_SOURCES += gnucash.cc @@ -68,17 +65,12 @@ libledger_bpy_a_CXXFLAGS += -DDEBUG_LEVEL=4 libledger_bpy_a_SOURCES += debug.cc endif -bin_PROGRAMS += amounts.so ledger.so - -amounts.so: pyamounts.cc libamounts_bpy.a - CFLAGS="$(CPPFLAGS) -L." python setup_amounts.py build --build-lib=. +bin_PROGRAMS += ledger.so -ledger.so: pyledger.cc libamounts_bpy.a libledger_bpy.a - CFLAGS="$(CPPFLAGS) -L." python setup_ledger.py build --build-lib=. +ledger.so: pyledger.cc libledger_bpy.a + CFLAGS="$(CPPFLAGS) -L." python setup.py build --build-lib=. install-exec-hook: - CFLAGS="$(CPPFLAGS) -L." python setup_amounts.py install --prefix=$(DESTDIR) - CFLAGS="$(CPPFLAGS) -L." python setup_ledger.py install --prefix=$(DESTDIR) + CFLAGS="$(CPPFLAGS) -L." python setup.py install --prefix=$(DESTDIR) endif -endif @@ -328,8 +328,8 @@ Output customization:\n\ -Y, --yearly show yearly sub-totals\n\ -l, --limit EXPR calculate only transactions matching EXPR\n\ -d, --display EXPR display only transactions matching EXPR\n\ - -t, --value EXPR set the value expression for all report types\n\ - -T, --total EXPR set the total expression for all report types\n\ + -t, --value-expr EXPR set the value expression for all report types\n\ + -T, --total-expr EXPR set the total expression for all report types\n\ -j, --value-data print only raw value data (useful when scripting)\n\ -J, --total-data print only raw total data\n\n\ Commodity reporting:\n\ @@ -531,13 +531,13 @@ OPT_BEGIN(display, "d:") { config.display_predicate += ")"; } OPT_END(display); -OPT_BEGIN(value, "t:") { +OPT_BEGIN(value_expr, "t:") { config.value_expr = optarg; -} OPT_END(value); +} OPT_END(value_expr); -OPT_BEGIN(total, "T:") { +OPT_BEGIN(total_expr, "T:") { config.total_expr = optarg; -} OPT_END(total); +} OPT_END(total_expr); OPT_BEGIN(value_data, "j") { config.value_expr = "S" + config.value_expr; @@ -28,7 +28,7 @@ class FormatTransaction (TransactionHandler): def __call__ (self, xact): print self.formatter.format(xact) -handler = FormatTransaction("%D %-20P %N") +handler = FormatTransaction("%D %-20P %N %('foo'100)") handler = FilterTransactions (handler, "/Checking/") expr = parse_value_expr ("a*2") diff --git a/python.cc b/python.cc new file mode 100644 index 00000000..9699873c --- /dev/null +++ b/python.cc @@ -0,0 +1,78 @@ +#include "python.h" +#include "ledger.h" +#include "acconf.h" + +#include <boost/python.hpp> + +using namespace boost::python; + +void export_amount(); +void export_balance(); +void export_value(); +void export_journal(); +void export_parser(); +void export_textual(); +void export_binary(); +void export_qif(); +#ifdef READ_GNUCASH +void export_gnucash(); +#endif +void export_option(); +void export_walk(); +void export_format(); +void export_valexpr(); +void export_datetime(); + +namespace ledger { + +python_support * python_interpretor = NULL; + +static struct cleanup_python { + ~cleanup_python() { + if (python_interpretor) { + Py_Finalize(); + delete python_interpretor; + } + } +} _cleanup; + +void init_module() +{ + export_amount(); + export_balance(); + export_value(); + export_journal(); + export_parser(); + export_textual(); + export_binary(); + export_qif(); +#ifdef READ_GNUCASH + export_gnucash(); +#endif + export_option(); + export_walk(); + export_format(); + export_valexpr(); + export_datetime(); +} + +void init_python() +{ + assert(! python_interpretor); + + Py_Initialize(); + python_interpretor = new python_support; + +#if 1 + boost::python::detail::init_module("ledger", &init_module); +#else + object m_obj(python_interpretor->main_module); + scope current_module(m_obj); + + python_interpretor->main_namespace.attr("bar") = 10; + + handle_exception(init_module_main); +#endif +} + +} // namespace ledger diff --git a/python.h b/python.h new file mode 100644 index 00000000..7faefa0d --- /dev/null +++ b/python.h @@ -0,0 +1,55 @@ +#ifndef _PYTHON_H +#define _PYTHON_H + +#include <boost/python.hpp> + +using namespace boost::python; + +namespace ledger { + +struct python_support +{ + handle<> main_module; + dict main_namespace; + + python_support() + : main_module(borrowed(PyImport_AddModule("__main__"))), + main_namespace(handle<>(borrowed(PyModule_GetDict(main_module.get())))) + {} + ~python_support() { + } +}; + +extern python_support * python_interpretor; + +void init_python(); + +inline void python_eval(std::istream& in) +{ + if (! python_interpretor) + init_python(); + + std::string buffer; + buffer.reserve(4096); + while (! in.eof()) { + char buf[256]; + in.getline(buf, 255); + if (buf[0] == '!') + break; + buffer += buf; + buffer += "\n"; + } + + try { + handle<>(borrowed(PyRun_String(buffer.c_str(), Py_file_input, + python_interpretor->main_namespace.ptr(), + python_interpretor->main_namespace.ptr()))); + } + catch(const boost::python::error_already_set&) { + PyErr_Print(); + } +} + +} // namespace ledger + +#endif // _PYTHON_H @@ -1,3 +1,14 @@ +;; If Python support is enabled, then the following code may be +;; uncomment to demonstrate a rather useless example. + +; !python +; from ledger import * +; def foo(d, val): +; return val + d.xact.amount +; !end +; +; --value-expr 'foo'{$100} + 2004/05/01 Checking balance Assets:Checking $500.00 Equity:Opening Balances @@ -9,4 +20,3 @@ 2004/05/29 Restaurant Expenses:Food $50.00 Liabilities:MasterCard - diff --git a/setup.py b/setup.py new file mode 100755 index 00000000..5cb1e67e --- /dev/null +++ b/setup.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python + +from distutils.core import setup, Extension + +setup(name = "Amounts", + version = "2.0b", + description = "Amounts Library", + author = "John Wiegley", + author_email = "johnw@newartisans.com", + url = "http://www.newartisans.com/johnw/", + ext_modules = [ + Extension("amounts", ["pyamounts.cc"], + define_macros = [('PYTHON_MODULE', None)], + libraries = ["ledger_bpy", "boost_python", "gmp"])]) + +setup(name = "Ledger", + version = "2.0b", + description = "Ledger Accounting Tool", + author = "John Wiegley", + author_email = "johnw@newartisans.com", + url = "http://www.newartisans.com/johnw/", + ext_modules = [ + Extension("ledger", ["pyledger.cc"], + define_macros = [('PYTHON_MODULE', None)], + libraries = ["ledger_bpy", "boost_python", "gmp", + "pcre", "xmlparse", "xmltok"])]) diff --git a/setup_amounts.py b/setup_amounts.py deleted file mode 100755 index 663faa2e..00000000 --- a/setup_amounts.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python - -from distutils.core import setup, Extension - -setup(name = "Amounts", - version = "2.0b", - description = "Amounts Library", - author = "John Wiegley", - author_email = "johnw@newartisans.com", - url = "http://www.newartisans.com/johnw/", - ext_modules = [ - Extension("amounts", ["pyamounts.cc"], - define_macros = [('PYTHON_MODULE', None)], - libraries = ["amounts_bpy", "boost_python", "gmp"])]) diff --git a/setup_ledger.py b/setup_ledger.py deleted file mode 100755 index 71d8f56a..00000000 --- a/setup_ledger.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env python - -from distutils.core import setup, Extension - -setup(name = "Ledger", - version = "2.0b", - description = "Ledger Accounting Tool", - author = "John Wiegley", - author_email = "johnw@newartisans.com", - url = "http://www.newartisans.com/johnw/", - ext_modules = [ - Extension("ledger", ["pyledger.cc"], - define_macros = [('PYTHON_MODULE', None)], - libraries = ["amounts_bpy", "gmp", - "ledger_bpy", "boost_python", - "pcre", "xmlparse", "xmltok"])]) @@ -7,6 +7,9 @@ #include "option.h" #include "timing.h" #include "util.h" +#ifdef USE_BOOST_PYTHON +#include "python.h" +#endif #include <fstream> #include <sstream> @@ -534,9 +537,11 @@ unsigned int textual_parser_t::parse(std::istream& in, break; } - case '!': // directive - in >> line; - if (std::string(line) == "!include") { + case '!': { // directive + std::string word; + in.get(c); + in >> word; + if (word == "include") { in.getline(line, MAX_LINE); linenum++; @@ -545,7 +550,14 @@ unsigned int textual_parser_t::parse(std::istream& in, count += parse_journal_file(skip_ws(line), journal, account_stack.front()); } +#ifdef USE_BOOST_PYTHON + else if (word == "python") { + in.getline(line, MAX_LINE); + python_eval(in); + } +#endif break; + } default: { unsigned int first_line = linenum; @@ -5,6 +5,9 @@ #include "datetime.h" #include "debug.h" #include "util.h" +#ifdef USE_BOOST_PYTHON +#include "python.h" +#endif #include <pcre.h> @@ -277,6 +280,27 @@ void value_expr_t::compute(value_t& result, const details_t& details) const break; } + case F_INTERP_FUNC: { +#ifdef USE_BOOST_PYTHON + if (! python_interpretor) + init_python(); + + try { + object func = python_interpretor->main_namespace[constant_s]; + if (right) { + right->compute(result, details); + result = call<value_t>(func.ptr(), details, result); + } else { + result = call<value_t>(func.ptr(), details); + } + } + catch(const boost::python::error_already_set&) { + PyErr_Print(); + } +#endif + break; + } + case O_NOT: left->compute(result, details); result.negate(); @@ -489,13 +513,25 @@ value_expr_t * parse_value_term(std::istream& in) in.get(c); node.reset(new value_expr_t(short_account_mask ? - value_expr_t::F_SHORT_ACCOUNT_MASK : - (payee_mask ? value_expr_t::F_PAYEE_MASK : - value_expr_t::F_ACCOUNT_MASK))); + value_expr_t::F_SHORT_ACCOUNT_MASK : + (payee_mask ? value_expr_t::F_PAYEE_MASK : + value_expr_t::F_ACCOUNT_MASK))); node->mask = new mask_t(buf); break; } + case '\'': { + READ_INTO(in, buf, 255, c, c != '\''); + if (c != '\'') + throw value_expr_error("Missing closing '\''"); + + in.get(c); + node.reset(new value_expr_t(value_expr_t::F_INTERP_FUNC)); + node->constant_s = buf; + node->right = parse_value_expr(in); + break; + } + case '(': node.reset(parse_value_expr(in)); if (peek_next_nonws(in) == ')') @@ -781,6 +817,10 @@ void dump_value_expr(std::ostream& out, const value_expr_t * node) out << ")"; break; + case value_expr_t::F_INTERP_FUNC: + out << "F_INTERP(" << node->constant_s << ")"; + break; + case value_expr_t::O_NOT: out << "!"; dump_value_expr(out, node->left); @@ -874,6 +914,20 @@ value_expr_t * py_parse_value_expr(const std::string& str) { void export_valexpr() { + class_< details_t > ("Details", init<const entry_t&>()) + .def(init<const transaction_t&>()) + .def(init<const account_t&>()) + .add_property("entry", + make_getter(&details_t::entry, + return_value_policy<reference_existing_object>())) + .add_property("xact", + make_getter(&details_t::xact, + return_value_policy<reference_existing_object>())) + .add_property("account", + make_getter(&details_t::account, + return_value_policy<reference_existing_object>())) + ; + class_< value_expr_t > ("ValueExpr", init<value_expr_t::kind_t>()) .def("compute", py_compute<account_t>) .def("compute", py_compute<entry_t>) @@ -72,6 +72,7 @@ struct value_expr_t F_PAYEE_MASK, F_ACCOUNT_MASK, F_SHORT_ACCOUNT_MASK, + F_INTERP_FUNC, // Binary operators O_ADD, @@ -100,7 +101,8 @@ struct value_expr_t std::time_t constant_t; unsigned int constant_i; }; - amount_t constant_a; + std::string constant_s; + amount_t constant_a; mask_t * mask; value_expr_t(const kind_t _kind) @@ -618,6 +618,30 @@ void export_value() .def(init<unsigned int>()) .def(init<bool>()) + .def(self + self) + .def(self + other<balance_pair_t>()) + .def(self + other<balance_t>()) + .def(self + other<amount_t>()) + .def(self + int()) + + .def(self - self) + .def(self - other<balance_pair_t>()) + .def(self - other<balance_t>()) + .def(self - other<amount_t>()) + .def(self - int()) + + .def(self * self) + .def(self * other<balance_pair_t>()) + .def(self * other<balance_t>()) + .def(self * other<amount_t>()) + .def(self * int()) + + .def(self / self) + .def(self / other<balance_pair_t>()) + .def(self / other<balance_t>()) + .def(self / other<amount_t>()) + .def(self / int()) + .def(self += self) .def(self += other<balance_pair_t>()) .def(self += other<balance_t>()) |