Skip to content

Commit

Permalink
Add RequirableContext, to allow Python/JS requires
Browse files Browse the repository at this point in the history
  • Loading branch information
lukegb committed Jan 15, 2016
1 parent d33e96c commit d24b1bd
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 2 deletions.
4 changes: 2 additions & 2 deletions dukpy/__init__.py
@@ -1,5 +1,5 @@
from .evaljs import evaljs, Context
from .evaljs import evaljs, Context, RequirableContext
from ._dukpy import JSRuntimeError
from .coffee import coffee_compile
from .babel import babel_compile
from .tsc import typescript_compile
from .tsc import typescript_compile
57 changes: 57 additions & 0 deletions dukpy/evaljs.py
@@ -1,5 +1,8 @@
from . import _dukpy

import os.path
import importlib

try: # pragma: no cover
unicode
string_types = (str, unicode)
Expand Down Expand Up @@ -29,6 +32,60 @@ def evaljs(self, code, **kwargs):
return _dukpy.ctx_eval_string(self._ctx, jscode, kwargs)


class RequirableContext(Context):
def __init__(self, search_paths, enable_python=False):
super(RequirableContext, self).__init__()
self.search_paths = search_paths
self.evaljs("Duktape.modSearch = dukpy.modSearch;", modSearch=self.require)
self.enable_python = True

def require(self, id_, require, exports, module):
# does the module ID begin with 'python/'
if self.enable_python and id_.startswith('python/'):
pyid = id_[len('python/'):].replace('/', '.')
ret = self.require_python(pyid, require, exports, module)
if ret:
return ret

for search_path in self.search_paths:
# we'll assume here that search_path is relative to cwd
# or is an absolute path(!)
try:
with open(os.path.join(search_path, id_ + '.js')) as f:
return f.read()
except IOError:
# assume this was probably No such file
continue

raise Exception("Unable to load {}".format(id_))

def require_python(self, pyid, require, exports, module):
try:
pymod = importlib.import_module(pyid)
except ImportError:
return None

# hack to work around the fact that
# JS objects in Python are copies, and don't reflect the state of the object
# "in JS land"
all_things = getattr(pymod, '__all__', [x for x in dir(pymod) if x and x[0] != '_'])
self.evaljs("""
Duktape._pymodules = Duktape._pymodules || {};
Duktape._pymodulesins = Duktape._pymodulesins || {};
Duktape._pylastmodule = dukpy.module_name;
Duktape._pymodules[dukpy.module_name] = dukpy.module;
Duktape._pymodulesins[dukpy.module_name] = dukpy.module_things;
""", module_name=pyid, module=pymod, module_things=all_things)
return """
var a = Duktape._pymodulesins[Duktape._pylastmodule];
for (var i = 0; i < a.length; i++) {
var k = a[i];
exports[k] = Duktape._pymodules[Duktape._pylastmodule][k];
}
"""


class JSFunction(object):
def __init__(self, func_ptr):
self._func = func_ptr
Expand Down
3 changes: 3 additions & 0 deletions tests/testjs/testjs.js
@@ -0,0 +1,3 @@
exports.call = function() {
return 'Hello from JS!';
};
18 changes: 18 additions & 0 deletions tests/tests_evaljs.py
@@ -1,4 +1,5 @@
import json
import os.path
import dukpy
from diffreport import report_diff

Expand Down Expand Up @@ -216,6 +217,23 @@ def call_first_argument_python(a, *args):
assert ret == 18


class TestRequirableContext(object):
test_js_dir = os.path.join(os.path.dirname(__file__), 'testjs')

def test_js_require(self):
c = dukpy.RequirableContext([self.test_js_dir])
assert c.evaljs("require('testjs').call()") == "Hello from JS!"

def test_python_require(self):
import sys, imp
testpy = imp.new_module('testpy')
testpy.call = lambda: 'Hello from Python!'
testpy.__all__ = ['call']
sys.modules['testpy'] = testpy
c = dukpy.RequirableContext([], enable_python=True)
assert c.evaljs("require('python/testpy').call()") == "Hello from Python!"


class TestRegressions(object):
def test_pyargs_segfault(self):
for _ in range(1, 10):
Expand Down

0 comments on commit d24b1bd

Please sign in to comment.