mirror of
https://github.com/xonsh/xonsh.git
synced 2025-03-06 01:10:57 +01:00
Merge branch 'msubnest' into mcont
This commit is contained in:
commit
00afc3aff0
14 changed files with 318 additions and 68 deletions
21
news/history-api.rst
Normal file
21
news/history-api.rst
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
**Added:**
|
||||||
|
|
||||||
|
* ``History`` methods ``__iter__`` and ``__getitem__``
|
||||||
|
|
||||||
|
* ``tools.get_portions`` that yields parts of an iterable
|
||||||
|
|
||||||
|
**Changed:**
|
||||||
|
|
||||||
|
* ``_curr_session_parser`` now iterates over ``History``
|
||||||
|
|
||||||
|
**Deprecated:** None
|
||||||
|
|
||||||
|
**Removed:**
|
||||||
|
|
||||||
|
* ``History`` method ``show``
|
||||||
|
|
||||||
|
* ``_hist_get_portion`` in favor of ``tools.get_portions``
|
||||||
|
|
||||||
|
**Fixed:** None
|
||||||
|
|
||||||
|
**Security:** None
|
14
news/ptk-completion-display.rst
Normal file
14
news/ptk-completion-display.rst
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
**Added:** None
|
||||||
|
|
||||||
|
**Changed:**
|
||||||
|
|
||||||
|
* ``prompt_toolkit`` completions now only show the rightmost portion
|
||||||
|
of a given completion in the dropdown
|
||||||
|
|
||||||
|
**Deprecated:** None
|
||||||
|
|
||||||
|
**Removed:** None
|
||||||
|
|
||||||
|
**Fixed:** None
|
||||||
|
|
||||||
|
**Security:** None
|
13
news/test_xsh.rst
Normal file
13
news/test_xsh.rst
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
**Added:**
|
||||||
|
|
||||||
|
* Added a py.test plugin to collect `test_*.xsh` files and run `test_*()` functions.
|
||||||
|
|
||||||
|
**Changed:** None
|
||||||
|
|
||||||
|
**Deprecated:** None
|
||||||
|
|
||||||
|
**Removed:** None
|
||||||
|
|
||||||
|
**Fixed:** None
|
||||||
|
|
||||||
|
**Security:** None
|
3
setup.py
3
setup.py
|
@ -310,7 +310,8 @@ def main():
|
||||||
skw['entry_points'] = {
|
skw['entry_points'] = {
|
||||||
'pygments.lexers': ['xonsh = xonsh.pyghooks:XonshLexer',
|
'pygments.lexers': ['xonsh = xonsh.pyghooks:XonshLexer',
|
||||||
'xonshcon = xonsh.pyghooks:XonshConsoleLexer'],
|
'xonshcon = xonsh.pyghooks:XonshConsoleLexer'],
|
||||||
}
|
'pytest11': ['xonsh = xonsh.pytest_plugin']
|
||||||
|
}
|
||||||
skw['cmdclass']['develop'] = xdevelop
|
skw['cmdclass']['develop'] = xdevelop
|
||||||
setup(**skw)
|
setup(**skw)
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
|
import glob
|
||||||
import builtins
|
import builtins
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from tools import DummyShell, sp
|
|
||||||
import xonsh.built_ins
|
import xonsh.built_ins
|
||||||
|
|
||||||
from xonsh.built_ins import ensure_list_of_strs
|
from xonsh.built_ins import ensure_list_of_strs
|
||||||
from xonsh.execer import Execer
|
from xonsh.execer import Execer
|
||||||
from xonsh.tools import XonshBlockError
|
from xonsh.tools import XonshBlockError
|
||||||
from xonsh.events import events
|
from xonsh.events import events
|
||||||
import glob
|
|
||||||
|
from tools import DummyShell, sp
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
|
@ -68,24 +68,24 @@ def test_cmd_field(hist, xonsh_builtins):
|
||||||
assert None == hist.outs[-1]
|
assert None == hist.outs[-1]
|
||||||
|
|
||||||
|
|
||||||
cmds = ['ls', 'cat hello kitty', 'abc', 'def', 'touch me', 'grep from me']
|
CMDS = ['ls', 'cat hello kitty', 'abc', 'def', 'touch me', 'grep from me']
|
||||||
|
|
||||||
@pytest.mark.parametrize('inp, commands, offset', [
|
@pytest.mark.parametrize('inp, commands, offset', [
|
||||||
('', cmds, (0, 1)),
|
('', CMDS, (0, 1)),
|
||||||
('-r', list(reversed(cmds)), (len(cmds)- 1, -1)),
|
('-r', list(reversed(CMDS)), (len(CMDS)- 1, -1)),
|
||||||
('0', cmds[0:1], (0, 1)),
|
('0', CMDS[0:1], (0, 1)),
|
||||||
('1', cmds[1:2], (1, 1)),
|
('1', CMDS[1:2], (1, 1)),
|
||||||
('-2', cmds[-2:-1], (len(cmds) -2 , 1)),
|
('-2', CMDS[-2:-1], (len(CMDS) -2 , 1)),
|
||||||
('1:3', cmds[1:3], (1, 1)),
|
('1:3', CMDS[1:3], (1, 1)),
|
||||||
('1::2', cmds[1::2], (1, 2)),
|
('1::2', CMDS[1::2], (1, 2)),
|
||||||
('-4:-2', cmds[-4:-2], (len(cmds) - 4, 1))
|
('-4:-2', CMDS[-4:-2], (len(CMDS) - 4, 1))
|
||||||
])
|
])
|
||||||
def test_show_cmd_numerate(inp, commands, offset, hist, xonsh_builtins, capsys):
|
def test_show_cmd_numerate(inp, commands, offset, hist, xonsh_builtins, capsys):
|
||||||
"""Verify that CLI history commands work."""
|
"""Verify that CLI history commands work."""
|
||||||
base_idx, step = offset
|
base_idx, step = offset
|
||||||
xonsh_builtins.__xonsh_history__ = hist
|
xonsh_builtins.__xonsh_history__ = hist
|
||||||
xonsh_builtins.__xonsh_env__['HISTCONTROL'] = set()
|
xonsh_builtins.__xonsh_env__['HISTCONTROL'] = set()
|
||||||
for ts,cmd in enumerate(cmds): # populate the shell history
|
for ts,cmd in enumerate(CMDS): # populate the shell history
|
||||||
hist.append({'inp': cmd, 'rtn': 0, 'ts':(ts+1, ts+1.5)})
|
hist.append({'inp': cmd, 'rtn': 0, 'ts':(ts+1, ts+1.5)})
|
||||||
|
|
||||||
exp = ('{}: {}'.format(base_idx + idx * step, cmd)
|
exp = ('{}: {}'.format(base_idx + idx * step, cmd)
|
||||||
|
@ -185,3 +185,19 @@ def test_parser_show(args, exp):
|
||||||
'timestamp': False}
|
'timestamp': False}
|
||||||
ns = _hist_parse_args(shlex.split(args))
|
ns = _hist_parse_args(shlex.split(args))
|
||||||
assert ns.__dict__ == exp_ns
|
assert ns.__dict__ == exp_ns
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('index, exp', [
|
||||||
|
(-1, 'grep from me'),
|
||||||
|
('hello', 'cat hello kitty'),
|
||||||
|
((-1, -1), 'me'),
|
||||||
|
(('hello', 0), 'cat'),
|
||||||
|
((-1, slice(0,2)), 'grep from'),
|
||||||
|
(('kitty', slice(1,3)), 'hello kitty')
|
||||||
|
])
|
||||||
|
def test_history_getitem(index, exp, hist, xonsh_builtins):
|
||||||
|
xonsh_builtins.__xonsh_env__['HISTCONTROL'] = set()
|
||||||
|
for ts,cmd in enumerate(CMDS): # populate the shell history
|
||||||
|
hist.append({'inp': cmd, 'rtn': 0, 'ts':(ts+1, ts+1.5)})
|
||||||
|
|
||||||
|
assert hist[index] == exp
|
||||||
|
|
|
@ -24,7 +24,7 @@ from xonsh.tools import (
|
||||||
to_dynamic_cwd_tuple, to_logfile_opt, pathsep_to_set, set_to_pathsep,
|
to_dynamic_cwd_tuple, to_logfile_opt, pathsep_to_set, set_to_pathsep,
|
||||||
is_string_seq, pathsep_to_seq, seq_to_pathsep, is_nonstring_seq_of_strings,
|
is_string_seq, pathsep_to_seq, seq_to_pathsep, is_nonstring_seq_of_strings,
|
||||||
pathsep_to_upper_seq, seq_to_upper_pathsep, expandvars, is_int_as_str, is_slice_as_str,
|
pathsep_to_upper_seq, seq_to_upper_pathsep, expandvars, is_int_as_str, is_slice_as_str,
|
||||||
ensure_timestamp,
|
ensure_timestamp, get_portions
|
||||||
)
|
)
|
||||||
from xonsh.commands_cache import CommandsCache
|
from xonsh.commands_cache import CommandsCache
|
||||||
from xonsh.built_ins import expand_path
|
from xonsh.built_ins import expand_path
|
||||||
|
@ -832,6 +832,7 @@ def test_bool_or_int_to_str(inp, exp):
|
||||||
|
|
||||||
@pytest.mark.parametrize('inp, exp', [
|
@pytest.mark.parametrize('inp, exp', [
|
||||||
(42, slice(42, 43)),
|
(42, slice(42, 43)),
|
||||||
|
(0, slice(0, 1)),
|
||||||
(None, slice(None, None, None)),
|
(None, slice(None, None, None)),
|
||||||
(slice(1,2), slice(1,2)),
|
(slice(1,2), slice(1,2)),
|
||||||
('-1', slice(-1, None, None)),
|
('-1', slice(-1, None, None)),
|
||||||
|
@ -851,6 +852,21 @@ def test_ensure_slice(inp, exp):
|
||||||
assert exp == obs
|
assert exp == obs
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('inp, exp', [
|
||||||
|
((range(50), slice(25, 40)),
|
||||||
|
list(i for i in range(25,40))),
|
||||||
|
|
||||||
|
(([1,2,3,4,5,6,7,8,9,10], [slice(1,4), slice(6, None)]),
|
||||||
|
[2, 3, 4, 7, 8, 9, 10]),
|
||||||
|
|
||||||
|
(([1,2,3,4,5], [slice(-2, None), slice(-5, -3)]),
|
||||||
|
[4, 5, 1, 2]),
|
||||||
|
])
|
||||||
|
def test_get_portions(inp, exp):
|
||||||
|
obs = get_portions(*inp)
|
||||||
|
assert list(obs) == exp
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('inp', [
|
@pytest.mark.parametrize('inp', [
|
||||||
'42.3',
|
'42.3',
|
||||||
'3:asd5:1',
|
'3:asd5:1',
|
||||||
|
|
18
tests/test_xonsh.xsh
Normal file
18
tests/test_xonsh.xsh
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
|
||||||
|
|
||||||
|
def test_simple():
|
||||||
|
assert 1 + 1 == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_envionment():
|
||||||
|
$USER = 'snail'
|
||||||
|
x = 'USER'
|
||||||
|
assert x in ${...}
|
||||||
|
assert ${'U' + 'SER'} == 'snail'
|
||||||
|
|
||||||
|
|
||||||
|
def test_xonsh_party():
|
||||||
|
x = 'xonsh'
|
||||||
|
y = 'party'
|
||||||
|
out = $(echo @(x + ' ' + y))
|
||||||
|
assert out == 'xonsh party\n'
|
|
@ -1,7 +1,7 @@
|
||||||
__version__ = '0.4.5'
|
__version__ = '0.4.5'
|
||||||
|
|
||||||
# amalgamate exclude jupyter_kernel parser_table parser_test_table pyghooks
|
# amalgamate exclude jupyter_kernel parser_table parser_test_table pyghooks
|
||||||
# amalgamate exclude winutils wizard
|
# amalgamate exclude winutils wizard pytest_plugin
|
||||||
import os as _os
|
import os as _os
|
||||||
if _os.getenv('XONSH_DEBUG', ''):
|
if _os.getenv('XONSH_DEBUG', ''):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -186,12 +186,11 @@ def _splitpath(path):
|
||||||
|
|
||||||
def _splitpath_helper(path, sofar=()):
|
def _splitpath_helper(path, sofar=()):
|
||||||
folder, path = os.path.split(path)
|
folder, path = os.path.split(path)
|
||||||
if path == "":
|
if path:
|
||||||
|
sofar = sofar + (path, )
|
||||||
|
if not folder or folder == xt.get_sep():
|
||||||
return sofar[::-1]
|
return sofar[::-1]
|
||||||
elif folder == "":
|
return _splitpath_helper(folder, sofar)
|
||||||
return (sofar + (path, ))[::-1]
|
|
||||||
else:
|
|
||||||
return _splitpath_helper(folder, sofar + (path, ))
|
|
||||||
|
|
||||||
|
|
||||||
def subsequence_match(ref, typed, csc):
|
def subsequence_match(ref, typed, csc):
|
||||||
|
|
129
xonsh/history.py
129
xonsh/history.py
|
@ -8,17 +8,16 @@ import time
|
||||||
import uuid
|
import uuid
|
||||||
import argparse
|
import argparse
|
||||||
import builtins
|
import builtins
|
||||||
|
import collections
|
||||||
import datetime
|
import datetime
|
||||||
import functools
|
import functools
|
||||||
import itertools
|
|
||||||
import threading
|
import threading
|
||||||
import collections
|
|
||||||
import collections.abc as cabc
|
import collections.abc as cabc
|
||||||
|
|
||||||
from xonsh.lazyasd import lazyobject
|
from xonsh.lazyasd import lazyobject
|
||||||
from xonsh.lazyjson import LazyJSON, ljdump, LJNode
|
from xonsh.lazyjson import LazyJSON, ljdump, LJNode
|
||||||
from xonsh.tools import (ensure_slice, to_history_tuple,
|
from xonsh.tools import (ensure_slice, to_history_tuple, is_string,
|
||||||
expanduser_abs_path, ensure_timestamp)
|
get_portions, expanduser_abs_path, ensure_timestamp)
|
||||||
from xonsh.diff_history import _dh_create_parser, _dh_main_action
|
from xonsh.diff_history import _dh_create_parser, _dh_main_action
|
||||||
|
|
||||||
|
|
||||||
|
@ -262,7 +261,7 @@ def _all_xonsh_parser(**kwargs):
|
||||||
"""
|
"""
|
||||||
Returns all history as found in XONSH_DATA_DIR.
|
Returns all history as found in XONSH_DATA_DIR.
|
||||||
|
|
||||||
return format: (name, start_time, index)
|
return format: (cmd, start_time, index)
|
||||||
"""
|
"""
|
||||||
data_dir = builtins.__xonsh_env__.get('XONSH_DATA_DIR')
|
data_dir = builtins.__xonsh_env__.get('XONSH_DATA_DIR')
|
||||||
data_dir = expanduser_abs_path(data_dir)
|
data_dir = expanduser_abs_path(data_dir)
|
||||||
|
@ -285,14 +284,11 @@ def _all_xonsh_parser(**kwargs):
|
||||||
def _curr_session_parser(hist=None, **kwargs):
|
def _curr_session_parser(hist=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Take in History object and return command list tuple with
|
Take in History object and return command list tuple with
|
||||||
format: (name, start_time, index)
|
format: (cmd, start_time, index)
|
||||||
"""
|
"""
|
||||||
if hist is None:
|
if hist is None:
|
||||||
hist = builtins.__xonsh_history__
|
hist = builtins.__xonsh_history__
|
||||||
start_times = (start for start, end in hist.tss)
|
return iter(hist)
|
||||||
names = (name.rstrip() for name in hist.inps)
|
|
||||||
for ind, (c, t) in enumerate(zip(names, start_times)):
|
|
||||||
yield (c, t, ind)
|
|
||||||
|
|
||||||
|
|
||||||
def _zsh_hist_parser(location=None, **kwargs):
|
def _zsh_hist_parser(location=None, **kwargs):
|
||||||
|
@ -394,20 +390,6 @@ def _hist_create_parser():
|
||||||
return p
|
return p
|
||||||
|
|
||||||
|
|
||||||
def _hist_get_portion(commands, slices):
|
|
||||||
"""Yield from portions of history commands."""
|
|
||||||
if len(slices) == 1:
|
|
||||||
s = slices[0]
|
|
||||||
try:
|
|
||||||
yield from itertools.islice(commands, s.start, s.stop, s.step)
|
|
||||||
return
|
|
||||||
except ValueError: # islice failed
|
|
||||||
pass
|
|
||||||
commands = list(commands)
|
|
||||||
for s in slices:
|
|
||||||
yield from commands[s]
|
|
||||||
|
|
||||||
|
|
||||||
def _hist_filter_ts(commands, start_time, end_time):
|
def _hist_filter_ts(commands, start_time, end_time):
|
||||||
"""Yield only the commands between start and end time."""
|
"""Yield only the commands between start and end time."""
|
||||||
for cmd in commands:
|
for cmd in commands:
|
||||||
|
@ -439,7 +421,7 @@ def _hist_get(session='session', *, slices=None, datetime_format=None,
|
||||||
if slices:
|
if slices:
|
||||||
# transform/check all slices
|
# transform/check all slices
|
||||||
slices = [ensure_slice(s) for s in slices]
|
slices = [ensure_slice(s) for s in slices]
|
||||||
cmds = _hist_get_portion(cmds, slices)
|
cmds = get_portions(cmds, slices)
|
||||||
if start_time or end_time:
|
if start_time or end_time:
|
||||||
if start_time is None:
|
if start_time is None:
|
||||||
start_time = 0.0
|
start_time = 0.0
|
||||||
|
@ -488,6 +470,37 @@ def _hist_show(ns, *args, **kwargs):
|
||||||
class History(object):
|
class History(object):
|
||||||
"""Xonsh session history.
|
"""Xonsh session history.
|
||||||
|
|
||||||
|
Indexing
|
||||||
|
--------
|
||||||
|
History object acts like a sequence that can be indexed in a special way
|
||||||
|
that adds extra functionality. At the moment only history from the
|
||||||
|
current session can be retrieved. Note that the most recent command
|
||||||
|
is the last item in history.
|
||||||
|
|
||||||
|
The index acts as a filter with two parts, command and argument,
|
||||||
|
separated by comma. Based on the type of each part different
|
||||||
|
filtering can be achieved,
|
||||||
|
|
||||||
|
for the command part:
|
||||||
|
|
||||||
|
- an int returns the command in that position.
|
||||||
|
- a slice returns a list of commands.
|
||||||
|
- a string returns the most recent command containing the string.
|
||||||
|
|
||||||
|
for the argument part:
|
||||||
|
|
||||||
|
- an int returns the argument of the command in that position.
|
||||||
|
- a slice returns a part of the command based on the argument
|
||||||
|
position.
|
||||||
|
|
||||||
|
The argument part of the filter can be omitted but the command part is
|
||||||
|
required.
|
||||||
|
|
||||||
|
Command arguments are separated by white space.
|
||||||
|
|
||||||
|
If the filtering produces only one result it is
|
||||||
|
returned as a string else a list of strings is returned.
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
----------
|
----------
|
||||||
rtns : sequence of ints
|
rtns : sequence of ints
|
||||||
|
@ -605,16 +618,66 @@ class History(object):
|
||||||
self.buffer.clear()
|
self.buffer.clear()
|
||||||
return hf
|
return hf
|
||||||
|
|
||||||
def show(self, *args, **kwargs):
|
def __iter__(self):
|
||||||
"""Return shell history as a list
|
"""Get current session history.
|
||||||
|
|
||||||
Valid options:
|
Yields
|
||||||
`session` - returns xonsh history from current session
|
------
|
||||||
`xonsh` - returns xonsh history from all sessions
|
tuple
|
||||||
`zsh` - returns all zsh history
|
``tuple`` of the form (cmd, start_time, index).
|
||||||
`bash` - returns all bash history
|
|
||||||
"""
|
"""
|
||||||
return list(_hist_get(*args, **kwargs))
|
start_times = (start for start, end in self.tss)
|
||||||
|
names = (name.rstrip() for name in self.inps)
|
||||||
|
for ind, (c, t) in enumerate(zip(names, start_times)):
|
||||||
|
yield (c, t, ind)
|
||||||
|
|
||||||
|
def __getitem__(self, item):
|
||||||
|
"""Retrieve history parts based on filtering rules,
|
||||||
|
see ``History`` docs for more info. Accepts one of
|
||||||
|
int, string, slice or tuple of length two.
|
||||||
|
"""
|
||||||
|
if isinstance(item, tuple):
|
||||||
|
cmd_pat, arg_pat = item
|
||||||
|
else:
|
||||||
|
cmd_pat, arg_pat = item, None
|
||||||
|
cmds = (c for c, *_ in self)
|
||||||
|
cmds = self._cmd_filter(cmds, cmd_pat)
|
||||||
|
if arg_pat is not None:
|
||||||
|
cmds = self._args_filter(cmds, arg_pat)
|
||||||
|
cmds = list(cmds)
|
||||||
|
if len(cmds) == 1:
|
||||||
|
return cmds[0]
|
||||||
|
else:
|
||||||
|
return cmds
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _cmd_filter(cmds, pat):
|
||||||
|
if isinstance(pat, (int, slice)):
|
||||||
|
s = ensure_slice(pat)
|
||||||
|
yield from get_portions(cmds, s)
|
||||||
|
elif is_string(pat):
|
||||||
|
for command in reversed(list(cmds)):
|
||||||
|
if pat in command:
|
||||||
|
yield command
|
||||||
|
else:
|
||||||
|
raise TypeError('Command filter must be '
|
||||||
|
'string, int or slice')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _args_filter(cmds, pat):
|
||||||
|
args = None
|
||||||
|
if isinstance(pat, (int, slice)):
|
||||||
|
s = ensure_slice(pat)
|
||||||
|
for command in cmds:
|
||||||
|
yield ' '.join(command.split()[s])
|
||||||
|
else:
|
||||||
|
raise TypeError('Argument filter must be '
|
||||||
|
'int or slice')
|
||||||
|
return args
|
||||||
|
|
||||||
|
def __setitem__(self, *args):
|
||||||
|
raise PermissionError('You cannot change history! '
|
||||||
|
'you can create new though.')
|
||||||
|
|
||||||
|
|
||||||
def _hist_info(ns, hist):
|
def _hist_info(ns, hist):
|
||||||
|
|
|
@ -4,7 +4,7 @@ import os
|
||||||
import builtins
|
import builtins
|
||||||
|
|
||||||
from prompt_toolkit.layout.dimension import LayoutDimension
|
from prompt_toolkit.layout.dimension import LayoutDimension
|
||||||
from prompt_toolkit.completion import Completer, Completion
|
from prompt_toolkit.completion import Completer, Completion, _commonprefix
|
||||||
|
|
||||||
|
|
||||||
class PromptToolkitCompleter(Completer):
|
class PromptToolkitCompleter(Completer):
|
||||||
|
@ -40,17 +40,13 @@ class PromptToolkitCompleter(Completer):
|
||||||
pass
|
pass
|
||||||
elif len(os.path.commonprefix(completions)) <= len(prefix):
|
elif len(os.path.commonprefix(completions)) <= len(prefix):
|
||||||
self.reserve_space()
|
self.reserve_space()
|
||||||
# don't mess with envvar and path expansion comps
|
c_prefix = _commonprefix([a.strip('\'/').rsplit('/', 1)[0]
|
||||||
if any(x in prefix for x in ['$', '/']):
|
for a in completions])
|
||||||
for comp in completions:
|
for comp in completions:
|
||||||
yield Completion(comp, -l)
|
if comp.endswith('/') and not c_prefix.startswith('/'):
|
||||||
else: # don't show common prefixes in attr completions
|
c_prefix = ''
|
||||||
prefix, _, compprefix = prefix.rpartition('.')
|
display = comp[len(c_prefix):].lstrip('/')
|
||||||
for comp in completions:
|
yield Completion(comp, -l, display=display)
|
||||||
if comp.rsplit('.', 1)[0] in prefix:
|
|
||||||
comp = comp.rsplit('.', 1)[-1]
|
|
||||||
l = len(compprefix) if compprefix in comp else 0
|
|
||||||
yield Completion(comp, -l)
|
|
||||||
|
|
||||||
def reserve_space(self):
|
def reserve_space(self):
|
||||||
cli = builtins.__xonsh_shell__.shell.prompter.cli
|
cli = builtins.__xonsh_shell__.shell.prompter.cli
|
||||||
|
|
66
xonsh/pytest_plugin.py
Normal file
66
xonsh/pytest_plugin.py
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Pytest plugin for testing xsh files."""
|
||||||
|
import sys
|
||||||
|
import importlib
|
||||||
|
from traceback import format_list, extract_tb
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from xonsh.imphooks import install_hook
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_configure(config):
|
||||||
|
install_hook()
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_collection_modifyitems(items):
|
||||||
|
items.sort(key=lambda x: 0 if isinstance(x, XshFunction) else 1)
|
||||||
|
|
||||||
|
|
||||||
|
def _limited_traceback(excinfo):
|
||||||
|
""" Return a formatted traceback with all the stack
|
||||||
|
from this frame (i.e __file__) up removed
|
||||||
|
"""
|
||||||
|
tb = extract_tb(excinfo.tb)
|
||||||
|
try:
|
||||||
|
idx = [__file__ in e for e in tb].index(True)
|
||||||
|
return format_list(tb[idx+1:])
|
||||||
|
except ValueError:
|
||||||
|
return format_list(tb)
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_collect_file(parent, path):
|
||||||
|
if path.ext.lower() == ".xsh" and path.basename.startswith("test_"):
|
||||||
|
return XshFile(path, parent)
|
||||||
|
|
||||||
|
|
||||||
|
class XshFile(pytest.File):
|
||||||
|
def collect(self):
|
||||||
|
sys.path.append(self.fspath.dirname)
|
||||||
|
mod = importlib.import_module(self.fspath.purebasename)
|
||||||
|
sys.path.pop(0)
|
||||||
|
tests = [t for t in dir(mod) if t.startswith('test_')]
|
||||||
|
for test_name in tests:
|
||||||
|
obj = getattr(mod, test_name)
|
||||||
|
if hasattr(obj, '__call__'):
|
||||||
|
yield XshFunction(name=test_name, parent=self,
|
||||||
|
test_func=obj, test_module=mod)
|
||||||
|
|
||||||
|
|
||||||
|
class XshFunction(pytest.Item):
|
||||||
|
def __init__(self, name, parent, test_func, test_module):
|
||||||
|
super().__init__(name, parent)
|
||||||
|
self._test_func = test_func
|
||||||
|
self._test_module = test_module
|
||||||
|
|
||||||
|
def runtest(self):
|
||||||
|
self._test_func()
|
||||||
|
|
||||||
|
def repr_failure(self, excinfo):
|
||||||
|
""" called when self.runtest() raises an exception. """
|
||||||
|
formatted_tb = _limited_traceback(excinfo)
|
||||||
|
formatted_tb.insert(0, "xonsh execution failed\n")
|
||||||
|
formatted_tb.append('{}: {}'.format(excinfo.type.__name__, excinfo.value))
|
||||||
|
return "".join(formatted_tb)
|
||||||
|
|
||||||
|
def reportinfo(self):
|
||||||
|
return self.fspath, 0, "xonsh test: {}".format(self.name)
|
|
@ -25,6 +25,7 @@ import ctypes
|
||||||
import datetime
|
import datetime
|
||||||
import functools
|
import functools
|
||||||
import glob
|
import glob
|
||||||
|
import itertools
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
|
@ -939,14 +940,14 @@ def SLICE_REG():
|
||||||
|
|
||||||
def ensure_slice(x):
|
def ensure_slice(x):
|
||||||
"""Try to convert an object into a slice, complain on failure"""
|
"""Try to convert an object into a slice, complain on failure"""
|
||||||
if not x:
|
if not x and x != 0:
|
||||||
return slice(None)
|
return slice(None)
|
||||||
elif isinstance(x, slice):
|
elif is_slice(x):
|
||||||
return x
|
return x
|
||||||
try:
|
try:
|
||||||
x = int(x)
|
x = int(x)
|
||||||
if x != -1:
|
if x != -1:
|
||||||
s = slice(x, x+1)
|
s = slice(x, x + 1)
|
||||||
else:
|
else:
|
||||||
s = slice(-1, None, None)
|
s = slice(-1, None, None)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
@ -965,6 +966,28 @@ def ensure_slice(x):
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def get_portions(it, slices):
|
||||||
|
"""Yield from portions of an iterable.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
it: iterable
|
||||||
|
slices: a slice or a list of slice objects
|
||||||
|
"""
|
||||||
|
if is_slice(slices):
|
||||||
|
slices = [slices]
|
||||||
|
if len(slices) == 1:
|
||||||
|
s = slices[0]
|
||||||
|
try:
|
||||||
|
yield from itertools.islice(it, s.start, s.stop, s.step)
|
||||||
|
return
|
||||||
|
except ValueError: # islice failed
|
||||||
|
pass
|
||||||
|
it = list(it)
|
||||||
|
for s in slices:
|
||||||
|
yield from it[s]
|
||||||
|
|
||||||
|
|
||||||
def is_slice_as_str(x):
|
def is_slice_as_str(x):
|
||||||
"""
|
"""
|
||||||
Test if string x is a slice. If not a string return False.
|
Test if string x is a slice. If not a string return False.
|
||||||
|
|
Loading…
Add table
Reference in a new issue