first light on import hooks

This commit is contained in:
Anthony Scopatz 2015-03-29 17:49:44 -05:00
parent 1ff8b24cdf
commit 8fd9e05b9b
4 changed files with 101 additions and 2 deletions

3
tests/sample.xsh Normal file
View file

@ -0,0 +1,3 @@
# I am a test module.
x = $(echo "hello mom")

14
tests/test_imphooks.py Normal file
View file

@ -0,0 +1,14 @@
"""Testing xonsh import hooks"""
from __future__ import unicode_literals, print_function
import nose
from nose.tools import assert_equal
from xonsh import imphooks
def test_relative_import():
import sample
assert_equal('hello mom\n', sample.x)
if __name__ == '__main__':
nose.runmodule()

View file

@ -17,7 +17,7 @@ class Execer(object):
"""Executes xonsh code in a context."""
def __init__(self, filename='<xonsh-code>', debug_level=0,
parser_args=None):
parser_args=None, unload=True):
"""Parameters
----------
filename : str, optional
@ -26,16 +26,20 @@ class Execer(object):
Debugging level to use in lexing and parsing.
parser_args : dict, optional
Arguments to pass down to the parser.
unload : bool, optional
Whether or not to unload xonsh builtins upon deletion.
"""
parser_args = parser_args or {}
self.parser = Parser(**parser_args)
self.filename = filename
self.debug_level = debug_level
self.unload = unload
self.ctxtransformer = ast.CtxAwareTransformer(self.parser)
load_builtins(execer=self)
def __del__(self):
unload_builtins()
if self.unload:
unload_builtins()
def parse(self, input, ctx, mode='exec'):
"""Parses xonsh code in a context-aware fashion. For context-free

78
xonsh/imphooks.py Normal file
View file

@ -0,0 +1,78 @@
"""Import hooks for importing xonsh source files. This module registers
the hooks it defines when it is imported.
"""
from __future__ import print_function, unicode_literals
import os
import sys
import builtins
from importlib.machinery import ModuleSpec
from importlib.abc import MetaPathFinder, SourceLoader
from xonsh.tools import string_types
from xonsh.execer import Execer
class XonshImportHook(MetaPathFinder, SourceLoader):
"""Implements the import hook for xonsh source files."""
def __init__(self, *args, **kwargs):
super(XonshImportHook, self).__init__(*args, **kwargs)
self._filenames = {}
self._execer = None
@property
def execer(self):
if hasattr(builtins, '__xonsh_execer__'):
execer = builtins.__xonsh_execer__
if self._execer is not None:
self._execer = None
elif self._execer is None:
self._execer = execer = Execer(unload=False)
else:
execer = self._execer
return execer
#
# MetaPathFinder methods
#
def find_spec(self, fullname, path, target=None):
"""Finds the spec for a xonsh module if it exists."""
spec = None
path = sys.path if path is None else path
name = fullname.rsplit('.', 1)[-1]
fname = name + '.xsh'
for p in path:
if not isinstance(p, string_types):
continue
if fname not in os.listdir(p):
continue
spec = ModuleSpec(fullname, self)
self._filenames[fullname] = os.path.join(p, fname)
break
return spec
#
# SourceLoader methods
#
def get_filename(self, fullname):
"""Returns the filename for a module's fullname."""
return self._filenames[fullname]
def get_data(self, path):
"""Gets the bytes for a path."""
raise NotImplementedError
def get_code(self, fullname):
"""Gets the code object for a xonsh file."""
filename = self._filenames.get(fullname, None)
if filename is None:
msg = "xonsh file {0!r} could not be found".format(fullname)
raise ImportError(msg)
with open(filename, 'r') as f:
src = f.read()
src = src if src.endswith('\n') else src + '\n'
execer = self.execer
execer.filename = filename
ctx = {} # dummy for modules
code = execer.compile(src, glbs=ctx, locs=ctx)
return code
sys.meta_path.append(XonshImportHook())