# coding=utf-8 import sys import os import os.path import codecs import json import gc import imp import re from file import File BASE_PATH = os.path.abspath(os.path.dirname(__file__)) is_python3 = sys.version_info[0] > 2 core_files = ['emmet-app.js', 'python-wrapper.js'] def should_use_unicode(): """ WinXP unable to eval JS in unicode object (while other OSes requires it) This function checks if we have to use unicode when reading files """ ctx = PyV8.JSContext() ctx.enter() use_unicode = True try: ctx.eval(u'(function(){return;})()') except: use_unicode = False ctx.leave() return use_unicode def make_path(filename): return os.path.normpath(os.path.join(BASE_PATH, filename)) def js_log(message): print(message) def js_file_reader(file_path, use_unicode=True): if use_unicode: f = codecs.open(file_path, 'r', 'utf-8') else: f = open(file_path, 'r') content = f.read() f.close() return content def import_pyv8(): # Importing non-existing modules is a bit tricky in Python: # if we simply call `import PyV8` and module doesn't exists, # Python will cache this failed import and will always # throw exception even if this module appear in PYTHONPATH. # To prevent this, we have to manually test if # PyV8.py(c) exists in PYTHONPATH before importing PyV8 if 'PyV8' in sys.modules: # PyV8 was loaded by ST2, create global alias if 'PyV8' not in globals(): globals()['PyV8'] = __import__('PyV8') return loaded = False f, pathname, description = imp.find_module('PyV8') bin_f, bin_pathname, bin_description = imp.find_module('_PyV8') if f: try: imp.acquire_lock() globals()['_PyV8'] = imp.load_module('_PyV8', bin_f, bin_pathname, bin_description) globals()['PyV8'] = imp.load_module('PyV8', f, pathname, description) imp.release_lock() loaded = True finally: # Since we may exit via an exception, close fp explicitly. if f: f.close() if bin_f: bin_f.close() if not loaded: raise ImportError('No PyV8 module found') class Context(): """ Creates Emmet JS core context. Before instantiating this class, make sure PyV8 is available in `sys.path` @param files: Additional files to load with JS core @param path: Path to Emmet extensions @param contrib: Python objects to contribute to JS execution context @param pyv8_path: Location of PyV8 binaries """ def __init__(self, files=[], ext_path=None, contrib=None, logger=None, reader=js_file_reader): self.logger = logger self.reader = reader try: import_pyv8() except ImportError as e: pass self._ctx = None self._contrib = contrib self._should_load_extension = True # detect reader encoding self._use_unicode = None self._core_files = [] + core_files + files self._ext_path = None self.set_ext_path(ext_path) self._user_data = None def log(self, message): if self.logger: self.logger(message) def get_ext_path(self): return self._ext_path def set_ext_path(self, val): try: if val and val[:1] == '~': val = os.path.expanduser(val) val = os.path.abspath(val) except Exception as e: return if val == self._ext_path: return self._ext_path = val self.reset() def load_extensions(self, path=None): if path is None: path = self._ext_path; if path and os.path.isdir(path): ext_files = [] self.log('Loading Emmet extensions from %s' % self._ext_path) for dirname, dirnames, filenames in os.walk(self._ext_path): for filename in filenames: if filename[0] != '.': ext_files.append(os.path.join(dirname, filename)) self.js().locals.pyLoadExtensions(ext_files) def js(self): "Returns JS context" if not self._ctx: try: import_pyv8() except ImportError as e: return None if 'PyV8' not in sys.modules: # Binary is not available yet return None if self._use_unicode is None: self._use_unicode = should_use_unicode() glue = u'\n' if self._use_unicode else '\n' core_src = [self.read_js_file(make_path(f)) for f in self._core_files] self._ctx = PyV8.JSContext() self._ctx.enter() self._ctx.eval(glue.join(core_src)) # for f in self._core_files: # self._ctx.eval(self.read_js_file(make_path(f)), name=f, line=0, col=0) # load default snippets self._ctx.locals.pyLoadSystemSnippets(self.read_js_file(make_path('snippets.json'))) # expose some methods self._ctx.locals.log = js_log self._ctx.locals.pyFile = File() if self._contrib: for k in self._contrib: self._ctx.locals[k] = self._contrib[k] else: self._ctx.enter() if self._should_load_extension: self._ctx.locals.pyResetUserData() self._should_load_extension = False self.load_extensions() if self._user_data: self._ctx.locals.pyLoadUserData(self._user_data) self._user_data = None return self._ctx def load_user_data(self, data): "Loads user data payload from JSON" self._user_data = data # self.js().locals.pyLoadUserData(data) def reset(self): "Resets JS execution context" if self._ctx: self._ctx.leave() self._ctx = None try: PyV8.JSEngine.collect() gc.collect() except: pass self._should_load_extension = True def read_js_file(self, file_path): return self.reader(file_path, self._use_unicode) def eval(self, source): self.js().eval(source) def eval_js_file(self, file_path): self.eval(self.read_js_file(file_path))