summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2012-03-01 17:32:51 -0600
committerJohn Wiegley <johnw@newartisans.com>2012-03-01 17:32:51 -0600
commitf6c087cfe48e6410db61a9367ce7c718a490af77 (patch)
treea2130aa5b47c3dd17ab698db9374343915193579
parentff89cb9c4de8240d7a7f79406755a86d8d2d5f18 (diff)
downloadfork-ledger-f6c087cfe48e6410db61a9367ce7c718a490af77.tar.gz
fork-ledger-f6c087cfe48e6410db61a9367ce7c718a490af77.tar.bz2
fork-ledger-f6c087cfe48e6410db61a9367ce7c718a490af77.zip
Added a new 'python' directive
-rw-r--r--src/py_value.cc2
-rw-r--r--src/pyinterp.cc77
-rw-r--r--src/pyinterp.h5
-rw-r--r--src/textual.cc62
-rwxr-xr-xtest/LedgerHarness.py4
-rwxr-xr-xtest/RegressTests.py4
-rw-r--r--test/baseline/dir-python_py.test26
-rw-r--r--test/baseline/feat-import_py.test23
-rw-r--r--test/baseline/featimport.py4
-rw-r--r--tools/Makefile.am16
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