mirror of
https://github.com/xonsh/xonsh.git
synced 2025-03-06 09:20:57 +01:00
commit
4afd9da1f0
8 changed files with 219 additions and 37 deletions
31
Makefile
Normal file
31
Makefile
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# Make GNU Make xonshy
|
||||||
|
SHELL=xonsh
|
||||||
|
.SHELLFLAGS=-c
|
||||||
|
.ONESHELL:
|
||||||
|
.SILENT:
|
||||||
|
|
||||||
|
# Unlike normal makefiles: executes the entire body in one go under xonsh, and doesn't echo
|
||||||
|
|
||||||
|
.PHONY: help
|
||||||
|
help:
|
||||||
|
print("""
|
||||||
|
Utility file for xonsh project. Try these targets:
|
||||||
|
* amalgamate: Generate __amalgam__.py files
|
||||||
|
* clean: Remove generated files (namely, the amalgamations)
|
||||||
|
* xonsh/ply: Pull down most recent ply
|
||||||
|
""")
|
||||||
|
|
||||||
|
xonsh/ply:
|
||||||
|
git subtree pull --prefix xonsh/ply https://github.com/dabeaz/ply.git master --squash
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
find xonsh -name __amalgam__.py -delete -print
|
||||||
|
|
||||||
|
.PHONY: amalgamate
|
||||||
|
amalgamate:
|
||||||
|
sys.path.insert(0, '.')
|
||||||
|
import setup
|
||||||
|
setup.amalgamate_source()
|
||||||
|
_ = sys.path.pop(0)
|
14
news/load-events.rst
Normal file
14
news/load-events.rst
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
**Added:**
|
||||||
|
|
||||||
|
* Load events are now available
|
||||||
|
* New events added: ``on_post_init``, ``on_pre_cmdloop``, ``on_pre_rc``, ``on_post_rc``, ``on_ptk_create``
|
||||||
|
|
||||||
|
**Changed:** None
|
||||||
|
|
||||||
|
**Deprecated:** None
|
||||||
|
|
||||||
|
**Removed:** None
|
||||||
|
|
||||||
|
**Fixed:** None
|
||||||
|
|
||||||
|
**Security:** None
|
|
@ -3,10 +3,12 @@ import inspect
|
||||||
import pytest
|
import pytest
|
||||||
from xonsh.events import EventManager, Event, LoadEvent
|
from xonsh.events import EventManager, Event, LoadEvent
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def events():
|
def events():
|
||||||
return EventManager()
|
return EventManager()
|
||||||
|
|
||||||
|
|
||||||
def test_event_calling(events):
|
def test_event_calling(events):
|
||||||
called = False
|
called = False
|
||||||
|
|
||||||
|
@ -19,6 +21,7 @@ def test_event_calling(events):
|
||||||
|
|
||||||
assert called == "eggs"
|
assert called == "eggs"
|
||||||
|
|
||||||
|
|
||||||
def test_event_returns(events):
|
def test_event_returns(events):
|
||||||
called = 0
|
called = 0
|
||||||
|
|
||||||
|
@ -39,6 +42,7 @@ def test_event_returns(events):
|
||||||
assert called == 2
|
assert called == 2
|
||||||
assert set(vals) == {1, 2}
|
assert set(vals) == {1, 2}
|
||||||
|
|
||||||
|
|
||||||
def test_validator(events):
|
def test_validator(events):
|
||||||
called = None
|
called = None
|
||||||
|
|
||||||
|
@ -92,6 +96,7 @@ def test_transmogrify(events):
|
||||||
assert len(events.on_test) == 1
|
assert len(events.on_test) == 1
|
||||||
assert inspect.getdoc(events.on_test) == docstring
|
assert inspect.getdoc(events.on_test) == docstring
|
||||||
|
|
||||||
|
|
||||||
def test_transmogrify_by_string(events):
|
def test_transmogrify_by_string(events):
|
||||||
docstring = "Test event"
|
docstring = "Test event"
|
||||||
events.doc('on_test', docstring)
|
events.doc('on_test', docstring)
|
||||||
|
@ -110,6 +115,46 @@ def test_transmogrify_by_string(events):
|
||||||
assert len(events.on_test) == 1
|
assert len(events.on_test) == 1
|
||||||
assert inspect.getdoc(events.on_test) == docstring
|
assert inspect.getdoc(events.on_test) == docstring
|
||||||
|
|
||||||
|
|
||||||
|
def test_load(events):
|
||||||
|
events.transmogrify('on_test', 'LoadEvent')
|
||||||
|
called = 0
|
||||||
|
|
||||||
|
@events.on_test
|
||||||
|
def on_test():
|
||||||
|
nonlocal called
|
||||||
|
called += 1
|
||||||
|
|
||||||
|
assert called == 0
|
||||||
|
|
||||||
|
events.on_test.fire()
|
||||||
|
assert called == 1
|
||||||
|
|
||||||
|
@events.on_test
|
||||||
|
def second():
|
||||||
|
nonlocal called
|
||||||
|
called += 1
|
||||||
|
|
||||||
|
assert called == 2
|
||||||
|
|
||||||
|
def test_load_2nd_call(events):
|
||||||
|
events.transmogrify('on_test', 'LoadEvent')
|
||||||
|
called = 0
|
||||||
|
|
||||||
|
@events.on_test
|
||||||
|
def on_test():
|
||||||
|
nonlocal called
|
||||||
|
called += 1
|
||||||
|
|
||||||
|
assert called == 0
|
||||||
|
|
||||||
|
events.on_test.fire()
|
||||||
|
assert called == 1
|
||||||
|
|
||||||
|
events.on_test.fire()
|
||||||
|
assert called == 1
|
||||||
|
|
||||||
|
|
||||||
def test_typos(xonsh_builtins):
|
def test_typos(xonsh_builtins):
|
||||||
for name, ev in vars(xonsh_builtins.events).items():
|
for name, ev in vars(xonsh_builtins.events).items():
|
||||||
if 'pytest' in name:
|
if 'pytest' in name:
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
#!/usr/bin/env xonsh
|
|
||||||
|
|
||||||
# update the ply repo bundled with xonsh
|
|
||||||
git subtree pull --prefix xonsh/ply https://github.com/dabeaz/ply.git master --squash
|
|
|
@ -144,7 +144,13 @@ class Event(AbstractEvent):
|
||||||
class LoadEvent(AbstractEvent):
|
class LoadEvent(AbstractEvent):
|
||||||
"""
|
"""
|
||||||
An event species where each handler is called exactly once, shortly after either the event is
|
An event species where each handler is called exactly once, shortly after either the event is
|
||||||
fired or the handler is registered (whichever is later).
|
fired or the handler is registered (whichever is later). Additional firings are ignored.
|
||||||
|
|
||||||
|
Note: Does not support scatter/gather, due to never knowing when we have all the handlers.
|
||||||
|
|
||||||
|
Note: Maintains a strong reference to pargs/kwargs in case of the addition of future handlers.
|
||||||
|
|
||||||
|
Note: This is currently NOT thread safe.
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._fired = set()
|
self._fired = set()
|
||||||
|
@ -167,7 +173,11 @@ class LoadEvent(AbstractEvent):
|
||||||
|
|
||||||
This has no effect if the element is already present.
|
This has no effect if the element is already present.
|
||||||
"""
|
"""
|
||||||
self._fired.add(item)
|
if self._hasfired:
|
||||||
|
self._call(item)
|
||||||
|
self._fired.add(item)
|
||||||
|
else:
|
||||||
|
self._unfired.add(item)
|
||||||
|
|
||||||
def discard(self, item):
|
def discard(self, item):
|
||||||
"""
|
"""
|
||||||
|
@ -178,8 +188,22 @@ class LoadEvent(AbstractEvent):
|
||||||
self._fired.discard(item)
|
self._fired.discard(item)
|
||||||
self._unfired.discard(item)
|
self._unfired.discard(item)
|
||||||
|
|
||||||
|
def _call(self, handler):
|
||||||
|
try:
|
||||||
|
handler(*self._pargs, **self._kwargs)
|
||||||
|
except Exception:
|
||||||
|
print_exception("Exception raised in event handler; ignored.")
|
||||||
|
|
||||||
def fire(self, *pargs, **kwargs):
|
def fire(self, *pargs, **kwargs):
|
||||||
raise NotImplementedError("See #1550")
|
if self._hasfired:
|
||||||
|
return
|
||||||
|
self._pargs = pargs
|
||||||
|
self._kwargs = kwargs
|
||||||
|
while self._unfired:
|
||||||
|
handler = self._unfired.pop()
|
||||||
|
self._call(handler)
|
||||||
|
self._hasfired = True
|
||||||
|
return () # Entirely for API compatibility
|
||||||
|
|
||||||
|
|
||||||
class EventManager:
|
class EventManager:
|
||||||
|
|
119
xonsh/main.py
119
xonsh/main.py
|
@ -19,6 +19,57 @@ from xonsh.codecache import run_script_with_cache, run_code_with_cache
|
||||||
from xonsh.xonfig import xonfig_main
|
from xonsh.xonfig import xonfig_main
|
||||||
from xonsh.lazyimps import pygments, pyghooks
|
from xonsh.lazyimps import pygments, pyghooks
|
||||||
from xonsh.imphooks import install_hook
|
from xonsh.imphooks import install_hook
|
||||||
|
from xonsh.events import events
|
||||||
|
|
||||||
|
|
||||||
|
events.transmogrify('on_post_init', 'LoadEvent')
|
||||||
|
events.doc('on_post_init', """
|
||||||
|
on_post_init() -> None
|
||||||
|
|
||||||
|
Fired after all initialization is finished and we're ready to do work.
|
||||||
|
|
||||||
|
NOTE: This is fired before the wizard is automatically started.
|
||||||
|
""")
|
||||||
|
|
||||||
|
events.transmogrify('on_exit', 'LoadEvent')
|
||||||
|
events.doc('on_exit', """
|
||||||
|
on_exit() -> None
|
||||||
|
|
||||||
|
Fired after all commands have been executed, before tear-down occurs.
|
||||||
|
|
||||||
|
NOTE: All the caveats of the atexit module also apply to this event.
|
||||||
|
""")
|
||||||
|
|
||||||
|
|
||||||
|
events.transmogrify('on_pre_cmdloop', 'LoadEvent')
|
||||||
|
events.doc('on_pre_cmdloop', """
|
||||||
|
on_pre_cmdloop() -> None
|
||||||
|
|
||||||
|
Fired just before the command loop is started, if it is.
|
||||||
|
""")
|
||||||
|
|
||||||
|
events.transmogrify('on_post_cmdloop', 'LoadEvent')
|
||||||
|
events.doc('on_post_cmdloop', """
|
||||||
|
on_post_cmdloop() -> None
|
||||||
|
|
||||||
|
Fired just after the command loop finishes, if it is.
|
||||||
|
|
||||||
|
NOTE: All the caveats of the atexit module also apply to this event.
|
||||||
|
""")
|
||||||
|
|
||||||
|
events.transmogrify('on_pre_rc', 'LoadEvent')
|
||||||
|
events.doc('on_pre_rc', """
|
||||||
|
on_pre_rc() -> None
|
||||||
|
|
||||||
|
Fired just before rc files are loaded, if they are.
|
||||||
|
""")
|
||||||
|
|
||||||
|
events.transmogrify('on_post_rc', 'LoadEvent')
|
||||||
|
events.doc('on_post_rc', """
|
||||||
|
on_post_rc() -> None
|
||||||
|
|
||||||
|
Fired just before rc files are loaded, if they are.
|
||||||
|
""")
|
||||||
|
|
||||||
|
|
||||||
def get_setproctitle():
|
def get_setproctitle():
|
||||||
|
@ -206,38 +257,46 @@ def main(argv=None):
|
||||||
if argv is None:
|
if argv is None:
|
||||||
argv = sys.argv[1:]
|
argv = sys.argv[1:]
|
||||||
args = premain(argv)
|
args = premain(argv)
|
||||||
|
events.on_post_init.fire()
|
||||||
env = builtins.__xonsh_env__
|
env = builtins.__xonsh_env__
|
||||||
shell = builtins.__xonsh_shell__
|
shell = builtins.__xonsh_shell__
|
||||||
if args.mode == XonshMode.interactive:
|
try:
|
||||||
# enter the shell
|
if args.mode == XonshMode.interactive:
|
||||||
env['XONSH_INTERACTIVE'] = True
|
# enter the shell
|
||||||
ignore_sigtstp()
|
env['XONSH_INTERACTIVE'] = True
|
||||||
if (env['XONSH_INTERACTIVE'] and
|
ignore_sigtstp()
|
||||||
not env['LOADED_CONFIG'] and
|
if (env['XONSH_INTERACTIVE'] and
|
||||||
not any(os.path.isfile(i) for i in env['XONSHRC'])):
|
not env['LOADED_CONFIG'] and
|
||||||
print('Could not find xonsh configuration or run control files.',
|
not any(os.path.isfile(i) for i in env['XONSHRC'])):
|
||||||
file=sys.stderr)
|
print('Could not find xonsh configuration or run control files.',
|
||||||
xonfig_main(['wizard', '--confirm'])
|
file=sys.stderr)
|
||||||
shell.shell.cmdloop()
|
xonfig_main(['wizard', '--confirm'])
|
||||||
elif args.mode == XonshMode.single_command:
|
events.on_pre_cmdloop.fire()
|
||||||
# run a single command and exit
|
try:
|
||||||
run_code_with_cache(args.command.lstrip(), shell.execer, mode='single')
|
shell.shell.cmdloop()
|
||||||
elif args.mode == XonshMode.script_from_file:
|
finally:
|
||||||
# run a script contained in a file
|
events.on_post_cmdloop.fire()
|
||||||
path = os.path.abspath(os.path.expanduser(args.file))
|
elif args.mode == XonshMode.single_command:
|
||||||
if os.path.isfile(path):
|
# run a single command and exit
|
||||||
sys.argv = [args.file] + args.args
|
run_code_with_cache(args.command.lstrip(), shell.execer, mode='single')
|
||||||
env['ARGS'] = sys.argv[:] # $ARGS is not sys.argv
|
elif args.mode == XonshMode.script_from_file:
|
||||||
env['XONSH_SOURCE'] = path
|
# run a script contained in a file
|
||||||
run_script_with_cache(args.file, shell.execer, glb=shell.ctx,
|
path = os.path.abspath(os.path.expanduser(args.file))
|
||||||
loc=None, mode='exec')
|
if os.path.isfile(path):
|
||||||
else:
|
sys.argv = [args.file] + args.args
|
||||||
print('xonsh: {0}: No such file or directory.'.format(args.file))
|
env['ARGS'] = sys.argv[:] # $ARGS is not sys.argv
|
||||||
elif args.mode == XonshMode.script_from_stdin:
|
env['XONSH_SOURCE'] = path
|
||||||
# run a script given on stdin
|
run_script_with_cache(args.file, shell.execer, glb=shell.ctx,
|
||||||
code = sys.stdin.read()
|
loc=None, mode='exec')
|
||||||
run_code_with_cache(code, shell.execer, glb=shell.ctx, loc=None,
|
else:
|
||||||
mode='exec')
|
print('xonsh: {0}: No such file or directory.'.format(args.file))
|
||||||
|
elif args.mode == XonshMode.script_from_stdin:
|
||||||
|
# run a script given on stdin
|
||||||
|
code = sys.stdin.read()
|
||||||
|
run_code_with_cache(code, shell.execer, glb=shell.ctx, loc=None,
|
||||||
|
mode='exec')
|
||||||
|
finally:
|
||||||
|
events.on_exit.fire()
|
||||||
postmain(args)
|
postmain(args)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,15 @@ from xonsh.ptk.completer import PromptToolkitCompleter
|
||||||
from xonsh.ptk.history import PromptToolkitHistory
|
from xonsh.ptk.history import PromptToolkitHistory
|
||||||
from xonsh.ptk.key_bindings import load_xonsh_bindings
|
from xonsh.ptk.key_bindings import load_xonsh_bindings
|
||||||
from xonsh.ptk.shortcuts import Prompter
|
from xonsh.ptk.shortcuts import Prompter
|
||||||
|
from xonsh.events import events
|
||||||
|
|
||||||
|
|
||||||
|
events.transmogrify('on_ptk_create', 'LoadEvent')
|
||||||
|
events.doc('on_ptk_create', """
|
||||||
|
on_ptk_create(prompter: Prompter, history: PromptToolkitHistory, completer: PromptToolkitCompleter, bindings: KeyBindingManager) ->
|
||||||
|
|
||||||
|
Fired after prompt toolkit has been initialized
|
||||||
|
""")
|
||||||
|
|
||||||
|
|
||||||
class PromptToolkitShell(BaseShell):
|
class PromptToolkitShell(BaseShell):
|
||||||
|
@ -37,6 +46,8 @@ class PromptToolkitShell(BaseShell):
|
||||||
}
|
}
|
||||||
self.key_bindings_manager = KeyBindingManager(**key_bindings_manager_args)
|
self.key_bindings_manager = KeyBindingManager(**key_bindings_manager_args)
|
||||||
load_xonsh_bindings(self.key_bindings_manager)
|
load_xonsh_bindings(self.key_bindings_manager)
|
||||||
|
# This assumes that PromptToolkitShell is a singleton
|
||||||
|
events.on_ptk_create.fire(self.prompter, self.history, self.pt_completer, self.key_bindings_manager)
|
||||||
|
|
||||||
def singleline(self, store_in_history=True, auto_suggest=None,
|
def singleline(self, store_in_history=True, auto_suggest=None,
|
||||||
enable_history_search=True, multiline=True, **kwargs):
|
enable_history_search=True, multiline=True, **kwargs):
|
||||||
|
|
|
@ -148,5 +148,7 @@ class Shell(object):
|
||||||
# load run control files
|
# load run control files
|
||||||
env = builtins.__xonsh_env__
|
env = builtins.__xonsh_env__
|
||||||
rc = env.get('XONSHRC') if rc is None else rc
|
rc = env.get('XONSHRC') if rc is None else rc
|
||||||
|
events.on_pre_rc.fire()
|
||||||
self.ctx.update(xonshrc_context(rcfiles=rc, execer=self.execer, initial=self.ctx))
|
self.ctx.update(xonshrc_context(rcfiles=rc, execer=self.execer, initial=self.ctx))
|
||||||
|
events.on_post_rc.fire()
|
||||||
self.ctx['__name__'] = '__main__'
|
self.ctx['__name__'] = '__main__'
|
||||||
|
|
Loading…
Add table
Reference in a new issue