# -*- coding: utf-8 -*- import subprocess import sys from os import path from urllib import quote from pyload.utils import encode, decode, uniqify class JsEngine: """ JS Engine superclass """ def __init__(self, core, engine=None): #: engine can be a jse name """string""" or an AbstractEngine """class""" self.core = core self.engine = None #: Default engine Instance if not ENGINES: self.core.log.critical("No JS Engine found!") return if not engine: engine = self.core.config.get("general", "jsengine") if engine != "auto" and self.set(engine) is False: engine = "auto" self.core.log.warning("JS Engine set to \"auto\" for safely") if engine == "auto": for E in self.find(): if self.set(E) is True: break else: self.core.log.error("No JS Engine available") @classmethod def find(cls): """ Check if there is any engine available """ return [E for E in ENGINES if E.find()] def get(self, engine): """ Convert engine name (string) to relative JSE class (AbstractEngine extended) """ if isinstance(engine, basestring): engine_name = engine.lower() for E in ENGINES: if E.NAME == engine_name: #: doesn't check if E(NGINE) is available, just convert string to class JSE = E break else: JSE = None elif issubclass(engine, AbstractEngine): JSE = engine else: JSE = None return JSE def set(self, engine): """ Set engine name (string) or JSE class (AbstractEngine extended) as default engine """ if isinstance(engine, basestring): self.set(self.get(engine)) elif issubclass(engine, AbstractEngine) and engine.find(): self.engine = engine return True else: return False def eval(self, script, engine=None): #: engine can be a jse name """string""" or an AbstractEngine """class""" if not engine: JSE = self.engine else: JSE = self.get(engine) if not JSE: return None script = encode(script) out, err = JSE.eval(script) results = [out] if self.core.config.get("general", "debug"): if err: self.core.log.debug(JSE.NAME + ":", err) engines = self.find() engines.remove(JSE) for E in engines: out, err = E.eval(script) res = err or out self.core.log.debug(E.NAME + ":", res) results.append(res) if len(results) > 1 and len(uniqify(results)) > 1: self.core.log.warning("JS output of two or more engines mismatch") return results[0] class AbstractEngine: """ JSE base class """ NAME = "" def __init__(self): self.setup() self.available = self.find() def setup(self): pass @classmethod def find(cls): """ Check if the engine is available """ try: __import__(cls.NAME) except ImportError: try: out, err = cls().eval("print(23+19)") except: res = False else: res = out == "42" else: res = True finally: return res def _eval(args): if not self.available: return None, "JS Engine \"%s\" not found" % self.NAME try: p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=-1) return map(lambda x: x.strip(), p.communicate()) except Exception, e: return None, e def eval(script): raise NotImplementedError class Pyv8Engine(AbstractEngine): NAME = "pyv8" def eval(self, script): if not self.available: return None, "JS Engine \"%s\" not found" % self.NAME try: rt = PyV8.JSContext() rt.enter() res = rt.eval(script), None #@TODO: parse stderr except Exception, e: res = None, e finally: return res class CommonEngine(AbstractEngine): NAME = "js" def setup(self): subprocess.Popen(["js", "-v"], bufsize=-1).communicate() def eval(self, script): script = "print(eval(unescape('%s')))" % quote(script) args = ["js", "-e", script] return self._eval(args) class NodeEngine(AbstractEngine): NAME = "nodejs" def setup(self): subprocess.Popen(["node", "-v"], bufsize=-1).communicate() def eval(self, script): script = "console.log(eval(unescape('%s')))" % quote(script) args = ["node", "-e", script] return self._eval(args) class RhinoEngine(AbstractEngine): NAME = "rhino" def setup(self): jspath = [ "/usr/share/java*/js.jar", "js.jar", path.join(pypath, "js.jar") ] for p in jspath: if path.exists(p): self.path = p break else: self.path = "" def eval(self, script): script = "print(eval(unescape('%s')))" % quote(script) args = ["java", "-cp", self.path, "org.mozilla.javascript.tools.shell.Main", "-e", script] res = decode(self._eval(args)) try: return res.encode("ISO-8859-1") finally: return res class JscEngine(AbstractEngine): NAME = "javascriptcore" def setup(self): jspath = "/System/Library/Frameworks/JavaScriptCore.framework/Resources/jsc" self.path = jspath if path.exists(jspath) else "" def eval(self, script): script = "print(eval(unescape('%s')))" % quote(script) args = [self.path, "-e", script] return self._eval(args) #@NOTE: Priority ordered ENGINES = [CommonEngine, Pyv8Engine, NodeEngine, RhinoEngine] if sys.platform == "darwin": ENGINES.insert(JscEngine)