Nicer wrapper for duktape

This commit is contained in:
Kovid Goyal 2015-06-18 17:12:43 +05:30
parent 655c9499fd
commit 6186a266e7
6 changed files with 98 additions and 43 deletions

View File

@ -170,7 +170,7 @@ def test_icu():
def test_dukpy():
print ('Testing dukpy')
from duktape.tests import test_build
from duktape import test_build
test_build()
print ('dukpy OK!')

View File

@ -7,9 +7,9 @@ __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
__all__ = ['dukpy', 'Context', 'undefined']
__all__ = ['dukpy', 'Context', 'undefined', 'JSError']
import errno, os
import errno, os, sys
from functools import partial
from calibre.constants import plugins
@ -28,15 +28,60 @@ def load_file(base_dirs, name):
raise
raise EnvironmentError('No module named: %s found in the base directories: %s' % (name, os.pathsep.join(base_dirs)))
def Context(base_dirs=()):
ans = Context_()
if not base_dirs:
base_dirs = (os.getcwdu(),)
ans.g.Duktape.load_file = partial(load_file, base_dirs or (os.getcwdu(),))
ans.eval('''
console = { log: function() { print(Array.prototype.join.call(arguments, ' ')); } };
Duktape.modSearch = function (id, require, exports, module) {
return Duktape.load_file(id);
}
''')
return ans
class JSError(Exception):
def __init__(self, e):
e = e.args[0]
Exception.__init__(self, e.toString())
self.name = e.name
self.js_message = e.message
self.fileName = e.fileName
self.lineNumber = e.lineNumber
self.stack = e.stack
class Context(object):
def __init__(self, base_dirs=()):
self._ctx = Context_()
self.g = self._ctx.g
self.g.Duktape.load_file = partial(load_file, base_dirs or (os.getcwdu(),))
self.eval('''
console = { log: function() { print(Array.prototype.join.call(arguments, ' ')); } };
Duktape.modSearch = function (id, require, exports, module) {
return Duktape.load_file(id);
}
''')
def eval(self, code='', noreturn=False):
try:
self._ctx.eval(code, noreturn)
except dukpy.JSError as e:
raise JSError(e)
def eval_file(self, path, noreturn=False):
try:
self._ctx.eval_file(path, noreturn)
except dukpy.JSError as e:
raise JSError(e)
def test_build():
import unittest
def load_tests(loader, suite, pattern):
from duktape import tests
for x in vars(tests).itervalues():
if isinstance(x, type) and issubclass(x, unittest.TestCase):
tests = loader.loadTestsFromTestCase(x)
suite.addTests(tests)
return suite
class TestRunner(unittest.main):
def createTests(self):
tl = unittest.TestLoader()
suite = unittest.TestSuite()
self.test = load_tests(tl, suite, None)
result = TestRunner(verbosity=0, buffer=True, catchbreak=True, failfast=True, argv=sys.argv[:1], exit=False).result
if not result.wasSuccessful():
raise SystemExit(1)

View File

@ -98,8 +98,12 @@ static PyObject *DukContext_eval(DukContext *self, PyObject *args, PyObject *kw)
if (temp && PyObject_IsTrue(temp)) noresult = 1;
if (duk_peval_string(self->ctx, code) != 0) {
PyErr_Format(PyExc_SyntaxError, "%s",
duk_safe_to_string(self->ctx, -1));
temp = duk_to_python(self->ctx, -1);
if (temp) {
PyErr_SetObject(JSError, temp);
Py_DECREF(temp);
}
duk_pop(self->ctx);
return NULL;
}
@ -128,8 +132,12 @@ static PyObject *DukContext_eval_file(DukContext *self, PyObject *args, PyObject
if (temp && PyObject_IsTrue(temp)) noresult = 1;
if (duk_peval_file(self->ctx, path) != 0) {
PyErr_Format(PyExc_SyntaxError,
"%s:%s", path, duk_safe_to_string(self->ctx, -1));
temp = duk_to_python(self->ctx, -1);
if (temp) {
PyErr_SetObject(JSError, temp);
Py_DECREF(temp);
}
duk_pop(self->ctx);
return NULL;
}

View File

@ -13,6 +13,7 @@ typedef struct DukEnum_ DukEnum;
PyObject DukUndefined;
#define Duk_undefined (&DukUndefined)
extern PyObject *JSError;
/* context.c */

View File

@ -1,5 +1,7 @@
#include "dukpy.h"
PyObject *JSError = NULL;
/* ARGSUSED */
static PyObject *
undefined_repr(PyObject *op)
@ -131,6 +133,9 @@ initdukpy(void)
Py_INCREF(Duk_undefined);
PyModule_AddObject(mod, "undefined", (PyObject *)Duk_undefined);
JSError = PyErr_NewException("dukpy.JSError", NULL, NULL);
if (JSError) PyModule_AddObject(mod, "JSError", JSError);
}
#if PY_MAJOR_VERSION >= 3

View File

@ -1,7 +1,8 @@
import os
import sys
import unittest
from duktape import Context, undefined
from duktape import dukpy
undefined, JSError, Context = dukpy.undefined, dukpy.JSError, dukpy.Context
class ContextTests(unittest.TestCase):
def setUp(self):
@ -129,6 +130,24 @@ class EvalTests(unittest.TestCase):
def test_eval_kwargs(self):
self.assertEqual(self.ctx.eval(code="1+1"), 2)
def test_eval_errors(self):
try:
self.ctx.eval('1+/1')
self.assert_('No error raised for malformed js')
except JSError as e:
e = e.args[0]
self.assertEqual('SyntaxError', e.name)
self.assertEqual(1, e.lineNumber)
self.assertIn('line 1', e.toString())
try:
self.ctx.eval('\na()')
self.assert_('No error raised for malformed js')
except JSError as e:
e = e.args[0]
self.assertEqual('ReferenceError', e.name)
self.assertEqual(2, e.lineNumber)
def test_eval_noreturn(self):
self.assertIsNone(self.ctx.eval("1+1", noreturn=True))
@ -148,27 +167,4 @@ class EvalTests(unittest.TestCase):
def test_eval_file_noreturn(self):
self.assertIsNone(self.ctx.eval_file(self.testfile, noreturn=True))
def load_tests(loader, suite, pattern):
for x in globals().itervalues():
if isinstance(x, type) and issubclass(x, unittest.TestCase):
tests = loader.loadTestsFromTestCase(x)
suite.addTests(tests)
return suite
class TestRunner(unittest.main):
def createTests(self):
tl = unittest.TestLoader()
suite = unittest.TestSuite()
self.test = load_tests(tl, suite, None)
def run(verbosity=4):
TestRunner(verbosity=verbosity, exit=False)
def test_build():
result = TestRunner(verbosity=0, buffer=True, catchbreak=True, failfast=True, argv=sys.argv[:1], exit=False).result
if not result.wasSuccessful():
raise SystemExit(1)
if __name__ == '__main__':
run()