diff options
author | John Wiegley <johnw@newartisans.com> | 2012-03-01 17:32:51 -0600 |
---|---|---|
committer | John Wiegley <johnw@newartisans.com> | 2012-03-01 17:32:51 -0600 |
commit | f6c087cfe48e6410db61a9367ce7c718a490af77 (patch) | |
tree | a2130aa5b47c3dd17ab698db9374343915193579 | |
parent | ff89cb9c4de8240d7a7f79406755a86d8d2d5f18 (diff) | |
download | fork-ledger-f6c087cfe48e6410db61a9367ce7c718a490af77.tar.gz fork-ledger-f6c087cfe48e6410db61a9367ce7c718a490af77.tar.bz2 fork-ledger-f6c087cfe48e6410db61a9367ce7c718a490af77.zip |
Added a new 'python' directive
-rw-r--r-- | src/py_value.cc | 2 | ||||
-rw-r--r-- | src/pyinterp.cc | 77 | ||||
-rw-r--r-- | src/pyinterp.h | 5 | ||||
-rw-r--r-- | src/textual.cc | 62 | ||||
-rwxr-xr-x | test/LedgerHarness.py | 4 | ||||
-rwxr-xr-x | test/RegressTests.py | 4 | ||||
-rw-r--r-- | test/baseline/dir-python_py.test | 26 | ||||
-rw-r--r-- | test/baseline/feat-import_py.test | 23 | ||||
-rw-r--r-- | test/baseline/featimport.py | 4 | ||||
-rw-r--r-- | tools/Makefile.am | 16 |
10 files changed, 190 insertions, 33 deletions
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<balance_t>()) .def(init<mask_t>()) .def(init<std::string>()) - // 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<value_t::sequence_t>()) .def(init<value_t>()) 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 <system.hh> #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<const post_t *>(scope)) - lst.append(ptr(post)); - else if (const xact_t * xact = dynamic_cast<const xact_t *>(scope)) - lst.append(ptr(xact)); - else if (const account_t * account = - dynamic_cast<const account_t *>(scope)) - lst.append(ptr(account)); - else if (const period_xact_t * period_xact = - dynamic_cast<const period_xact_t *>(scope)) - lst.append(ptr(period_xact)); - else if (const auto_xact_t * auto_xact = - dynamic_cast<const auto_xact_t *>(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<const post_t *>(scope)) + return object(ptr(post)); + else if (const xact_t * xact = dynamic_cast<const xact_t *>(scope)) + return object(ptr(xact)); + else if (const account_t * account = + dynamic_cast<const account_t *>(scope)) + return object(ptr(account)); + else if (const period_xact_t * period_xact = + dynamic_cast<const period_xact_t *>(scope)) + return object(ptr(period_xact)); + else if (const auto_xact_t * auto_xact = + dynamic_cast<const auto_xact_t *>(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 |