288 lines
8.0 KiB
Python
288 lines
8.0 KiB
Python
from subprocess import Popen, PIPE
|
|
import io
|
|
import json
|
|
import os
|
|
import os.path
|
|
import platform
|
|
import re
|
|
import stat
|
|
import sys
|
|
import tempfile
|
|
import six
|
|
import execjs._json2 as _json2
|
|
import execjs._runner_sources as _runner_sources
|
|
import execjs._exceptions as exceptions
|
|
from execjs._abstract_runtime import AbstractRuntime
|
|
from execjs._abstract_runtime_context import AbstractRuntimeContext
|
|
from execjs._misc import encode_unicode_codepoints
|
|
|
|
|
|
class ExternalRuntime(AbstractRuntime):
|
|
'''Runtime to execute codes with external command.'''
|
|
def __init__(self, name, command, runner_source, encoding='utf8', tempfile=False):
|
|
self._name = name
|
|
if isinstance(command, str):
|
|
command = [command]
|
|
self._command = command
|
|
self._runner_source = runner_source
|
|
self._encoding = encoding
|
|
self._tempfile = tempfile
|
|
|
|
self._available = self._binary() is not None
|
|
|
|
def __str__(self):
|
|
return "{class_name}({runtime_name})".format(
|
|
class_name=type(self).__name__,
|
|
runtime_name=self._name,
|
|
)
|
|
|
|
@property
|
|
def name(self):
|
|
return self._name
|
|
|
|
def is_available(self):
|
|
return self._available
|
|
|
|
def _compile(self, source, cwd=None):
|
|
return self.Context(self, source, cwd=cwd, tempfile=tempfile)
|
|
|
|
def _binary(self):
|
|
if not hasattr(self, "_binary_cache"):
|
|
self._binary_cache = _which(self._command)
|
|
return self._binary_cache
|
|
|
|
class Context(AbstractRuntimeContext):
|
|
# protected
|
|
|
|
def __init__(self, runtime, source='', cwd=None, tempfile=False):
|
|
self._runtime = runtime
|
|
self._source = source
|
|
self._cwd = cwd
|
|
self._tempfile = tempfile
|
|
|
|
def is_available(self):
|
|
return self._runtime.is_available()
|
|
|
|
def _eval(self, source):
|
|
if not source.strip():
|
|
data = "''"
|
|
else:
|
|
data = "'('+" + json.dumps(source, ensure_ascii=True) + "+')'"
|
|
|
|
code = 'return eval({data})'.format(data=data)
|
|
return self.exec_(code)
|
|
|
|
def _exec_(self, source):
|
|
if self._source:
|
|
source = self._source + '\n' + source
|
|
|
|
if self._tempfile:
|
|
output = self._exec_with_tempfile(source)
|
|
else:
|
|
output = self._exec_with_pipe(source)
|
|
return self._extract_result(output)
|
|
|
|
def _call(self, identifier, *args):
|
|
args = json.dumps(args)
|
|
return self._eval("{identifier}.apply(this, {args})".format(identifier=identifier, args=args))
|
|
|
|
def _exec_with_pipe(self, source):
|
|
cmd = self._runtime._binary()
|
|
|
|
p = None
|
|
try:
|
|
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, cwd=self._cwd, universal_newlines=True)
|
|
stdoutdata, stderrdata = p.communicate(input=source)
|
|
ret = p.wait()
|
|
finally:
|
|
del p
|
|
|
|
self._fail_on_non_zero_status(ret, stdoutdata, stderrdata)
|
|
return stdoutdata
|
|
|
|
def _exec_with_tempfile(self, source):
|
|
(fd, filename) = tempfile.mkstemp(prefix='execjs', suffix='.js')
|
|
os.close(fd)
|
|
try:
|
|
with io.open(filename, "w+", encoding=self._runtime._encoding) as fp:
|
|
fp.write(self._compile(source))
|
|
cmd = self._runtime._binary() + [filename]
|
|
|
|
p = None
|
|
try:
|
|
p = Popen(cmd, stdout=PIPE, stderr=PIPE, cwd=self._cwd)
|
|
stdoutdata, stderrdata = p.communicate()
|
|
ret = p.wait()
|
|
finally:
|
|
del p
|
|
|
|
self._fail_on_non_zero_status(ret, stdoutdata, stderrdata)
|
|
return stdoutdata
|
|
finally:
|
|
os.remove(filename)
|
|
|
|
def _fail_on_non_zero_status(self, status, stdoutdata, stderrdata):
|
|
if status != 0:
|
|
raise exceptions.RuntimeError("stdout: {}, stderr: {}".format(repr(stdoutdata), repr(stderrdata)))
|
|
|
|
def _compile(self, source):
|
|
runner_source = self._runtime._runner_source
|
|
|
|
replacements = {
|
|
'#{source}': lambda: source,
|
|
'#{encoded_source}': lambda: json.dumps(
|
|
"(function(){ " +
|
|
encode_unicode_codepoints(source) +
|
|
" })()"
|
|
),
|
|
'#{json2_source}': _json2._json2_source,
|
|
}
|
|
|
|
pattern = "|".join(re.escape(k) for k in replacements)
|
|
|
|
runner_source = re.sub(pattern, lambda m: replacements[m.group(0)](), runner_source)
|
|
|
|
return runner_source
|
|
|
|
def _extract_result(self, output):
|
|
output = output.decode(self._runtime._encoding)
|
|
output = output.replace("\r\n", "\n").replace("\r", "\n")
|
|
output_last_line = output.split("\n")[-2]
|
|
|
|
if not output_last_line:
|
|
status = value = None
|
|
else:
|
|
ret = json.loads(output_last_line)
|
|
if len(ret) == 1:
|
|
ret = [ret[0], None]
|
|
status, value = ret
|
|
|
|
if status == "ok":
|
|
return value
|
|
elif value.startswith('SyntaxError:'):
|
|
raise exceptions.RuntimeError(value)
|
|
else:
|
|
raise exceptions.ProgramError(value)
|
|
|
|
|
|
def _is_windows():
|
|
"""protected"""
|
|
return platform.system() == 'Windows'
|
|
|
|
|
|
def _decode_if_not_text(s):
|
|
"""protected"""
|
|
if isinstance(s, six.text_type):
|
|
return s
|
|
return s.decode(sys.getfilesystemencoding())
|
|
|
|
|
|
def _find_executable(prog, pathext=("",)):
|
|
"""protected"""
|
|
pathlist = _decode_if_not_text(os.environ.get('PATH', '')).split(os.pathsep)
|
|
|
|
for dir in pathlist:
|
|
for ext in pathext:
|
|
filename = os.path.join(dir, prog + ext)
|
|
try:
|
|
st = os.stat(filename)
|
|
except os.error:
|
|
continue
|
|
if stat.S_ISREG(st.st_mode) and (stat.S_IMODE(st.st_mode) & 0o111):
|
|
return filename
|
|
return None
|
|
|
|
|
|
def _which(command):
|
|
"""protected"""
|
|
if isinstance(command, str):
|
|
command = [command]
|
|
command = list(command)
|
|
name = command[0]
|
|
args = command[1:]
|
|
|
|
if _is_windows():
|
|
pathext = _decode_if_not_text(os.environ.get("PATHEXT", ""))
|
|
path = _find_executable(name, pathext.split(os.pathsep))
|
|
else:
|
|
path = _find_executable(name)
|
|
|
|
if not path:
|
|
return None
|
|
return [path] + args
|
|
|
|
|
|
def node():
|
|
r = node_node()
|
|
if r.is_available():
|
|
return r
|
|
return node_nodejs()
|
|
|
|
|
|
def node_node():
|
|
return ExternalRuntime(
|
|
name="Node.js (V8)",
|
|
command=['node'],
|
|
encoding='UTF-8',
|
|
runner_source=_runner_sources.Node
|
|
)
|
|
|
|
|
|
def node_nodejs():
|
|
return ExternalRuntime(
|
|
name="Node.js (V8)",
|
|
command=['nodejs'],
|
|
encoding='UTF-8',
|
|
runner_source=_runner_sources.Node
|
|
)
|
|
|
|
|
|
def jsc():
|
|
return ExternalRuntime(
|
|
name="JavaScriptCore",
|
|
command=["/System/Library/Frameworks/JavaScriptCore.framework/Versions/A/Resources/jsc"],
|
|
runner_source=_runner_sources.JavaScriptCore
|
|
)
|
|
|
|
|
|
def spidermonkey():
|
|
return ExternalRuntime(
|
|
name="SpiderMonkey",
|
|
command=["js"],
|
|
runner_source=_runner_sources.SpiderMonkey
|
|
)
|
|
|
|
|
|
def jscript():
|
|
return ExternalRuntime(
|
|
name="JScript",
|
|
command=["cscript", "//E:jscript", "//Nologo"],
|
|
encoding="ascii",
|
|
runner_source=_runner_sources.JScript,
|
|
tempfile=True
|
|
)
|
|
|
|
|
|
def phantomjs():
|
|
return ExternalRuntime(
|
|
name="PhantomJS",
|
|
command=["phantomjs"],
|
|
runner_source=_runner_sources.PhantomJS
|
|
)
|
|
|
|
|
|
def slimerjs():
|
|
return ExternalRuntime(
|
|
name="SlimerJS",
|
|
command=["slimerjs"],
|
|
runner_source=_runner_sources.SlimerJS
|
|
)
|
|
|
|
|
|
def nashorn():
|
|
return ExternalRuntime(
|
|
name="Nashorn",
|
|
command=["jjs"],
|
|
runner_source=_runner_sources.Nashorn
|
|
)
|