Experimental support for 'require' in js code

This commit is contained in:
Alessandro Molina 2016-04-09 21:49:58 +02:00
parent 3856a486a6
commit 5b06de69b8
4 changed files with 130 additions and 12 deletions

View File

@ -1,4 +1,8 @@
from __future__ import print_function
import json import json
import os
from dukpy.module_loader import JSModuleLoader
from . import _dukpy from . import _dukpy
try: try:
@ -16,19 +20,17 @@ except NameError: # pragma: no cover
class JSInterpreter(object): class JSInterpreter(object):
"""JavaScript Interpreter""" """JavaScript Interpreter"""
def __init__(self): def __init__(self):
self._loader = JSModuleLoader()
self._ctx = _dukpy.create_context() self._ctx = _dukpy.create_context()
self._funcs = {} self._funcs = {}
def _adapt_code(self, code): self._init_process()
def _read_files(f): self._init_console()
if hasattr(f, 'read'): self._init_require()
return f.read()
else: @property
return f def loader(self):
code = _read_files(code) return self._loader
if not isinstance(code, string_types) and hasattr(code, '__iter__'):
code = ';\n'.join(map(_read_files, code))
return code
def evaljs(self, code, **kwargs): def evaljs(self, code, **kwargs):
"""Runs JavaScript code in the context of the interpreter. """Runs JavaScript code in the context of the interpreter.
@ -73,6 +75,42 @@ class JSInterpreter(object):
if ret is not None: if ret is not None:
return json.dumps(ret).encode('utf-8') return json.dumps(ret).encode('utf-8')
def _init_process(self):
self.evaljs("process = {}; process.env = dukpy.environ", environ=dict(os.environ))
def _init_console(self):
self.export_function('dukpy.print', print)
self.evaljs("""
;console = {
log: function() {
call_python('dukpy.print', Array.prototype.join.call(arguments, ' '));
}
};
""")
def _init_require(self):
self.export_function('dukpy.lookup_module', self._loader.load)
self.evaljs("""
;Duktape.modSearch = function (id, require, exports, module) {
var m = call_python('dukpy.lookup_module', id);
if (!m)
throw new Error('cannot find module: ' + id);
return m;
};
""")
def _adapt_code(self, code):
def _read_files(f):
if hasattr(f, 'read'):
return f.read()
else:
return f
code = _read_files(code)
if not isinstance(code, string_types) and hasattr(code, '__iter__'):
code = ';\n'.join(map(_read_files, code))
return code
def evaljs(code, **kwargs): def evaljs(code, **kwargs):
"""Evaluates the given ``code`` as JavaScript and returns the result""" """Evaluates the given ``code`` as JavaScript and returns the result"""

65
dukpy/module_loader.py Normal file
View File

@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
import json
import os
class JSModuleLoader(object):
"""Manages finding and loading JS modules in CommonJS format.
This allows to import a module from JSInterpreter using the
`require('modulename')` command.
To register additional paths where to look for modules use
the `JSModuleLoader.register_path` method.
"""
def __init__(self):
self._paths = []
self.register_path(os.path.join(os.path.dirname(__file__), 'jsmodules'))
self.register_path(os.getcwd())
def register_path(self, path):
"""Registers a directory where to look for modules.
By default only modules relative to current path are found.
"""
self._paths.insert(0, os.path.abspath(path))
def lookup(self, module_name):
"""Searches for a file providing given module."""
for search_path in self._paths:
module_path = os.path.join(search_path, module_name)
module_file = self._lookup(module_path)
if module_file:
return module_file
def load(self, module_name):
"""Returns source code of the given module."""
path = self.lookup(module_name)
if path:
with open(path, 'rb') as f:
return f.read()
def _lookup(self, module_path):
# Module is a plain .js file
for path in (module_path, os.path.extsep.join((module_path, 'js'))):
if os.path.exists(path) and os.path.isfile(path):
return path
# Module is a package
package = os.path.join(module_path, os.path.extsep.join(('package', 'json')))
try:
with open(package) as f:
package = json.load(f)
except IOError:
pass
else:
package_main = package.get('main')
if package_main:
path = self._lookup(os.path.join(module_path, package_main))
if path:
return path
# Module is directory with index.js inside
indexjs = os.path.join(module_path, os.path.extsep.join(('index', 'js')))
if os.path.exists(indexjs):
return indexjs

View File

@ -8,7 +8,9 @@ except IOError:
README = '' README = ''
duktape = Extension('dukpy._dukpy', duktape = Extension('dukpy._dukpy',
define_macros=[('DUK_OPT_DEEP_C_STACK', '1')], define_macros=[('DUK_OPT_DEEP_C_STACK', '1'),
('DUK_OPT_NONSTD_REGEXP_DOLLAR_ESCAPE', '1'),
('DUK_OPT_OCTAL_SUPPORT', '1')],
sources=[os.path.join('src', 'duktape', 'duktape.c'), sources=[os.path.join('src', 'duktape', 'duktape.c'),
os.path.join('src','_support.c'), os.path.join('src','_support.c'),
os.path.join('src','pyduktape.c')], os.path.join('src','pyduktape.c')],

View File

@ -1,4 +1,5 @@
import dukpy import dukpy
from diffreport import report_diff
class TestJSInterpreter(object): class TestJSInterpreter(object):
@ -16,4 +17,16 @@ class TestJSInterpreter(object):
interpreter = dukpy.JSInterpreter() interpreter = dukpy.JSInterpreter()
interpreter.export_function('say_hello', _say_hello) interpreter.export_function('say_hello', _say_hello)
res = interpreter.evaljs("call_python('say_hello', 3, 'world')") res = interpreter.evaljs("call_python('say_hello', 3, 'world')")
assert res == 'Hello world world world', res assert res == 'Hello world world world', res
def test_module_loader(self):
interpreter = dukpy.JSInterpreter()
res = interpreter.evaljs('''
babel = require('babel-6.4.4.min');
babel.transform(dukpy.es6code, {presets: ["es2015"]}).code;
''', es6code='let i=5;')
expected = '''"use strict";
var i = 5;'''
assert res == expected, report_diff(expected, res)