├── MANIFEST.in ├── .gitignore ├── generate_readme_rst.sh ├── install_development.sh ├── execjs ├── runtime_names.py ├── _misc.py ├── _exceptions.py ├── _abstract_runtime.py ├── _abstract_runtime_context.py ├── __init__.py ├── __main__.py ├── _runtimes.py ├── _pyv8runtime.py ├── _json2.py ├── _runner_sources.py └── _external_runtime.py ├── .travis.yml ├── LICENSE ├── setup.py ├── README.md ├── README.rst └── test_execjs.py /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.md 3 | include test_execjs.py -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #ignore python cache 2 | *.pyc 3 | __pycache__ 4 | PyExecJS.egg-info/ 5 | dist/ 6 | build/ -------------------------------------------------------------------------------- /generate_readme_rst.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cat README.md \ 3 | | grep -v -F '![Build Status]' \ 4 | | pandoc -f markdown -t rst - >| README.rst 5 | -------------------------------------------------------------------------------- /install_development.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | python -c "import sys; sys.version_info >= (2, 7) or sys.exit(1)" 3 | if [ $? -ne 0 ]; then 4 | pip install unittest2 5 | fi 6 | -------------------------------------------------------------------------------- /execjs/runtime_names.py: -------------------------------------------------------------------------------- 1 | PyV8 = "PyV8" 2 | Node = "Node" 3 | JavaScriptCore = "JavaScriptCore" 4 | SpiderMonkey = "SpiderMonkey" 5 | JScript = "JScript" 6 | PhantomJS = "PhantomJS" 7 | SlimerJS = "SlimerJS" 8 | Nashorn = "Nashorn" 9 | -------------------------------------------------------------------------------- /execjs/_misc.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | def encode_unicode_codepoints(str): 5 | r""" 6 | >>> encode_unicode_codepoints("a") == 'a' 7 | True 8 | >>> ascii = ''.join(chr(i) for i in range(0x80)) 9 | >>> encode_unicode_codepoints(ascii) == ascii 10 | True 11 | >>> encode_unicode_codepoints('\u4e16\u754c') == '\\u4e16\\u754c' 12 | True 13 | """ 14 | codepoint_format = '\\u{0:04x}'.format 15 | 16 | def codepoint(m): 17 | return codepoint_format(ord(m.group(0))) 18 | 19 | return re.sub('[^\x00-\x7f]', codepoint, str) 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | language: python 3 | 4 | addons: 5 | firefox: "latest" 6 | 7 | python: 8 | - "2.7" 9 | - "3.3" 10 | - "3.4" 11 | - "3.5" 12 | - "3.6" 13 | 14 | env: 15 | global: 16 | - "JDK=oracle8" 17 | 18 | jdk: 19 | - oraclejdk8 20 | 21 | install: 22 | - "sudo apt-get install -y oracle-java8-installer phantomjs libmozjs-24-bin" 23 | - "sudo ln -s /usr/bin/js24 /usr/bin/js" 24 | - "./install_development.sh" 25 | - "python setup.py install" 26 | 27 | script: 28 | - "jjs -v < /dev/null" 29 | - "js --help" 30 | - "node --version && node --help" 31 | - "python -m execjs --print-available-runtimes" 32 | - "python setup.py test" 33 | -------------------------------------------------------------------------------- /execjs/_exceptions.py: -------------------------------------------------------------------------------- 1 | # Abstract base error classes 2 | class Error(Exception): 3 | pass 4 | 5 | # Abstract class that represents errors of runtime engine. 6 | # ex. Specified runtime engine is not installed, runtime engine aborted (by its bugs). 7 | # By the way "RuntimeError" is bad name because it is confusing with the standard exception. 8 | class RuntimeError(Error): 9 | pass 10 | 11 | # Concrete runtime error classes 12 | class RuntimeUnavailableError(RuntimeError): pass 13 | 14 | class ProcessExitedWithNonZeroStatus(RuntimeError): 15 | def __init__(self, status, stdout, stderr): 16 | RuntimeError.__init__(self, status, stdout, stderr) 17 | self.status = status 18 | self.stdout = stdout 19 | self.stderr = stderr 20 | 21 | # Errors due to JS script. 22 | # ex. Script has syntax error, executed and raised exception. 23 | class ProgramError(Error): 24 | pass 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Omoto Kenji 2 | Copyright (c) 2011 Sam Stephenson 3 | Copyright (c) 2011 Josh Peek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: ascii -*- 3 | from __future__ import division, with_statement 4 | from setuptools import setup, find_packages 5 | import sys 6 | import io 7 | 8 | version = '1.5.1' 9 | author = "Omoto Kenji" 10 | license = "MIT License" 11 | author_email = 'doloopwhile@gmail.com' 12 | 13 | 14 | with io.open('README.rst', encoding='ascii') as fp: 15 | long_description = fp.read() 16 | 17 | setup( 18 | packages=find_packages(), 19 | include_package_data=True, 20 | name='PyExecJS', 21 | version=version, 22 | description='Run JavaScript code from Python', 23 | long_description=long_description, 24 | author=author, 25 | author_email=author_email, 26 | url='https://github.com/doloopwhile/PyExecJS', 27 | license=license, 28 | classifiers=[ 29 | 'Development Status :: 5 - Production/Stable', 30 | 'Intended Audience :: Developers', 31 | 'Natural Language :: English', 32 | 'License :: OSI Approved :: MIT License', 33 | 'Programming Language :: Python', 34 | "Programming Language :: Python :: 2", 35 | "Programming Language :: Python :: 2.7", 36 | "Programming Language :: Python :: 3", 37 | 'Programming Language :: Python :: 3.3', 38 | 'Programming Language :: Python :: 3.4', 39 | 'Programming Language :: Python :: 3.5', 40 | 'Programming Language :: JavaScript', 41 | ], 42 | install_requires=["six >= 1.10.0"], 43 | test_suite="test_execjs", 44 | ) 45 | -------------------------------------------------------------------------------- /execjs/_abstract_runtime.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod 2 | import six 3 | import execjs._exceptions as exceptions 4 | 5 | 6 | @six.add_metaclass(ABCMeta) 7 | class AbstractRuntime(object): 8 | ''' 9 | Abstract base class for runtime class. 10 | ''' 11 | def exec_(self, source, cwd=None): 12 | '''Execute source by JavaScript runtime and return all output to stdout as a string. 13 | 14 | source -- JavaScript code to execute. 15 | cwd -- Directory where call JavaScript runtime. It may be ignored in some derived class. 16 | ''' 17 | return self.compile('', cwd=cwd).exec_(source) 18 | 19 | def eval(self, source, cwd=None): 20 | '''Evaluate source in JavaScript runtime. 21 | 22 | source -- JavaScript code to evaluate. 23 | cwd -- Directory where call JavaScript runtime. It may be ignored in some derived class. 24 | ''' 25 | return self.compile('', cwd=cwd).eval(source) 26 | 27 | def compile(self, source, cwd=None): 28 | '''Bulk source as a context object. The source can be used to execute another code. 29 | 30 | source -- JavaScript code to bulk. 31 | cwd -- Directory where call JavaScript runtime. It may be ignored in some derived class. 32 | ''' 33 | if not self.is_available(): 34 | raise exceptions.RuntimeUnavailableError 35 | return self._compile(source, cwd=cwd) 36 | 37 | @abstractmethod 38 | def is_available(self): 39 | raise NotImplementedError 40 | 41 | @abstractmethod 42 | def _compile(self, source, cwd=None): 43 | raise NotImplementedError 44 | -------------------------------------------------------------------------------- /execjs/_abstract_runtime_context.py: -------------------------------------------------------------------------------- 1 | import execjs 2 | from abc import ABCMeta, abstractmethod 3 | import six 4 | 5 | 6 | @six.add_metaclass(ABCMeta) 7 | class AbstractRuntimeContext(object): 8 | ''' 9 | Abstract base class for runtime context class. 10 | ''' 11 | def exec_(self, source): 12 | '''Execute source by JavaScript runtime and return all output to stdout as a string. 13 | 14 | source -- JavaScript code to execute. 15 | ''' 16 | if not self.is_available(): 17 | raise execjs.RuntimeUnavailableError 18 | return self._exec_(source) 19 | 20 | def eval(self, source): 21 | '''Evaluate source in JavaScript runtime. 22 | 23 | source -- JavaScript code to evaluate. 24 | ''' 25 | if not self.is_available(): 26 | raise execjs.RuntimeUnavailableError 27 | return self._eval(source) 28 | 29 | def call(self, name, *args): 30 | '''Call a JavaScript function in context. 31 | 32 | name -- Name of funtion object to call 33 | args -- Arguments for the funtion object 34 | ''' 35 | if not self.is_available(): 36 | raise execjs.RuntimeUnavailableError 37 | return self._call(name, *args) 38 | 39 | @abstractmethod 40 | def is_available(self): 41 | raise NotImplementedError 42 | 43 | @abstractmethod 44 | def _exec_(self, source): 45 | raise NotImplementedError 46 | 47 | @abstractmethod 48 | def _eval(self, source): 49 | raise NotImplementedError 50 | 51 | @abstractmethod 52 | def _call(self, name, *args): 53 | raise NotImplementedError 54 | -------------------------------------------------------------------------------- /execjs/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: ascii -*- 3 | '''r 4 | Run JavaScript code from Python. 5 | 6 | PyExecJS is a porting of ExecJS from Ruby. 7 | PyExecJS automatically picks the best runtime available to evaluate your JavaScript program, 8 | then returns the result to you as a Python object. 9 | 10 | A short example: 11 | 12 | >>> import execjs 13 | >>> execjs.eval("'red yellow blue'.split(' ')") 14 | ['red', 'yellow', 'blue'] 15 | >>> ctx = execjs.compile(""" 16 | ... function add(x, y) { 17 | ... return x + y; 18 | ... } 19 | ... """) 20 | >>> ctx.call("add", 1, 2) 21 | 3 22 | ''' 23 | from __future__ import unicode_literals, division, with_statement 24 | 25 | from execjs._exceptions import ( 26 | Error, 27 | RuntimeError, 28 | ProgramError, 29 | RuntimeUnavailableError, 30 | ) 31 | 32 | import execjs._runtimes 33 | from execjs._external_runtime import ExternalRuntime 34 | from execjs._abstract_runtime import AbstractRuntime 35 | 36 | 37 | __all__ = """ 38 | get register runtimes get_from_environment exec_ eval compile 39 | ExternalRuntime 40 | Error RuntimeError ProgramError RuntimeUnavailableError 41 | """.split() 42 | 43 | 44 | register = execjs._runtimes.register 45 | get = execjs._runtimes.get 46 | runtimes = execjs._runtimes.runtimes 47 | get_from_environment = execjs._runtimes.get_from_environment 48 | 49 | 50 | def eval(source, cwd=None): 51 | return get().eval(source, cwd) 52 | eval.__doc__ = AbstractRuntime.eval.__doc__ 53 | 54 | 55 | def exec_(source, cwd=None): 56 | return get().exec_(source, cwd) 57 | exec_.__doc__ = AbstractRuntime.exec_.__doc__ 58 | 59 | 60 | def compile(source, cwd=None): 61 | return get().compile(source, cwd) 62 | compile.__doc__ = AbstractRuntime.compile.__doc__ 63 | -------------------------------------------------------------------------------- /execjs/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: ascii -*- 3 | from __future__ import unicode_literals 4 | import sys 5 | import io 6 | from argparse import ArgumentParser, Action, SUPPRESS 7 | 8 | import execjs 9 | 10 | 11 | class PrintRuntimes(Action): 12 | def __init__(self, option_strings, dest=SUPPRESS, default=SUPPRESS, help=None): 13 | super(PrintRuntimes, self).__init__( 14 | option_strings=option_strings, 15 | dest=dest, 16 | default=default, 17 | nargs=0, 18 | help=help, 19 | ) 20 | 21 | def __call__(self, parser, namespace, values, option_string=None): 22 | message = "".join(name + "\n" for name, runtime in execjs.runtimes().items() if runtime.is_available()) 23 | parser.exit(message=message) 24 | 25 | 26 | def main(): 27 | parser = ArgumentParser() 28 | parser.add_argument('--print-available-runtimes', action=PrintRuntimes) 29 | parser.add_argument('-r', '--runtime', action='store', dest='runtime') 30 | parser.add_argument('-e', '--eval', action='store', dest='expr') 31 | parser.add_argument("--encoding", action="store", dest="files_encoding", default="utf8") 32 | parser.add_argument(nargs="*", action='store', dest='files') 33 | 34 | opts = parser.parse_args() 35 | 36 | runtime = execjs.get(opts.runtime) 37 | 38 | codes = [] 39 | for f in opts.files: 40 | with io.open(f, encoding=opts.files_encoding) as fp: 41 | codes.append(fp.read()) 42 | 43 | context = runtime.compile("\n".join(codes)) 44 | if opts.expr: 45 | if isinstance(opts.expr, bytes): 46 | expr = opts.expr.decode() 47 | else: 48 | expr = opts.expr 49 | sys.stdout.write(repr(context.eval(expr)) + "\n") 50 | else: 51 | ret = context.eval(sys.stdin.read()) 52 | sys.stdout.write(repr(ret) + "\n") 53 | 54 | if "__main__" == __name__: 55 | main() 56 | -------------------------------------------------------------------------------- /execjs/_runtimes.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | from collections import OrderedDict 3 | 4 | import execjs.runtime_names as runtime_names 5 | import execjs._external_runtime as external_runtime 6 | import execjs._pyv8runtime as pyv8runtime 7 | import execjs._exceptions as exceptions 8 | 9 | 10 | def register(name, runtime): 11 | '''Register a JavaScript runtime.''' 12 | _runtimes.append((name, runtime)) 13 | 14 | 15 | def get(name=None): 16 | """ 17 | Return a appropriate JavaScript runtime. 18 | If name is specified, return the runtime. 19 | """ 20 | if name is None: 21 | return get_from_environment() or _find_available_runtime() 22 | return _find_runtime_by_name(name) 23 | 24 | 25 | def runtimes(): 26 | """return a dictionary of all supported JavaScript runtimes.""" 27 | return OrderedDict(_runtimes) 28 | 29 | 30 | def get_from_environment(): 31 | ''' 32 | Return the JavaScript runtime that is specified in EXECJS_RUNTIME environment variable. 33 | If EXECJS_RUNTIME environment variable is empty or invalid, return None. 34 | ''' 35 | name = os.environ.get("EXECJS_RUNTIME", "") 36 | if not name: 37 | return None 38 | 39 | try: 40 | return _find_runtime_by_name(name) 41 | except exceptions.RuntimeUnavailableError: 42 | return None 43 | 44 | 45 | def _find_available_runtime(): 46 | for _, runtime in _runtimes: 47 | if runtime.is_available(): 48 | return runtime 49 | raise exceptions.RuntimeUnavailableError("Could not find an available JavaScript runtime.") 50 | 51 | 52 | def _find_runtime_by_name(name): 53 | for runtime_name, runtime in _runtimes: 54 | if runtime_name.lower() == name.lower(): 55 | break 56 | else: 57 | raise exceptions.RuntimeUnavailableError("{name} runtime is not defined".format(name=name)) 58 | 59 | if not runtime.is_available(): 60 | raise exceptions.RuntimeUnavailableError( 61 | "{name} runtime is not available on this system".format(name=runtime.name)) 62 | return runtime 63 | 64 | 65 | _runtimes = [] 66 | 67 | register(runtime_names.PyV8, pyv8runtime.PyV8Runtime()) 68 | register(runtime_names.Node, external_runtime.node()) 69 | register(runtime_names.JavaScriptCore, external_runtime.jsc()) 70 | register(runtime_names.SpiderMonkey, external_runtime.spidermonkey()) 71 | register(runtime_names.JScript, external_runtime.jscript()) 72 | register(runtime_names.PhantomJS, external_runtime.phantomjs()) 73 | register(runtime_names.SlimerJS, external_runtime.slimerjs()) 74 | register(runtime_names.Nashorn, external_runtime.nashorn()) 75 | -------------------------------------------------------------------------------- /execjs/_pyv8runtime.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import execjs._exceptions as exceptions 4 | from execjs._abstract_runtime import AbstractRuntime 5 | from execjs._abstract_runtime_context import AbstractRuntimeContext 6 | from execjs._misc import encode_unicode_codepoints 7 | 8 | try: 9 | import PyV8 10 | except ImportError: 11 | _pyv8_available = False 12 | else: 13 | _pyv8_available = True 14 | 15 | 16 | class PyV8Runtime(AbstractRuntime): 17 | '''Runtime to execute codes with PyV8.''' 18 | def __init__(self): 19 | pass 20 | 21 | @property 22 | def name(self): 23 | return "PyV8" 24 | 25 | def _compile(self, source, cwd=None): 26 | return self.Context(source) 27 | 28 | def is_available(self): 29 | return _pyv8_available 30 | 31 | class Context(AbstractRuntimeContext): 32 | def __init__(self, source=""): 33 | self._source = source 34 | 35 | def is_available(self): 36 | return _pyv8_available 37 | 38 | def _exec_(self, source): 39 | source = '''\ 40 | (function() {{ 41 | {0}; 42 | {1}; 43 | }})()'''.format( 44 | encode_unicode_codepoints(self._source), 45 | encode_unicode_codepoints(source) 46 | ) 47 | source = str(source) 48 | 49 | # backward compatibility 50 | with PyV8.JSContext() as ctxt, PyV8.JSEngine() as engine: 51 | js_errors = (PyV8.JSError, IndexError, ReferenceError, SyntaxError, TypeError) 52 | try: 53 | script = engine.compile(source) 54 | except js_errors as e: 55 | raise exceptions.ProgramError(e) 56 | try: 57 | value = script.run() 58 | except js_errors as e: 59 | raise exceptions.ProgramError(e) 60 | return self.convert(value) 61 | 62 | def _eval(self, source): 63 | return self.exec_('return ' + encode_unicode_codepoints(source)) 64 | 65 | def _call(self, identifier, *args): 66 | args = json.dumps(args) 67 | return self.eval("{identifier}.apply(this, {args})".format(identifier=identifier, args=args)) 68 | 69 | @classmethod 70 | def convert(cls, obj): 71 | from PyV8 import _PyV8 72 | if isinstance(obj, bytes): 73 | return obj.decode('utf8') 74 | if isinstance(obj, _PyV8.JSArray): 75 | return [cls.convert(v) for v in obj] 76 | elif isinstance(obj, _PyV8.JSFunction): 77 | return None 78 | elif isinstance(obj, _PyV8.JSObject): 79 | ret = {} 80 | for k in obj.keys(): 81 | v = cls.convert(obj[k]) 82 | if v is not None: 83 | ret[cls.convert(k)] = v 84 | return ret 85 | else: 86 | return obj 87 | -------------------------------------------------------------------------------- /execjs/_json2.py: -------------------------------------------------------------------------------- 1 | def _json2_source(): 2 | # The folowing code is json2.js(https://github.com/douglascrockford/JSON-js). 3 | # It is compressed by YUI Compressor Online(http://yui.2clics.net/). 4 | 5 | return 'var JSON;if(!JSON){JSON={}}(function(){function f(n){return n<10?"0"+n:n}if(typeof Date.prototype.toJSON!=="function"){Date.prototype.toJSON=function(key){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf()}}var cx=/[\\u0000\\u00ad\\u0600-\\u0604\\u070f\\u17b4\\u17b5\\u200c-\\u200f\\u2028-\\u202f\\u2060-\\u206f\\ufeff\\ufff0-\\uffff]/g,escapable=/[\\\\\\"\\x00-\\x1f\\x7f-\\x9f\\u00ad\\u0600-\\u0604\\u070f\\u17b4\\u17b5\\u200c-\\u200f\\u2028-\\u202f\\u2060-\\u206f\\ufeff\\ufff0-\\uffff]/g,gap,indent,meta={"\\b":"\\\\b","\\t":"\\\\t","\\n":"\\\\n","\\f":"\\\\f","\\r":"\\\\r",\'"\':\'\\\\"\',"\\\\":"\\\\\\\\"},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?\'"\'+string.replace(escapable,function(a){var c=meta[a];return typeof c==="string"?c:"\\\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+\'"\':\'"\'+string+\'"\'}function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==="object"&&typeof value.toJSON==="function"){value=value.toJSON(key)}if(typeof rep==="function"){value=rep.call(holder,key,value)}switch(typeof value){case"string":return quote(value);case"number":return isFinite(value)?String(value):"null";case"boolean":case"null":return String(value);case"object":if(!value){return"null"}gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==="[object Array]"){length=value.length;for(i=0;i>> import execjs 22 | >>> execjs.eval("'red yellow blue'.split(' ')") 23 | ['red', 'yellow', 'blue'] 24 | >>> ctx = execjs.compile(""" 25 | ... function add(x, y) { 26 | ... return x + y; 27 | ... } 28 | ... """) 29 | >>> ctx.call("add", 1, 2) 30 | 3 31 | 32 | # Supported runtimes 33 | 34 | ## First-class support (runtime class is provided and tested) 35 | 36 | * [PyV8](http://code.google.com/p/pyv8/) - A python wrapper for Google V8 engine, 37 | * [Node.js](http://nodejs.org/) 38 | * [PhantomJS](http://phantomjs.org/) 39 | * [Nashorn](http://docs.oracle.com/javase/8/docs/technotes/guides/scripting/nashorn/intro.html#sthref16) - Included with Oracle Java 8 40 | 41 | ## Second-class support (runtime class is privided but not tested) 42 | 43 | * Apple JavaScriptCore - Included with Mac OS X 44 | * [Microsoft Windows Script Host](http://msdn.microsoft.com/en-us/library/9bbdkx3k.aspx) (JScript) 45 | * [SlimerJS](http://slimerjs.org/) 46 | * [Mozilla SpiderMonkey](http://www.mozilla.org/js/spidermonkey/) 47 | 48 | # Installation 49 | 50 | $ pip install PyExecJS 51 | 52 | or 53 | 54 | $ easy_install PyExecJS 55 | 56 | # Details 57 | 58 | If `EXECJS_RUNTIME` environment variable is specified, PyExecJS pick the JavaScript runtime as a default: 59 | 60 | >>> execjs.get().name # this value is depends on your environment. 61 | >>> os.environ["EXECJS_RUNTIME"] = "Node" 62 | >>> execjs.get().name 63 | 'Node.js (V8)' 64 | 65 | You can choose JavaScript runtime by `execjs.get()`: 66 | 67 | >>> default = execjs.get() # the automatically picked runtime 68 | >>> default.eval("1 + 2") 69 | 3 70 | >>> import execjs.runtime_names 71 | >>> jscript = execjs.get(execjs.runtime_names.JScript) 72 | >>> jscript.eval("1 + 2") 73 | 3 74 | >>> import execjs.runtime_names 75 | >>> node = execjs.get(execjs.runtime_names.Node) 76 | >>> node.eval("1 + 2") 77 | 3 78 | 79 | The pros of PyExecJS is that you do not need take care of JavaScript environment. 80 | Especially, it works in Windows environment without installing extra libraries. 81 | 82 | One of cons of PyExecJS is performance. PyExecJS communicate JavaScript runtime by text and it is slow. 83 | The other cons is that it does not fully support runtime specific features. 84 | 85 | [PyV8](https://code.google.com/p/pyv8/) might be better choice for some use case. 86 | 87 | # License 88 | 89 | Copyright (c) 2016 Omoto Kenji. 90 | Copyright (c) 2011 Sam Stephenson and Josh Peek. (As a author of ExecJS) 91 | 92 | Released under the MIT license. See `LICENSE` for details. 93 | 94 | # Changelog 95 | 96 | ## 1.5.0 97 | - Eased version requirement for six. 98 | 99 | ## 1.4.1 100 | - Fixed arguments of module-level functions. 101 | - Fixed bug of execution with pipe. 102 | - Fixed wrong excption is raised. 103 | 104 | ## 1.4.0 105 | - Fixed required libraries. 106 | - Fixed order of output of `--print-available-runtimes`. 107 | - Execute some JavaScript runtime with pipe/stdin (without temporary file). 108 | 109 | ## 1.3.1 110 | - Fixed `--print-available-runtimes` fails in Python 2.7. 111 | 112 | ## 1.3.0 113 | - Added `cwd` argument. 114 | 115 | ## 1.2.0 116 | - Supported Python 3.5 117 | - Supported Nashorn(Java 8 JavaScript engine) as runtime 118 | - Dropped support for Python 2.6 and 3.2 119 | 120 | ## 1.1.0 121 | - Supported Python 3.4 122 | - Supported SlimerJS as runtime 123 | - Supported PhantomJS as runtime 124 | - Fixed JScript runtime on Windows 8 125 | 126 | ## 1.0.5 127 | - Supported Python 3.3 128 | - Fixed file handle leaking 129 | - Fixed issue with passenger-nginx-4.0 130 | 131 | ## 1.0.4 132 | - Removed "import execjs" (it prevent execution of setup.py by Python 2.6) 133 | 134 | ## 1.0.3 135 | - Javascript sources were embeded in __init__.py. 'which' command were reimplemented by pure python. 136 | 137 | ## 1.0.2 138 | - Python 2.6.x was supported. 139 | 140 | ## 1.0.1 141 | - Forgotten shell=True was added to Popen. 142 | 143 | ## 1.0.0 144 | - First release. 145 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | PyExecJS (EOL) 2 | ============== 3 | 4 | End of life 5 | =========== 6 | 7 | This library is no longer maintananced. Bugs are not be fixed (even if 8 | they are trivial or essential). 9 | 10 | We suggest to use other library or to make a fork. 11 | 12 | -------------- 13 | 14 | Run JavaScript code from Python. 15 | 16 | PyExecJS is a porting of ExecJS from Ruby. PyExecJS **automatically** 17 | picks the best runtime available to evaluate your JavaScript program. 18 | 19 | A short example: 20 | 21 | :: 22 | 23 | >>> import execjs 24 | >>> execjs.eval("'red yellow blue'.split(' ')") 25 | ['red', 'yellow', 'blue'] 26 | >>> ctx = execjs.compile(""" 27 | ... function add(x, y) { 28 | ... return x + y; 29 | ... } 30 | ... """) 31 | >>> ctx.call("add", 1, 2) 32 | 3 33 | 34 | Supported runtimes 35 | ================== 36 | 37 | First-class support (runtime class is provided and tested) 38 | ---------------------------------------------------------- 39 | 40 | - `PyV8 `__ - A python wrapper for 41 | Google V8 engine, 42 | - `Node.js `__ 43 | - `PhantomJS `__ 44 | - `Nashorn `__ 45 | - Included with Oracle Java 8 46 | 47 | Second-class support (runtime class is privided but not tested) 48 | --------------------------------------------------------------- 49 | 50 | - Apple JavaScriptCore - Included with Mac OS X 51 | - `Microsoft Windows Script 52 | Host `__ 53 | (JScript) 54 | - `SlimerJS `__ 55 | - `Mozilla SpiderMonkey `__ 56 | 57 | Installation 58 | ============ 59 | 60 | :: 61 | 62 | $ pip install PyExecJS 63 | 64 | or 65 | 66 | :: 67 | 68 | $ easy_install PyExecJS 69 | 70 | Details 71 | ======= 72 | 73 | If ``EXECJS_RUNTIME`` environment variable is specified, PyExecJS pick 74 | the JavaScript runtime as a default: 75 | 76 | :: 77 | 78 | >>> execjs.get().name # this value is depends on your environment. 79 | >>> os.environ["EXECJS_RUNTIME"] = "Node" 80 | >>> execjs.get().name 81 | 'Node.js (V8)' 82 | 83 | You can choose JavaScript runtime by ``execjs.get()``: 84 | 85 | :: 86 | 87 | >>> default = execjs.get() # the automatically picked runtime 88 | >>> default.eval("1 + 2") 89 | 3 90 | >>> import execjs.runtime_names 91 | >>> jscript = execjs.get(execjs.runtime_names.JScript) 92 | >>> jscript.eval("1 + 2") 93 | 3 94 | >>> import execjs.runtime_names 95 | >>> node = execjs.get(execjs.runtime_names.Node) 96 | >>> node.eval("1 + 2") 97 | 3 98 | 99 | The pros of PyExecJS is that you do not need take care of JavaScript 100 | environment. Especially, it works in Windows environment without 101 | installing extra libraries. 102 | 103 | One of cons of PyExecJS is performance. PyExecJS communicate JavaScript 104 | runtime by text and it is slow. The other cons is that it does not fully 105 | support runtime specific features. 106 | 107 | `PyV8 `__ might be better choice for 108 | some use case. 109 | 110 | License 111 | ======= 112 | 113 | Copyright (c) 2016 Omoto Kenji. Copyright (c) 2011 Sam Stephenson and 114 | Josh Peek. (As a author of ExecJS) 115 | 116 | Released under the MIT license. See ``LICENSE`` for details. 117 | 118 | Changelog 119 | ========= 120 | 121 | 1.5.0 122 | ----- 123 | 124 | - Eased version requirement for six. 125 | 126 | 1.4.1 127 | ----- 128 | 129 | - Fixed arguments of module-level functions. 130 | - Fixed bug of execution with pipe. 131 | - Fixed wrong excption is raised. 132 | 133 | 1.4.0 134 | ----- 135 | 136 | - Fixed required libraries. 137 | - Fixed order of output of ``--print-available-runtimes``. 138 | - Execute some JavaScript runtime with pipe/stdin (without temporary 139 | file). 140 | 141 | 1.3.1 142 | ----- 143 | 144 | - Fixed ``--print-available-runtimes`` fails in Python 2.7. 145 | 146 | 1.3.0 147 | ----- 148 | 149 | - Added ``cwd`` argument. 150 | 151 | 1.2.0 152 | ----- 153 | 154 | - Supported Python 3.5 155 | - Supported Nashorn(Java 8 JavaScript engine) as runtime 156 | - Dropped support for Python 2.6 and 3.2 157 | 158 | 1.1.0 159 | ----- 160 | 161 | - Supported Python 3.4 162 | - Supported SlimerJS as runtime 163 | - Supported PhantomJS as runtime 164 | - Fixed JScript runtime on Windows 8 165 | 166 | 1.0.5 167 | ----- 168 | 169 | - Supported Python 3.3 170 | - Fixed file handle leaking 171 | - Fixed issue with passenger-nginx-4.0 172 | 173 | 1.0.4 174 | ----- 175 | 176 | - Removed "import execjs" (it prevent execution of setup.py by Python 177 | 2.6) 178 | 179 | 1.0.3 180 | ----- 181 | 182 | - Javascript sources were embeded in **init**.py. 'which' command were 183 | reimplemented by pure python. 184 | 185 | 1.0.2 186 | ----- 187 | 188 | - Python 2.6.x was supported. 189 | 190 | 1.0.1 191 | ----- 192 | 193 | - Forgotten shell=True was added to Popen. 194 | 195 | 1.0.0 196 | ----- 197 | 198 | - First release. 199 | -------------------------------------------------------------------------------- /test_execjs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: ascii -*- 3 | from __future__ import unicode_literals 4 | import sys 5 | import os 6 | import doctest 7 | import six 8 | 9 | import execjs 10 | 11 | if sys.version_info < (2, 7): 12 | import unittest2 as unittest 13 | else: 14 | import unittest 15 | 16 | 17 | class RuntimeTestBase: 18 | def test_context_call(self): 19 | context = self.runtime.compile("id = function(v) { return v; }") 20 | self.assertEqual("bar", context.call("id", "bar")) 21 | 22 | def test_nested_context_call(self): 23 | context = self.runtime.compile("a = {}; a.b = {}; a.b.id = function(v) { return v; }") 24 | self.assertEqual("bar", context.call("a.b.id", "bar")) 25 | 26 | def test_context_call_missing_function(self): 27 | context = self.runtime.compile("") 28 | with self.assertRaises(execjs.Error): 29 | context.call("missing") 30 | 31 | def test_exec(self): 32 | self.assertIsNone(self.runtime.exec_("1")) 33 | self.assertIsNone(self.runtime.exec_("return")) 34 | self.assertIsNone(self.runtime.exec_("return null")) 35 | self.assertIsNone(self.runtime.exec_("return function() {}")) 36 | self.assertIs(0, self.runtime.exec_("return 0")) 37 | self.assertIs(True, self.runtime.exec_("return true")) 38 | self.assertEqual("hello", self.runtime.exec_("return 'hello'")) 39 | self.assertEqual([1, 2], self.runtime.exec_("return [1, 2]")) 40 | self.assertEqual({"a": 1, "b": 2}, self.runtime.exec_("return {a:1,b:2}")) 41 | self.assertEqual("\u3042", self.runtime.exec_('return "\u3042"')) # unicode char 42 | self.assertEqual("\u3042", self.runtime.exec_(r'return "\u3042"')) # unicode char by escape sequence 43 | self.assertEqual("\\", self.runtime.exec_('return "\\\\"')) 44 | 45 | def test_eval(self): 46 | self.assertIsNone(self.runtime.eval("")) 47 | self.assertIsNone(self.runtime.eval(" ")) 48 | self.assertIsNone(self.runtime.eval("null")) 49 | self.assertIsNone(self.runtime.eval("function(){}")) 50 | self.assertIs(0, self.runtime.eval("0")) 51 | self.assertIs(True, self.runtime.eval("true")) 52 | self.assertEqual([1, 2], self.runtime.eval("[1, 2]")) 53 | self.assertEqual([1, None], self.runtime.eval("[1, function() {}]")) 54 | self.assertEqual("hello", self.runtime.eval("'hello'")) 55 | self.assertEqual(["red", "yellow", "blue"], self.runtime.eval("'red yellow blue'.split(' ')")) 56 | self.assertEqual({"a": 1, "b": 2}, self.runtime.eval("{a:1, b:2}")) 57 | self.assertEqual({"a": True}, self.runtime.eval("{a:true,b:function (){}}")) 58 | self.assertEqual("\u3042", self.runtime.eval('"\u3042"')) 59 | self.assertEqual("\u3042", self.runtime.eval(r'"\u3042"')) 60 | self.assertEqual(r"\\", self.runtime.eval(r'"\\\\"')) 61 | 62 | def test_compile(self): 63 | context = self.runtime.compile("foo = function() { return \"bar\"; }") 64 | self.assertEqual("bar", context.exec_("return foo()")) 65 | self.assertEqual("bar", context.eval("foo()")) 66 | self.assertEqual("bar", context.call("foo")) 67 | 68 | def test_this_is_global_scope(self): 69 | self.assertIs(True, self.runtime.eval("this === (function() {return this})()")) 70 | self.assertIs(True, self.runtime.exec_("return this === (function() {return this})()")) 71 | 72 | def test_compile_large_scripts(self): 73 | body = "var foo = 'bar';\n" * (10 ** 4) 74 | code = "function foo() {\n" + body + "\n};\nreturn true" 75 | self.assertTrue(self.runtime.exec_(code)) 76 | 77 | def test_syntax_error(self): 78 | with self.assertRaises(execjs.Error): 79 | self.runtime.exec_(")") 80 | 81 | def test_thrown_exception(self): 82 | with self.assertRaises(execjs.Error): 83 | self.runtime.exec_("throw 'hello'") 84 | 85 | def test_broken_substitutions(self): 86 | s = '#{source}#{encoded_source}#{json2_source}' 87 | self.assertEqual(s, self.runtime.eval('"' + s + '"')) 88 | 89 | 90 | class DefaultRuntimeTest(unittest.TestCase, RuntimeTestBase): 91 | def setUp(self): 92 | self.runtime = execjs 93 | 94 | class NodeRuntimeTest(unittest.TestCase, RuntimeTestBase): 95 | def setUp(self): 96 | self.runtime = execjs.get('Node') 97 | 98 | class NashornRuntimeTest(unittest.TestCase, RuntimeTestBase): 99 | def setUp(self): 100 | self.runtime = execjs.get('Nashorn') 101 | 102 | class PhantomJSRuntimeTest(unittest.TestCase, RuntimeTestBase): 103 | def setUp(self): 104 | self.runtime = execjs.get('PhantomJS') 105 | 106 | class CommonTest(unittest.TestCase): 107 | def test_empty_path_environ(self): 108 | """ 109 | Some version of passenger-nginx set PATH empty. 110 | """ 111 | orig_path = os.environ['PATH'] 112 | try: 113 | del os.environ['PATH'] 114 | ctx = execjs.compile(""" 115 | function add(x, y) { 116 | return x + y; 117 | } 118 | """) 119 | ctx.call("add", 1, 2) 120 | finally: 121 | os.environ['PATH'] = orig_path 122 | 123 | def test_runtime_availability(self): 124 | r = execjs.ExternalRuntime("fail", ["nonexistent"], "") 125 | self.assertFalse(r.is_available()) 126 | r = execjs.ExternalRuntime("success", ["python"], "") 127 | self.assertTrue(r.is_available()) 128 | 129 | def test_attributes_export(self): 130 | for name in execjs.__all__: 131 | self.assertTrue(hasattr(execjs, name), "{} is not defined".format(name)) 132 | 133 | 134 | def load_tests(loader, tests, ignore): 135 | if six.PY3: 136 | tests.addTests(doctest.DocTestSuite(execjs)) 137 | return tests 138 | 139 | 140 | if __name__ == "__main__": 141 | unittest.main() 142 | -------------------------------------------------------------------------------- /execjs/_external_runtime.py: -------------------------------------------------------------------------------- 1 | from subprocess import Popen, PIPE 2 | import io 3 | import json 4 | import os 5 | import os.path 6 | import platform 7 | import re 8 | import stat 9 | import sys 10 | import tempfile 11 | import six 12 | import execjs._json2 as _json2 13 | import execjs._runner_sources as _runner_sources 14 | 15 | from execjs._exceptions import ( 16 | ProcessExitedWithNonZeroStatus, 17 | ProgramError 18 | ) 19 | 20 | from execjs._abstract_runtime import AbstractRuntime 21 | from execjs._abstract_runtime_context import AbstractRuntimeContext 22 | from execjs._misc import encode_unicode_codepoints 23 | 24 | 25 | class ExternalRuntime(AbstractRuntime): 26 | '''Runtime to execute codes with external command.''' 27 | def __init__(self, name, command, runner_source, encoding='utf8', tempfile=False): 28 | self._name = name 29 | if isinstance(command, str): 30 | command = [command] 31 | self._command = command 32 | self._runner_source = runner_source 33 | self._encoding = encoding 34 | self._tempfile = tempfile 35 | 36 | self._available = self._binary() is not None 37 | 38 | def __str__(self): 39 | return "{class_name}({runtime_name})".format( 40 | class_name=type(self).__name__, 41 | runtime_name=self._name, 42 | ) 43 | 44 | @property 45 | def name(self): 46 | return self._name 47 | 48 | def is_available(self): 49 | return self._available 50 | 51 | def _compile(self, source, cwd=None): 52 | return self.Context(self, source, cwd=cwd, tempfile=self._tempfile) 53 | 54 | def _binary(self): 55 | if not hasattr(self, "_binary_cache"): 56 | self._binary_cache = _which(self._command) 57 | return self._binary_cache 58 | 59 | class Context(AbstractRuntimeContext): 60 | # protected 61 | 62 | def __init__(self, runtime, source='', cwd=None, tempfile=False): 63 | self._runtime = runtime 64 | self._source = source 65 | self._cwd = cwd 66 | self._tempfile = tempfile 67 | 68 | def is_available(self): 69 | return self._runtime.is_available() 70 | 71 | def _eval(self, source): 72 | if not source.strip(): 73 | data = "''" 74 | else: 75 | data = "'('+" + json.dumps(source, ensure_ascii=True) + "+')'" 76 | 77 | code = 'return eval({data})'.format(data=data) 78 | return self.exec_(code) 79 | 80 | def _exec_(self, source): 81 | if self._source: 82 | source = self._source + '\n' + source 83 | 84 | if self._tempfile: 85 | output = self._exec_with_tempfile(source) 86 | else: 87 | output = self._exec_with_pipe(source) 88 | return self._extract_result(output) 89 | 90 | def _call(self, identifier, *args): 91 | args = json.dumps(args) 92 | return self._eval("{identifier}.apply(this, {args})".format(identifier=identifier, args=args)) 93 | 94 | def _exec_with_pipe(self, source): 95 | cmd = self._runtime._binary() 96 | 97 | p = None 98 | try: 99 | p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, cwd=self._cwd, universal_newlines=True) 100 | input = self._compile(source) 101 | if six.PY2: 102 | input = input.encode(sys.getfilesystemencoding()) 103 | stdoutdata, stderrdata = p.communicate(input=input) 104 | ret = p.wait() 105 | finally: 106 | del p 107 | 108 | self._fail_on_non_zero_status(ret, stdoutdata, stderrdata) 109 | return stdoutdata 110 | 111 | def _exec_with_tempfile(self, source): 112 | (fd, filename) = tempfile.mkstemp(prefix='execjs', suffix='.js') 113 | os.close(fd) 114 | try: 115 | with io.open(filename, "w+", encoding=self._runtime._encoding) as fp: 116 | fp.write(self._compile(source)) 117 | cmd = self._runtime._binary() + [filename] 118 | 119 | p = None 120 | try: 121 | p = Popen(cmd, stdout=PIPE, stderr=PIPE, cwd=self._cwd, universal_newlines=True) 122 | stdoutdata, stderrdata = p.communicate() 123 | ret = p.wait() 124 | finally: 125 | del p 126 | 127 | self._fail_on_non_zero_status(ret, stdoutdata, stderrdata) 128 | return stdoutdata 129 | finally: 130 | os.remove(filename) 131 | 132 | def _fail_on_non_zero_status(self, status, stdoutdata, stderrdata): 133 | if status != 0: 134 | raise ProcessExitedWithNonZeroStatus(status=status, stdout=stdoutdata, stderr=stderrdata) 135 | 136 | def _compile(self, source): 137 | runner_source = self._runtime._runner_source 138 | 139 | replacements = { 140 | '#{source}': lambda: source, 141 | '#{encoded_source}': lambda: json.dumps( 142 | "(function(){ " + 143 | encode_unicode_codepoints(source) + 144 | " })()" 145 | ), 146 | '#{json2_source}': _json2._json2_source, 147 | } 148 | 149 | pattern = "|".join(re.escape(k) for k in replacements) 150 | 151 | runner_source = re.sub(pattern, lambda m: replacements[m.group(0)](), runner_source) 152 | 153 | return runner_source 154 | 155 | def _extract_result(self, output): 156 | output = output.replace("\r\n", "\n").replace("\r", "\n") 157 | output_last_line = output.split("\n")[-2] 158 | 159 | ret = json.loads(output_last_line) 160 | if len(ret) == 1: 161 | ret = [ret[0], None] 162 | status, value = ret 163 | 164 | if status == "ok": 165 | return value 166 | else: 167 | raise ProgramError(value) 168 | 169 | 170 | def _is_windows(): 171 | """protected""" 172 | return platform.system() == 'Windows' 173 | 174 | 175 | def _decode_if_not_text(s): 176 | """protected""" 177 | if isinstance(s, six.text_type): 178 | return s 179 | return s.decode(sys.getfilesystemencoding()) 180 | 181 | 182 | def _find_executable(prog, pathext=("",)): 183 | """protected""" 184 | pathlist = _decode_if_not_text(os.environ.get('PATH', '')).split(os.pathsep) 185 | 186 | for dir in pathlist: 187 | for ext in pathext: 188 | filename = os.path.join(dir, prog + ext) 189 | try: 190 | st = os.stat(filename) 191 | except os.error: 192 | continue 193 | if stat.S_ISREG(st.st_mode) and (stat.S_IMODE(st.st_mode) & 0o111): 194 | return filename 195 | return None 196 | 197 | 198 | def _which(command): 199 | """protected""" 200 | if isinstance(command, str): 201 | command = [command] 202 | command = list(command) 203 | name = command[0] 204 | args = command[1:] 205 | 206 | if _is_windows(): 207 | pathext = _decode_if_not_text(os.environ.get("PATHEXT", "")) 208 | path = _find_executable(name, pathext.split(os.pathsep)) 209 | else: 210 | path = _find_executable(name) 211 | 212 | if not path: 213 | return None 214 | return [path] + args 215 | 216 | 217 | def node(): 218 | r = node_node() 219 | if r.is_available(): 220 | return r 221 | return node_nodejs() 222 | 223 | 224 | def node_node(): 225 | return ExternalRuntime( 226 | name="Node.js (V8)", 227 | command=['node'], 228 | encoding='UTF-8', 229 | runner_source=_runner_sources.Node 230 | ) 231 | 232 | 233 | def node_nodejs(): 234 | return ExternalRuntime( 235 | name="Node.js (V8)", 236 | command=['nodejs'], 237 | encoding='UTF-8', 238 | runner_source=_runner_sources.Node 239 | ) 240 | 241 | 242 | def jsc(): 243 | return ExternalRuntime( 244 | name="JavaScriptCore", 245 | command=["/System/Library/Frameworks/JavaScriptCore.framework/Versions/A/Resources/jsc"], 246 | runner_source=_runner_sources.JavaScriptCore, 247 | tempfile=True 248 | ) 249 | 250 | 251 | def spidermonkey(): 252 | return ExternalRuntime( 253 | name="SpiderMonkey", 254 | command=["js"], 255 | runner_source=_runner_sources.SpiderMonkey, 256 | tempfile=True 257 | ) 258 | 259 | 260 | def jscript(): 261 | return ExternalRuntime( 262 | name="JScript", 263 | command=["cscript", "//E:jscript", "//Nologo"], 264 | encoding="ascii", 265 | runner_source=_runner_sources.JScript, 266 | tempfile=True 267 | ) 268 | 269 | 270 | def phantomjs(): 271 | return ExternalRuntime( 272 | name="PhantomJS", 273 | command=["phantomjs"], 274 | runner_source=_runner_sources.PhantomJS, 275 | tempfile=True 276 | ) 277 | 278 | 279 | def slimerjs(): 280 | return ExternalRuntime( 281 | name="SlimerJS", 282 | command=["slimerjs"], 283 | runner_source=_runner_sources.SlimerJS, 284 | tempfile=True 285 | ) 286 | 287 | 288 | def nashorn(): 289 | return ExternalRuntime( 290 | name="Nashorn", 291 | command=["jjs"], 292 | runner_source=_runner_sources.Nashorn, 293 | tempfile=True 294 | ) 295 | --------------------------------------------------------------------------------