mirror of
https://github.com/xonsh/xonsh.git
synced 2025-03-05 17:00:58 +01:00
Merge branch 'master' of https://github.com/scopatz/xonsh
This commit is contained in:
commit
0d71b85744
28 changed files with 422 additions and 344 deletions
21
.binstar.yml
21
.binstar.yml
|
@ -1,21 +0,0 @@
|
|||
package: xonsh
|
||||
|
||||
platform:
|
||||
- linux-64
|
||||
|
||||
engine:
|
||||
- python=3.5
|
||||
- python=3.4
|
||||
|
||||
|
||||
script:
|
||||
- conda build recipe
|
||||
|
||||
after_success:
|
||||
- conda convert -p all -f /opt/miniconda/conda-bld/linux-64/xonsh-*.tar.bz2 -o /opt/miniconda/conda-bld
|
||||
|
||||
after_failure:
|
||||
- echo "Build failed!"
|
||||
|
||||
build_targets:
|
||||
- /opt/miniconda/conda-bld/*/*.tar.bz2
|
|
@ -8,8 +8,8 @@ xonsh
|
|||
.. image:: https://travis-ci.org/xonsh/xonsh.svg?branch=master
|
||||
:target: https://travis-ci.org/xonsh/xonsh
|
||||
|
||||
.. image:: https://ci.appveyor.com/api/projects/status/ufqtigii8ma3rctt/branch/master?svg=true
|
||||
:target: https://ci.appveyor.com/project/rbrewer123/xonsh-unq93
|
||||
.. image:: https://ci.appveyor.com/api/projects/status/github/xonsh/xonsh?svg=true
|
||||
:target: https://ci.appveyor.com/project/xonsh/xonsh
|
||||
|
||||
.. image:: https://landscape.io/github/xonsh/xonsh/master/landscape.svg?style=flat
|
||||
:target: https://landscape.io/github/xonsh/xonsh/master
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
.binstar.yml
|
|
@ -14,8 +14,6 @@ You can install xonsh using ``conda``, ``pip``, or from source.
|
|||
$ conda config --add channels conda-forge
|
||||
$ conda install xonsh
|
||||
|
||||
.. note:: For the bleeding edge development version use ``conda install -c xonsh/channel/dev xonsh``
|
||||
|
||||
|
||||
**pip:**
|
||||
|
||||
|
|
|
@ -20,8 +20,6 @@ You can install xonsh using homebrew, conda, pip, or from source.
|
|||
|
||||
$ conda config --add channels conda-forge
|
||||
$ conda install xonsh
|
||||
|
||||
.. note:: For the bleeding edge development version use ``conda install -c xonsh/channel/dev xonsh``
|
||||
|
||||
|
||||
**pip:**
|
||||
|
|
|
@ -69,8 +69,8 @@ default, they cannot be unloaded (easily).
|
|||
.. note::
|
||||
|
||||
When a xontrib is loaded from a config file or via the xontrib command,
|
||||
its public variables are placed in the current execution context, just
|
||||
like variables set in run control files.
|
||||
its public variables are placed in the current execution context unless
|
||||
``__all__`` is defined, just like in regular Python modules.
|
||||
|
||||
Loading xontribs in the config file is as simple as adding a list of string
|
||||
xontrib names to the top-level ``"xontribs"`` key. For example, the following
|
||||
|
|
|
@ -23,8 +23,6 @@ Install xonsh with the following command:
|
|||
> conda config --add channels conda-forge
|
||||
> conda install xonsh
|
||||
|
||||
.. note:: For the bleeding edge development version use ``conda install -c xonsh/channel/dev xonsh``
|
||||
|
||||
This will install xonsh and all the recommended dependencies. Next, run xonsh:
|
||||
|
||||
.. code-block:: bat
|
||||
|
|
13
news/fix-xonsh-python.rst
Normal file
13
news/fix-xonsh-python.rst
Normal file
|
@ -0,0 +1,13 @@
|
|||
**Added:** None
|
||||
|
||||
**Changed:**
|
||||
|
||||
* xon.sh uses the interpreter used to install instead of the default python3.
|
||||
|
||||
**Deprecated:** None
|
||||
|
||||
**Removed:** None
|
||||
|
||||
**Fixed:** None
|
||||
|
||||
**Security:** None
|
21
news/history-refactor.rst
Normal file
21
news/history-refactor.rst
Normal file
|
@ -0,0 +1,21 @@
|
|||
**Added:**
|
||||
|
||||
* ``_hist_get`` that uses generators to filter and fetch
|
||||
the history commands of each session.
|
||||
|
||||
* ``-n`` option to the show subcommand to choose
|
||||
to numerate the commands.
|
||||
|
||||
**Changed:**
|
||||
|
||||
* ``_hist_show`` now uses ``_hist_get`` to print out the commands.
|
||||
|
||||
**Deprecated:** None
|
||||
|
||||
**Removed:** None
|
||||
|
||||
**Fixed:**
|
||||
|
||||
* ``_zsh_hist_parser`` not parsing history files without timestamps.
|
||||
|
||||
**Security:** None
|
17
news/remove_anaconda_build.rst
Normal file
17
news/remove_anaconda_build.rst
Normal file
|
@ -0,0 +1,17 @@
|
|||
**Added:** None
|
||||
|
||||
**Changed:** None
|
||||
|
||||
**Deprecated:** None
|
||||
|
||||
**Removed:**
|
||||
|
||||
* Anaconda Build is shutting down so we can no longer build conda development packages.
|
||||
All references to these packages are removed from the documentation.
|
||||
* Removed conda build recipe since the it is no longer used for Anaconda Build.
|
||||
The recipe used to build xonsh on conda-forge can be found here:
|
||||
https://github.com/conda-forge/xonsh-feedstock/blob/master/recipe/meta.yaml
|
||||
|
||||
**Fixed:** None
|
||||
|
||||
**Security:** None
|
14
news/srcenc.rst
Normal file
14
news/srcenc.rst
Normal file
|
@ -0,0 +1,14 @@
|
|||
**Added:** None
|
||||
|
||||
**Changed:** None
|
||||
|
||||
**Deprecated:** None
|
||||
|
||||
**Removed:** None
|
||||
|
||||
**Fixed:**
|
||||
|
||||
* Fixed error with not sourcing files with ``$XONSH_ENCODING`` and
|
||||
``$XONSH_ENCODING_ERRORS``.
|
||||
|
||||
**Security:** None
|
13
news/xontrib-all.rst
Normal file
13
news/xontrib-all.rst
Normal file
|
@ -0,0 +1,13 @@
|
|||
**Added:** None
|
||||
|
||||
**Changed:**
|
||||
|
||||
* Xontribs may now define ``__all__`` as a module top-level to limit what gets exported to the shell context
|
||||
|
||||
**Deprecated:** None
|
||||
|
||||
**Removed:** None
|
||||
|
||||
**Fixed:** None
|
||||
|
||||
**Security:** None
|
14
news/xontribs.rst
Normal file
14
news/xontribs.rst
Normal file
|
@ -0,0 +1,14 @@
|
|||
**Added:**
|
||||
|
||||
* Added xontribs:
|
||||
* `z (Tracks your most used directories, based on 'frecency'.) <https://github.com/astronouth7303/xontrib-z>`_
|
||||
|
||||
**Changed:** None
|
||||
|
||||
**Deprecated:** None
|
||||
|
||||
**Removed:** None
|
||||
|
||||
**Fixed:** None
|
||||
|
||||
**Security:** None
|
|
@ -1,37 +0,0 @@
|
|||
package:
|
||||
name: xonsh
|
||||
version: {{ GIT_DESCRIBE_TAG + '.dev' + GIT_DESCRIBE_NUMBER }}
|
||||
|
||||
source:
|
||||
git_url: ../
|
||||
|
||||
build:
|
||||
script: python setup.py install --single-version-externally-managed --record=record.txt
|
||||
number: 0
|
||||
string: {{'py' + CONDA_PY + '_' + GIT_DESCRIBE_HASH }}
|
||||
skip: True # [py2k]
|
||||
entry_points:
|
||||
- xonsh = xonsh.main:main
|
||||
|
||||
requirements:
|
||||
build:
|
||||
- python
|
||||
- ply
|
||||
- setuptools
|
||||
- jupyter
|
||||
run:
|
||||
- python
|
||||
- ply
|
||||
- prompt_toolkit
|
||||
- setproctitle
|
||||
- pygments
|
||||
|
||||
app:
|
||||
entry: xonsh
|
||||
icon: ../docs/_static/ascii_conch_part_color.png
|
||||
|
||||
about:
|
||||
home: http://xon.sh/
|
||||
license: BSD
|
||||
summary: xonsh is a Python-ish, BASHwards-facing shell.
|
||||
|
BIN
recipe/xonsh.ico
BIN
recipe/xonsh.ico
Binary file not shown.
Before Width: | Height: | Size: 103 KiB |
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"menu_name": "Anaconda${PY_VER} ${PLATFORM}",
|
||||
"menu_items":
|
||||
[
|
||||
{
|
||||
"name": "xonsh",
|
||||
"pyscript": "${PYTHON_SCRIPTS}/xonsh-script.py",
|
||||
"icon": "${MENU_DIR}/xonsh.ico"
|
||||
}
|
||||
]
|
||||
}
|
32
setup.py
32
setup.py
|
@ -181,7 +181,7 @@ class xinstall(install):
|
|||
import traceback
|
||||
traceback.print_exc()
|
||||
print('Installing Jupyter hook failed.')
|
||||
install.run(self)
|
||||
super().run()
|
||||
if dirty:
|
||||
restore_version()
|
||||
|
||||
|
@ -193,7 +193,7 @@ class xsdist(sdist):
|
|||
build_tables()
|
||||
amalgamate_source()
|
||||
dirty = dirty_version()
|
||||
sdist.make_release_tree(self, basedir, files)
|
||||
super().make_release_tree(basedir, files)
|
||||
if dirty:
|
||||
restore_version()
|
||||
|
||||
|
@ -213,11 +213,31 @@ class install_scripts_quoted_shebang(install_scripts):
|
|||
contents = contents.replace(shebang, quoted_shebang)
|
||||
super().write_script(script_name, contents, mode, *ignored)
|
||||
|
||||
|
||||
class install_scripts_rewrite(install_scripts):
|
||||
"""Change default python3 to the concrete python binary used to install/develop inside xon.sh script"""
|
||||
def run(self):
|
||||
super().run()
|
||||
if not self.dry_run:
|
||||
for file in self.get_outputs():
|
||||
if file.endswith('xon.sh'):
|
||||
# this is the value distutils use on its shebang translation
|
||||
bs_cmd = self.get_finalized_command('build_scripts')
|
||||
exec_param = getattr(bs_cmd, 'executable', None)
|
||||
|
||||
with open(file, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
processed = content.replace(' python3 ', ' "{}" '.format(exec_param))
|
||||
|
||||
with open(file, 'w') as f:
|
||||
f.write(processed)
|
||||
|
||||
# The custom install needs to be used on Windows machines
|
||||
if os.name == 'nt':
|
||||
cmdclass = {'install': xinstall, 'sdist': xsdist, 'install_scripts': install_scripts_quoted_shebang}
|
||||
else:
|
||||
cmdclass = {'install': xinstall, 'sdist': xsdist}
|
||||
cmdclass = {'install': xinstall, 'sdist': xsdist, 'install_scripts': install_scripts_rewrite}
|
||||
|
||||
|
||||
if HAVE_SETUPTOOLS:
|
||||
|
@ -231,6 +251,12 @@ if HAVE_SETUPTOOLS:
|
|||
if dirty:
|
||||
restore_version()
|
||||
|
||||
def install_script(self, dist, script_name, script_text, dev_path=None):
|
||||
if script_name == 'xon.sh':
|
||||
# change default python3 to the concrete python binary used to install/develop inside xon.sh script
|
||||
script_text = script_text.replace(' python3 ', ' "{}" '.format(sys.executable))
|
||||
super().install_script(dist, script_name, script_text, dev_path)
|
||||
|
||||
|
||||
def main():
|
||||
"""The main entry point."""
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Tests the xonsh history."""
|
||||
# pylint: disable=protected-access
|
||||
# TODO: Remove the following pylint directive when it correctly handles calls
|
||||
# to nose assert_xxx functions.
|
||||
# pylint: disable=no-value-for-parameter
|
||||
from __future__ import unicode_literals, print_function
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
|
@ -71,63 +67,34 @@ def test_cmd_field(hist, xonsh_builtins):
|
|||
assert 1 == hist.rtns[-1]
|
||||
assert None == hist.outs[-1]
|
||||
|
||||
def run_show_cmd(hist_args, commands, base_idx=0, step=1):
|
||||
"""Run and evaluate the output of the given show command."""
|
||||
stdout = sys.stdout
|
||||
stdout.seek(0, io.SEEK_SET)
|
||||
stdout.truncate()
|
||||
history.history_main(hist_args)
|
||||
stdout.seek(0, io.SEEK_SET)
|
||||
hist_lines = stdout.readlines()
|
||||
assert len(commands) == len(hist_lines)
|
||||
for idx, (cmd, actual) in enumerate(zip(commands, hist_lines)):
|
||||
expected = ' {:d}: {:s}\n'.format(base_idx + idx * step, cmd)
|
||||
assert expected == actual
|
||||
|
||||
def test_show_cmd(hist, xonsh_builtins):
|
||||
cmds = ['ls', 'cat hello kitty', 'abc', 'def', 'touch me', 'grep from me']
|
||||
|
||||
@pytest.mark.parametrize('inp, commands, offset', [
|
||||
('', cmds, (0, 1)),
|
||||
('-r', list(reversed(cmds)), (len(cmds)- 1, -1)),
|
||||
('0', cmds[0:1], (0, 1)),
|
||||
('1', cmds[1:2], (1, 1)),
|
||||
('-2', cmds[-2:-1], (len(cmds) -2 , 1)),
|
||||
('1:3', cmds[1:3], (1, 1)),
|
||||
('1::2', cmds[1::2], (1, 2)),
|
||||
('-4:-2', cmds[-4:-2], (len(cmds) - 4, 1))
|
||||
])
|
||||
def test_show_cmd_numerate(inp, commands, offset, hist, xonsh_builtins, capsys):
|
||||
"""Verify that CLI history commands work."""
|
||||
cmds = ['ls', 'cat hello kitty', 'abc', 'def', 'touch me', 'grep from me']
|
||||
sys.stdout = io.StringIO()
|
||||
base_idx, step = offset
|
||||
xonsh_builtins.__xonsh_history__ = hist
|
||||
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)})
|
||||
|
||||
# Verify an implicit "show" emits show history
|
||||
run_show_cmd([], cmds)
|
||||
exp = ('{}: {}'.format(base_idx + idx * step, cmd)
|
||||
for idx, cmd in enumerate(list(commands)))
|
||||
exp = '\n'.join(exp)
|
||||
|
||||
# Verify an explicit "show" with no qualifiers emits
|
||||
# show history.
|
||||
run_show_cmd(['show'], cmds)
|
||||
|
||||
# Verify an explicit "show" with a reversed qualifier
|
||||
# emits show history in reverse order.
|
||||
run_show_cmd(['show', '-r'], list(reversed(cmds)),
|
||||
len(cmds) - 1, -1)
|
||||
|
||||
# Verify that showing a specific history entry relative to
|
||||
# the start of the history works.
|
||||
run_show_cmd(['show', '0'], [cmds[0]], 0)
|
||||
run_show_cmd(['show', '1'], [cmds[1]], 1)
|
||||
|
||||
# Verify that showing a specific history entry relative to
|
||||
# the end of the history works.
|
||||
run_show_cmd(['show', '-2'], [cmds[-2]],
|
||||
len(cmds) - 2)
|
||||
|
||||
# Verify that showing a history range relative to the start of the
|
||||
# history works.
|
||||
run_show_cmd(['show', '0:2'], cmds[0:2], 0)
|
||||
run_show_cmd(['show', '1::2'], cmds[1::2], 1, 2)
|
||||
|
||||
# Verify that showing a history range relative to the end of the
|
||||
# history works.
|
||||
run_show_cmd(['show', '-2:'],
|
||||
cmds[-2:], len(cmds) - 2)
|
||||
run_show_cmd(['show', '-4:-2'],
|
||||
cmds[-4:-2], len(cmds) - 4)
|
||||
|
||||
sys.stdout = sys.__stdout__
|
||||
history.history_main(['show', '-n'] + shlex.split(inp))
|
||||
out, err = capsys.readouterr()
|
||||
assert out.rstrip() == exp
|
||||
|
||||
|
||||
def test_histcontrol(hist, xonsh_builtins):
|
||||
|
@ -195,16 +162,22 @@ def test_parse_args_help(args, capsys):
|
|||
|
||||
|
||||
@pytest.mark.parametrize('args, exp', [
|
||||
('', ('show', 'session', [])),
|
||||
('show', ('show', 'session', [])),
|
||||
('show session', ('show', 'session', [])),
|
||||
('show session 15', ('show', 'session', ['15'])),
|
||||
('show bash 3:5 15:66', ('show', 'bash', ['3:5', '15:66'])),
|
||||
('show zsh 3 5:6 16 9:3', ('show', 'zsh', ['3', '5:6', '16', '9:3'])),
|
||||
('', ('show', 'session', [], False, False)),
|
||||
('1:5', ('show', 'session', ['1:5'], False, False)),
|
||||
('show', ('show', 'session', [], False, False)),
|
||||
('show 15', ('show', 'session', ['15'], False, False)),
|
||||
('show bash 3:5 15:66', ('show', 'bash', ['3:5', '15:66'], False, False)),
|
||||
('show -r', ('show', 'session', [], False, True)),
|
||||
('show -rn bash', ('show', 'bash', [], True, True)),
|
||||
('show -n -r -30:20', ('show', 'session', ['-30:20'], True, True)),
|
||||
('show -n zsh 1:2:3', ('show', 'zsh', ['1:2:3'], True, False))
|
||||
])
|
||||
def test_parser_show(args, exp):
|
||||
args = _hist_parse_args(shlex.split(args))
|
||||
action, session, slices = exp
|
||||
assert args.action == action
|
||||
assert args.session == session
|
||||
assert args.slices == slices
|
||||
# use dict instead of argparse.Namespace for pretty pytest diff
|
||||
exp_ns = {'action': exp[0],
|
||||
'session': exp[1],
|
||||
'slices': exp[2],
|
||||
'numerate': exp[3],
|
||||
'reverse': exp[4]}
|
||||
ns = _hist_parse_args(shlex.split(args))
|
||||
assert ns.__dict__ == exp_ns
|
||||
|
|
|
@ -815,6 +815,7 @@ def test_bool_or_int_to_str(inp, exp):
|
|||
@pytest.mark.parametrize('inp, exp', [
|
||||
(42, slice(42, 43)),
|
||||
(None, slice(None, None, None)),
|
||||
(slice(1,2), slice(1,2)),
|
||||
('42', slice(42, 43)),
|
||||
('-42', slice(-42, -41)),
|
||||
('1:2:3', slice(1, 2, 3)),
|
||||
|
@ -823,25 +824,28 @@ def test_bool_or_int_to_str(inp, exp):
|
|||
('1:', slice(1, None, None)),
|
||||
('[1:2:3]', slice(1, 2, 3)),
|
||||
('(1:2:3)', slice(1, 2, 3)),
|
||||
((4, 8, 10), slice(4, 8, 10)),
|
||||
([10,20], slice(10,20))
|
||||
])
|
||||
def test_ensure_slice(inp, exp):
|
||||
obs = ensure_slice(inp)
|
||||
assert exp == obs
|
||||
|
||||
|
||||
@pytest.mark.parametrize('inp, error', [
|
||||
('42.3', ValueError),
|
||||
('3:asd5:1', ValueError),
|
||||
('test' , ValueError),
|
||||
('6.53:100:5', ValueError),
|
||||
('4:-', ValueError),
|
||||
('2:15-:3', ValueError),
|
||||
('50:-:666', ValueError),
|
||||
(object(), TypeError),
|
||||
([], TypeError)
|
||||
@pytest.mark.parametrize('inp', [
|
||||
'42.3',
|
||||
'3:asd5:1',
|
||||
'test' ,
|
||||
'6.53:100:5',
|
||||
'4:-',
|
||||
'2:15-:3',
|
||||
'50:-:666',
|
||||
object(),
|
||||
[1,5,3,4],
|
||||
('foo')
|
||||
])
|
||||
def test_ensure_slice_invalid(inp, error):
|
||||
with pytest.raises(error):
|
||||
def test_ensure_slice_invalid(inp):
|
||||
with pytest.raises(ValueError):
|
||||
obs = ensure_slice(inp)
|
||||
|
||||
|
||||
|
|
|
@ -43,3 +43,25 @@ def test_activate(xonsh_builtins, tmpdir):
|
|||
assert xonsh_builtins.__xonsh_env__['VIRTUAL_ENV'] == vox['spam'].env
|
||||
vox.deactivate()
|
||||
assert 'VIRTUAL_ENV' not in xonsh_builtins.__xonsh_env__
|
||||
|
||||
|
||||
@skip_if_on_conda
|
||||
def test_path(xonsh_builtins, tmpdir):
|
||||
"""
|
||||
Test to make sure Vox properly activates and deactivates by examining $PATH
|
||||
"""
|
||||
xonsh_builtins.__xonsh_env__['VIRTUALENV_HOME'] = str(tmpdir)
|
||||
# I consider the case that the user doesn't have a PATH set to be unreasonable
|
||||
xonsh_builtins.__xonsh_env__.setdefault('PATH', [])
|
||||
|
||||
oldpath = list(xonsh_builtins.__xonsh_env__['PATH'])
|
||||
vox = Vox()
|
||||
vox.create('eggs')
|
||||
|
||||
vox.activate('eggs')
|
||||
|
||||
assert oldpath != xonsh_builtins.__xonsh_env__['PATH']
|
||||
|
||||
vox.deactivate()
|
||||
|
||||
assert oldpath == xonsh_builtins.__xonsh_env__['PATH']
|
||||
|
|
|
@ -1,6 +1,56 @@
|
|||
"""xontrib tests, such as they are"""
|
||||
from xonsh.xontribs import xontrib_metadata
|
||||
import sys
|
||||
import pytest
|
||||
from xonsh.xontribs import xontrib_metadata, xontrib_context
|
||||
|
||||
def test_load_xontrib_metadata():
|
||||
# Simply tests that the xontribs JSON files isn't malformed.
|
||||
xontrib_metadata()
|
||||
xontrib_metadata()
|
||||
|
||||
@pytest.yield_fixture
|
||||
def tmpmod(tmpdir):
|
||||
"""
|
||||
Same as tmpdir but also adds/removes it to the front of sys.path.
|
||||
|
||||
Also cleans out any modules loaded as part of the test.
|
||||
"""
|
||||
sys.path.insert(0, str(tmpdir))
|
||||
loadedmods = set(sys.modules.keys())
|
||||
try:
|
||||
yield tmpdir
|
||||
finally:
|
||||
del sys.path[0]
|
||||
newmods = set(sys.modules.keys()) - loadedmods
|
||||
for m in newmods:
|
||||
del sys.modules[m]
|
||||
|
||||
def test_noall(tmpmod):
|
||||
"""
|
||||
Tests what get's exported from a module without __all__
|
||||
"""
|
||||
|
||||
with tmpmod.mkdir("xontrib").join("spameggs.py").open('w') as x:
|
||||
x.write("""
|
||||
spam = 1
|
||||
eggs = 2
|
||||
_foobar = 3
|
||||
""")
|
||||
|
||||
ctx = xontrib_context('spameggs')
|
||||
assert ctx == {'spam': 1, 'eggs': 2}
|
||||
|
||||
def test_withall(tmpmod):
|
||||
"""
|
||||
Tests what get's exported from a module with __all__
|
||||
"""
|
||||
|
||||
with tmpmod.mkdir("xontrib").join("spameggs.py").open('w') as x:
|
||||
x.write("""
|
||||
__all__ = 'spam', '_foobar'
|
||||
spam = 1
|
||||
eggs = 2
|
||||
_foobar = 3
|
||||
""")
|
||||
|
||||
ctx = xontrib_context('spameggs')
|
||||
assert ctx == {'spam': 1, '_foobar': 3}
|
|
@ -250,11 +250,15 @@ def source_foreign(args, stdin=None):
|
|||
def source_alias(args, stdin=None):
|
||||
"""Executes the contents of the provided files in the current context.
|
||||
If sourced file isn't found in cwd, search for file along $PATH to source
|
||||
instead"""
|
||||
instead.
|
||||
"""
|
||||
env = builtins.__xonsh_env__
|
||||
encoding = env.get('XONSH_ENCODING')
|
||||
errors = env.get('XONSH_ENCODING_ERRORS')
|
||||
for fname in args:
|
||||
if not os.path.isfile(fname):
|
||||
fname = locate_binary(fname)
|
||||
with open(fname, 'r') as fp:
|
||||
with open(fname, 'r', encoding=encoding, errors=errors) as fp:
|
||||
builtins.execx(fp.read(), 'exec', builtins.__xonsh_ctx__)
|
||||
|
||||
|
||||
|
|
308
xonsh/history.py
308
xonsh/history.py
|
@ -2,15 +2,15 @@
|
|||
"""Implements the xonsh history object."""
|
||||
import os
|
||||
import sys
|
||||
import uuid
|
||||
import time
|
||||
import json
|
||||
import glob
|
||||
import json
|
||||
import time
|
||||
import uuid
|
||||
import argparse
|
||||
import operator
|
||||
import datetime
|
||||
import builtins
|
||||
import datetime
|
||||
import functools
|
||||
import itertools
|
||||
import threading
|
||||
import collections
|
||||
import collections.abc as abc
|
||||
|
@ -236,36 +236,26 @@ class CommandField(abc.Sequence):
|
|||
return self is self.hist._queue[0]
|
||||
|
||||
|
||||
def _find_histfile_var(file_list=None, default=None):
|
||||
if file_list is None:
|
||||
return None
|
||||
hist_file = None
|
||||
|
||||
found_hist = False
|
||||
def _find_histfile_var(file_list, default=None):
|
||||
"""Return the path of the history file
|
||||
from the value of the envvar HISTFILE.
|
||||
"""
|
||||
for f in file_list:
|
||||
f = expanduser_abs_path(f)
|
||||
if not os.path.isfile(f):
|
||||
continue
|
||||
with open(f, 'r') as rc_file:
|
||||
for line in rc_file:
|
||||
if "HISTFILE=" in line:
|
||||
evar = line.split(' ', 1)[-1]
|
||||
hist_file = evar.split('=', 1)[-1]
|
||||
for char in ['"', "'", '\n']:
|
||||
hist_file = hist_file.replace(char, '')
|
||||
if line.startswith('HISTFILE='):
|
||||
hist_file = line.split('=', 1)[1].strip('\'"\n')
|
||||
hist_file = expanduser_abs_path(hist_file)
|
||||
if os.path.isfile(hist_file):
|
||||
found_hist = True
|
||||
break
|
||||
if found_hist:
|
||||
break
|
||||
|
||||
if hist_file is None:
|
||||
default = expanduser_abs_path(default)
|
||||
if os.path.isfile(default):
|
||||
hist_file = default
|
||||
|
||||
return hist_file
|
||||
return hist_file
|
||||
else:
|
||||
if default:
|
||||
default = expanduser_abs_path(default)
|
||||
if os.path.isfile(default):
|
||||
return default
|
||||
|
||||
|
||||
def _all_xonsh_parser(**kwargs):
|
||||
|
@ -279,19 +269,17 @@ def _all_xonsh_parser(**kwargs):
|
|||
|
||||
files = [os.path.join(data_dir, f) for f in os.listdir(data_dir)
|
||||
if f.startswith('xonsh-') and f.endswith('.json')]
|
||||
file_hist = []
|
||||
ind = 0
|
||||
for f in files:
|
||||
try:
|
||||
json_file = LazyJSON(f, reopen=False)
|
||||
file_hist.append(json_file.load()['cmds'])
|
||||
except ValueError:
|
||||
# Invalid json file
|
||||
pass
|
||||
commands = [(c['inp'][:-1] if c['inp'].endswith('\n') else c['inp'],
|
||||
c['ts'][0])
|
||||
for commands in file_hist for c in commands if c]
|
||||
commands.sort(key=operator.itemgetter(1))
|
||||
return [(c, t, ind) for ind, (c, t) in enumerate(commands)]
|
||||
commands = json_file.load()['cmds']
|
||||
for c in commands:
|
||||
yield (c['inp'].rstrip(), c['ts'][0], ind)
|
||||
ind += 1
|
||||
|
||||
|
||||
def _curr_session_parser(hist=None, **kwargs):
|
||||
|
@ -301,28 +289,22 @@ def _curr_session_parser(hist=None, **kwargs):
|
|||
"""
|
||||
if hist is None:
|
||||
hist = builtins.__xonsh_history__
|
||||
if not hist:
|
||||
return None
|
||||
start_times = [start for start, end in hist.tss]
|
||||
names = [name[:-1] if name.endswith('\n') else name
|
||||
for name in hist.inps]
|
||||
commands = enumerate(zip(names, start_times))
|
||||
return [(c, t, ind) for ind, (c, t) in commands]
|
||||
start_times = (start for start, end in hist.tss)
|
||||
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):
|
||||
default_location = os.path.join('~', '.zsh_history')
|
||||
location_list = [os.path.join('~', '.zshrc'),
|
||||
os.path.join('~', '.zprofile')]
|
||||
"""Yield commands from zsh history file"""
|
||||
if location is None:
|
||||
location = _find_histfile_var(location_list, default_location)
|
||||
z_hist_formatted = []
|
||||
if location and os.path.isfile(location):
|
||||
with open(location, 'r', errors='backslashreplace') as z_file:
|
||||
z_txt = z_file.read()
|
||||
z_hist = z_txt.splitlines()
|
||||
if z_hist:
|
||||
for ind, line in enumerate(z_hist):
|
||||
location = _find_histfile_var([os.path.join('~', '.zshrc'),
|
||||
os.path.join('~', '.zprofile')],
|
||||
os.path.join('~', '.zsh_history'))
|
||||
if location:
|
||||
with open(location, 'r', errors='backslashreplace') as zsh_hist:
|
||||
for ind, line in enumerate(zsh_hist):
|
||||
if line.startswith(':'):
|
||||
try:
|
||||
start_time, command = line.split(';', 1)
|
||||
except ValueError:
|
||||
|
@ -331,29 +313,25 @@ def _zsh_hist_parser(location=None, **kwargs):
|
|||
try:
|
||||
start_time = float(start_time.split(':')[1])
|
||||
except ValueError:
|
||||
start_time = -1
|
||||
z_hist_formatted.append((command, start_time, ind))
|
||||
return z_hist_formatted
|
||||
start_time = 0.0
|
||||
yield (command.rstrip(), start_time, ind)
|
||||
else:
|
||||
yield (line.rstrip(), 0.0, ind)
|
||||
|
||||
else:
|
||||
print("No zsh history file found", file=sys.stderr)
|
||||
|
||||
|
||||
def _bash_hist_parser(location=None, **kwargs):
|
||||
default_location = os.path.join('~', '.bash_history')
|
||||
location_list = [os.path.join('~', '.bashrc'),
|
||||
os.path.join('~', '.bash_profile')]
|
||||
"""Yield commands from bash history file"""
|
||||
if location is None:
|
||||
location = _find_histfile_var(location_list, default_location)
|
||||
bash_hist_formatted = []
|
||||
if location and os.path.isfile(location):
|
||||
with open(location, 'r', errors='backslashreplace') as bash_file:
|
||||
b_txt = bash_file.read()
|
||||
bash_hist = b_txt.splitlines()
|
||||
if bash_hist:
|
||||
for ind, command in enumerate(bash_hist):
|
||||
bash_hist_formatted.append((command, 0.0, ind))
|
||||
return bash_hist_formatted
|
||||
location = _find_histfile_var([os.path.join('~', '.bashrc'),
|
||||
os.path.join('~', '.bash_profile')],
|
||||
os.path.join('~', '.bash_history'))
|
||||
if location:
|
||||
with open(location, 'r', errors='backslashreplace') as bash_hist:
|
||||
for ind, line in enumerate(bash_hist):
|
||||
yield (line.rstrip(), 0.0, ind)
|
||||
else:
|
||||
print("No bash history file", file=sys.stderr)
|
||||
|
||||
|
@ -365,11 +343,11 @@ def _hist_create_parser():
|
|||
description='Tools for dealing with history')
|
||||
subp = p.add_subparsers(title='action', dest='action')
|
||||
# session action
|
||||
show = subp.add_parser('show', aliases=['session'],
|
||||
help='displays session history, default action')
|
||||
show = subp.add_parser('show', help='displays session history, default action')
|
||||
show.add_argument('-r', dest='reverse', default=False,
|
||||
action='store_true',
|
||||
help='reverses the direction')
|
||||
action='store_true', help='reverses the direction')
|
||||
show.add_argument('-n', dest='numerate', default=False, action='store_true',
|
||||
help='numerate each command')
|
||||
show.add_argument('session', nargs='?', choices=_HIST_SESSIONS.keys(), default='session',
|
||||
help='Choose a history session, defaults to current session')
|
||||
show.add_argument('slices', nargs=argparse.REMAINDER, default=[],
|
||||
|
@ -407,103 +385,83 @@ def _hist_create_parser():
|
|||
return p
|
||||
|
||||
|
||||
def _hist_show(ns=None, hist=None, start_index=None, end_index=None,
|
||||
start_time=None, end_time=None, location=None):
|
||||
"""Show the requested portion of shell history.
|
||||
Accepts multiple history sources (xonsh, bash, zsh)
|
||||
def _hist_get_portion(commands, slices):
|
||||
"""Yield from portions of history commands."""
|
||||
if len(slices) == 1:
|
||||
s = ensure_slice(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:
|
||||
s = ensure_slice(s)
|
||||
yield from commands[s]
|
||||
|
||||
May be invoked as an alias with history all/bash/zsh which will
|
||||
provide history as stdout or with __xonsh_history__.show()
|
||||
which will return the history as a list with each item
|
||||
in the tuple form (name, start_time, index).
|
||||
|
||||
If invoked via __xonsh_history__.show() then the ns parameter
|
||||
can be supplied as a str with the follow options::
|
||||
def _hist_filter_ts(commands, start_time=None, end_time=None):
|
||||
"""Yield only the commands between start and end time."""
|
||||
if start_time is None:
|
||||
start_time = 0.0
|
||||
elif isinstance(start_time, datetime.datetime):
|
||||
start_time = start_time.timestamp()
|
||||
if end_time is None:
|
||||
end_time = float('inf')
|
||||
elif isinstance(end_time, datetime.datetime):
|
||||
end_time = end_time.timestamp()
|
||||
for cmd in commands:
|
||||
if start_time <= cmd[1] < end_time:
|
||||
yield cmd
|
||||
|
||||
session - returns xonsh history from current session
|
||||
xonsh - returns xonsh history from all sessions
|
||||
all - alias of xonsh
|
||||
zsh - returns all zsh history
|
||||
bash - returns all bash history
|
||||
|
||||
def _hist_get(session='session', slices=None,
|
||||
start_time=None, end_time=None, location=None):
|
||||
"""Get the requested portion of shell history.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
session: {'session', 'all', 'xonsh', 'bash', 'zsh'}
|
||||
The history session to get.
|
||||
slices : list of slice-like objects, optional
|
||||
Get only portions of history.
|
||||
start_time, end_time: float, optional
|
||||
Filter commands by timestamp.
|
||||
location: string, optional
|
||||
The history file location (bash or zsh)
|
||||
|
||||
Returns
|
||||
-------
|
||||
generator
|
||||
A filtered list of commands
|
||||
"""
|
||||
# Check if ns is a string, meaning it was invoked from
|
||||
if hist is None:
|
||||
hist = bultins.__xonsh_history__
|
||||
alias = True
|
||||
if isinstance(ns, str) and ns in _HIST_SESSIONS:
|
||||
ns = _hist_create_parser().parse_args([ns])
|
||||
alias = False
|
||||
if not ns:
|
||||
ns = _hist_create_parser().parse_args(['show', 'xonsh'])
|
||||
alias = False
|
||||
cmds = _HIST_SESSIONS[session](location=location)
|
||||
if slices:
|
||||
cmds = _hist_get_portion(cmds, slices)
|
||||
if start_time or end_time:
|
||||
cmds = _hist_filter_ts(cmds, start_time, end_time)
|
||||
return cmds
|
||||
|
||||
|
||||
def _hist_show(ns, *args, **kwargs):
|
||||
"""Show the requested portion of shell history.
|
||||
Accepts same parameters with `_hist_get`.
|
||||
"""
|
||||
commands = _hist_get(ns.session, ns.slices, **kwargs)
|
||||
try:
|
||||
commands = _HIST_SESSIONS[ns.session](hist=hist, location=location)
|
||||
except KeyError:
|
||||
print("{} is not a valid history session".format(ns.action))
|
||||
return None
|
||||
if not commands:
|
||||
return None
|
||||
if start_time:
|
||||
if isinstance(start_time, datetime.datetime):
|
||||
start_time = start_time.timestamp()
|
||||
if isinstance(start_time, float):
|
||||
commands = [c for c in commands if c[1] >= start_time]
|
||||
if ns.reverse:
|
||||
commands = reversed(list(commands))
|
||||
if not ns.numerate:
|
||||
for c, _, _ in commands:
|
||||
print(c)
|
||||
else:
|
||||
print("Invalid start time, must be float or datetime.")
|
||||
if end_time:
|
||||
if isinstance(end_time, datetime.datetime):
|
||||
end_time = end_time.timestamp()
|
||||
if isinstance(end_time, float):
|
||||
commands = [c for c in commands if c[1] <= end_time]
|
||||
else:
|
||||
print("Invalid end time, must be float or datetime.")
|
||||
idx = None
|
||||
if ns:
|
||||
_commands = []
|
||||
for s in ns.slices:
|
||||
try:
|
||||
s = ensure_slice(s)
|
||||
except (ValueError, TypeError):
|
||||
print('{!r} is not a valid slice format'.format(s), file=sys.stderr)
|
||||
return
|
||||
if s:
|
||||
try:
|
||||
_commands.extend(commands[s])
|
||||
except IndexError:
|
||||
err = "Index likely not in range. Only {} commands."
|
||||
print(err.format(len(commands)), file=sys.stderr)
|
||||
return
|
||||
else:
|
||||
if _commands:
|
||||
commands = _commands
|
||||
else:
|
||||
idx = slice(start_index, end_index)
|
||||
|
||||
if (isinstance(idx, slice) and
|
||||
start_time is None and end_time is None):
|
||||
commands = commands[idx]
|
||||
|
||||
if ns and ns.reverse:
|
||||
commands = list(reversed(commands))
|
||||
|
||||
if commands:
|
||||
digits = len(str(max([i for c, t, i in commands])))
|
||||
if alias:
|
||||
for c, t, i in commands:
|
||||
for line_ind, line in enumerate(c.split('\n')):
|
||||
if line_ind == 0:
|
||||
print('{:>{width}}: {}'.format(i, line,
|
||||
width=digits + 1))
|
||||
else:
|
||||
print(' {:>>{width}} {}'.format('', line,
|
||||
width=digits + 1))
|
||||
else:
|
||||
return commands
|
||||
for c, _, i in commands:
|
||||
print('{}: {}'.format(i, c))
|
||||
except ValueError as err:
|
||||
print("history: error: {}".format(err), file=sys.stderr)
|
||||
|
||||
|
||||
#
|
||||
# Interface to History
|
||||
#
|
||||
class History(object):
|
||||
"""Xonsh session history.
|
||||
|
||||
|
@ -625,17 +583,15 @@ class History(object):
|
|||
return hf
|
||||
|
||||
def show(self, *args, **kwargs):
|
||||
"""
|
||||
Returns shell history as a list
|
||||
"""Return shell history as a list
|
||||
|
||||
Valid options:
|
||||
`session` - returns xonsh history from current session
|
||||
`show` - alias of `session`
|
||||
`xonsh` - returns xonsh history from all sessions
|
||||
`zsh` - returns all zsh history
|
||||
`bash` - returns all bash history
|
||||
"""
|
||||
return _hist_show(*args, **kwargs)
|
||||
return list(_hist_get(*args, **kwargs))
|
||||
|
||||
|
||||
def _hist_info(ns, hist):
|
||||
|
@ -687,14 +643,19 @@ def _hist_parse_args(args):
|
|||
"""Parse arguments using the history argument parser."""
|
||||
parser = _hist_create_parser()
|
||||
if not args:
|
||||
args = ['show']
|
||||
elif args[0] not in ['-h', '--help'] and args[0] not in _HIST_MAIN_ACTIONS:
|
||||
args.insert(0, 'show')
|
||||
if (args[0] == 'show'
|
||||
and len(args) > 1
|
||||
and args[1] not in ['-h', '--help', '-r']
|
||||
and args[1] not in _HIST_SESSIONS):
|
||||
args.insert(1, 'session')
|
||||
args = ['show', 'session']
|
||||
elif args[0] not in _HIST_MAIN_ACTIONS and args[0] not in ('-h', '--help'):
|
||||
args = ['show', 'session'] + args
|
||||
elif args[0] == 'show':
|
||||
slices_index = 0
|
||||
for i, a in enumerate(args[1:], 1):
|
||||
if a in _HIST_SESSIONS:
|
||||
break
|
||||
elif a.startswith('-') and a.lstrip('-').isalpha():
|
||||
# get last optional arg, before slices
|
||||
slices_index = i
|
||||
else: # no session arg found, insert before slices
|
||||
args.insert(slices_index + 1, 'session')
|
||||
return parser.parse_args(args)
|
||||
|
||||
|
||||
|
@ -702,4 +663,5 @@ def history_main(args=None, stdin=None):
|
|||
"""This is the history command entry point."""
|
||||
hist = builtins.__xonsh_history__
|
||||
ns = _hist_parse_args(args)
|
||||
_HIST_MAIN_ACTIONS[ns.action](ns, hist)
|
||||
if ns:
|
||||
_HIST_MAIN_ACTIONS[ns.action](ns, hist)
|
||||
|
|
|
@ -159,7 +159,7 @@ def premain(argv=None):
|
|||
parser.print_help()
|
||||
parser.exit()
|
||||
if args.version:
|
||||
version = '/'.join(('xonsh', __version__)),
|
||||
version = '/'.join(('xonsh', __version__))
|
||||
print(version)
|
||||
parser.exit()
|
||||
shell_kwargs = {'shell_type': args.shell_type,
|
||||
|
@ -224,8 +224,8 @@ def main(argv=None):
|
|||
# run a script contained in a file
|
||||
path = os.path.abspath(os.path.expanduser(args.file))
|
||||
if os.path.isfile(path):
|
||||
sys.argv = args.args
|
||||
env['ARGS'] = [args.file] + args.args
|
||||
sys.argv = [args.file] + args.args
|
||||
env['ARGS'] = sys.argv[:] # $ARGS is not sys.argv
|
||||
env['XONSH_SOURCE'] = path
|
||||
run_script_with_cache(args.file, shell.execer, glb=shell.ctx,
|
||||
loc=None, mode='exec')
|
||||
|
|
|
@ -926,9 +926,11 @@ def SLICE_REG():
|
|||
|
||||
|
||||
def ensure_slice(x):
|
||||
"""Convert a string or int to a slice."""
|
||||
if x is None:
|
||||
"""Try to convert an object into a slice, complain on failure"""
|
||||
if not x:
|
||||
return slice(None)
|
||||
elif isinstance(x, slice):
|
||||
return x
|
||||
try:
|
||||
x = int(x)
|
||||
s = slice(x, x+1)
|
||||
|
@ -941,7 +943,10 @@ def ensure_slice(x):
|
|||
else:
|
||||
raise ValueError('cannot convert {!r} to slice'.format(x))
|
||||
except TypeError:
|
||||
raise TypeError('ensure_slice() argument must be a string or a number not {}'.format(type(x)))
|
||||
try:
|
||||
s = slice(*(int(i) for i in x))
|
||||
except (TypeError, ValueError):
|
||||
raise ValueError('cannot convert {!r} to slice'.format(x))
|
||||
return s
|
||||
|
||||
|
||||
|
|
|
@ -80,6 +80,11 @@
|
|||
"package": "xontrib-avox",
|
||||
"url": "https://github.com/astronouth7303/xontrib-avox",
|
||||
"description": ["Automatic (de)activation of virtual environments as you cd around"]
|
||||
},
|
||||
{"name": "z",
|
||||
"package": "xontrib-z",
|
||||
"url": "https://github.com/astronouth7303/xontrib-z",
|
||||
"description": ["Tracks your most used directories, based on 'frecency'."]
|
||||
}
|
||||
],
|
||||
"packages": {
|
||||
|
@ -161,6 +166,13 @@
|
|||
"install": {
|
||||
"pip": "pip install xontrib-avox"
|
||||
}
|
||||
},
|
||||
"xontrib-z": {
|
||||
"license": "GPLv3",
|
||||
"url": "https://github.com/astronouth7303/xontrib-z",
|
||||
"install": {
|
||||
"pip": "pip install xontrib-z"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,11 @@ def xontrib_context(name):
|
|||
ImportWarning)
|
||||
return {}
|
||||
m = importlib.import_module(spec.name)
|
||||
ctx = {k: getattr(m, k) for k in dir(m) if not k.startswith('_')}
|
||||
pubnames = getattr(m, '__all__', None)
|
||||
if pubnames is not None:
|
||||
ctx = {k: getattr(m, k) for k in pubnames}
|
||||
else:
|
||||
ctx = {k: getattr(m, k) for k in dir(m) if not k.startswith('_')}
|
||||
return ctx
|
||||
|
||||
|
||||
|
|
|
@ -178,7 +178,7 @@ class Vox(collections.abc.Mapping):
|
|||
if 'VIRTUAL_ENV' in env:
|
||||
self.deactivate()
|
||||
|
||||
type(self).oldvars = {'PATH': env['PATH']}
|
||||
type(self).oldvars = {'PATH': list(env['PATH'])}
|
||||
env['PATH'].insert(0, bin_path)
|
||||
env['VIRTUAL_ENV'] = env_path
|
||||
if 'PYTHONHOME' in env:
|
||||
|
|
Loading…
Add table
Reference in a new issue