Repository: xonsh/xonsh

Issue:
Xonsh editable install does not load importhooks
In particular, as the dev install does not use amalgamate, the `sys.modules` ends up missing the following imports:

```
['xonsh.contexts',
 'xonsh.imphooks',
 'xonsh.completers.__amalgam__',
 'xonsh.tracer',
 'xonsh.__amalgam__',
 'xonsh.readline_shell',
 'xontrib.prompt_ret_code',
 'xonsh.ansi_colors',
 'xontrib.xonda'] 
```

and in particulat  `xonsh.imphooks` [which](https://lh3.googleusercontent.com/-5GTvwtJHZdU/TXmQM8FCatI/AAAAAAAAKko/hpQvPGuyz0k/I+Am+Not+A+Witch+-+Monty+Python+and+the+Holy+Grail.jpg) makes `xontrib/*.xsh` to  not be able to be imported. 

Not sure if it's an https://github.com/xonsh/amalgamate bug as well, as it shoudl not have found imphooks as it's imported nowhere else... 

imphooks not automatically loaded
Xonsh does not currently load the `.xsh` import machinery (`xonsh.imphooks`) automatically. This causes xontribs authored in xsh to not be found or loadable by `xontrib`.

I [made a test](https://github.com/xonsh/xonsh/compare/master...astronouth7303:test-xsh-xontrib) for the xontrib problem.

This is affecting @Siecje contributing the prompt-ret-code xontrib.



Discussion:

Resolving patch:
diff --git a/xonsh/imphooks.py b/xonsh/imphooks.py
--- a/xonsh/imphooks.py
+++ b/xonsh/imphooks.py
@@ -84,4 +84,14 @@ def get_code(self, fullname):
         return code
 
 
-sys.meta_path.append(XonshImportHook())
+def install_hook():
+    """
+    Install Xonsh import hook in `sys.metapath` in order for `.xsh` files to be
+    importable.
+
+    Can safely be called many times, will be no-op if a xonsh import hook is
+    already present.
+    """
+
+    if XonshImportHook not in {type(hook) for hook in sys.meta_path}:
+        sys.meta_path.append(XonshImportHook())
diff --git a/xonsh/main.py b/xonsh/main.py
--- a/xonsh/main.py
+++ b/xonsh/main.py
@@ -18,6 +18,7 @@
 from xonsh.codecache import run_script_with_cache, run_code_with_cache
 from xonsh.xonfig import xonfig_main
 from xonsh.lazyimps import pygments, pyghooks
+from xonsh.imphooks import install_hook
 
 
 def get_setproctitle():
@@ -188,6 +189,7 @@ def premain(argv=None):
         args.mode = XonshMode.interactive
         shell_kwargs['completer'] = True
         shell_kwargs['login'] = True
+    install_hook()
     builtins.__xonsh_shell__ = Shell(**shell_kwargs)
     env = builtins.__xonsh_env__
     env['XONSH_LOGIN'] = shell_kwargs['login']


Test patch:
diff --git a/tests/test_imphooks.py b/tests/test_imphooks.py
--- a/tests/test_imphooks.py
+++ b/tests/test_imphooks.py
@@ -2,13 +2,13 @@
 """Testing xonsh import hooks"""
 import pytest
 
-from xonsh import imphooks  # noqa
-from xonsh import built_ins
+from xonsh import imphooks
 from xonsh.environ import Env
-from xonsh.execer import Execer
 from xonsh.built_ins import load_builtins, unload_builtins
 import builtins
 
+imphooks.install_hook()
+
 
 @pytest.yield_fixture(autouse=True)
 def imp_env(xonsh_execer):
diff --git a/tests/test_xontribs.py b/tests/test_xontribs.py
--- a/tests/test_xontribs.py
+++ b/tests/test_xontribs.py
@@ -53,4 +53,16 @@ def test_withall(tmpmod):
 """)
 
     ctx = xontrib_context('spameggs')
-    assert ctx == {'spam': 1, '_foobar': 3}
\ No newline at end of file
+    assert ctx == {'spam': 1, '_foobar': 3}
+
+def test_xshxontrib(tmpmod):
+    """
+    Test that .xsh xontribs are loadable
+    """
+    with tmpmod.mkdir("xontrib").join("script.xsh").open('w') as x:
+        x.write("""
+hello = 'world'
+""")
+
+    ctx = xontrib_context('script')
+    assert ctx == {'hello': 'world'}