Merge remote-tracking branch 'upstream/master'

This commit is contained in:
halloleo 2018-12-17 18:06:49 +11:00
commit 8e28a2e8af
48 changed files with 580 additions and 281 deletions

View file

@ -1,4 +1,4 @@
version: 0.8.3.{build}
version: 0.8.5.{build}
os: Windows Server 2012 R2
environment:

View file

@ -5,7 +5,10 @@ IF "%XONSH_TEST_ENV%" == "MSYS2" (
%MSYS2_PATH%\usr\bin\pacman.exe -Syu --noconfirm
%MSYS2_PATH%\usr\bin\pacman.exe -S --noconfirm python3 python3-pip
%MSYS2_PATH%\usr\bin\bash.exe -c "/usr/bin/pip install -r requirements-tests.txt"
%MSYS2_PATH%\usr\bin\bash.exe -c "/usr/bin/python setup.py install"
) ELSE (
echo "Windows Environment"
%PYTHON%\Scripts\pip install -r requirements-tests.txt --upgrade --upgrade-strategy eager
%PYTHON%\python.exe --version
%PYTHON%\python.exe setup.py install
)

View file

@ -5,8 +5,10 @@ IF "%XONSH_TEST_ENV%" == "MSYS2" (
REM We monkey path `py._path.local.PosixPath` here such that it does not
REM allow to create symlinks which are not supported by MSYS2 anyway. As a
REM result the other pytest code uses a workaround.
call %MSYS2_PATH%\usr\bin\bash.exe -c "/usr/bin/python -u -c 'import py._path.local; del py._path.local.PosixPath.mksymlinkto; import pytest; raise SystemExit(pytest.main())'" || EXIT 1
SET "PATH=%MSYS2_PATH%\usr\bin;%PATH%"
call bash.exe -c "/usr/bin/xonsh run-tests.xsh" || EXIT 1
) ELSE (
echo "Windows Environment"
call %PYTHON%\Scripts\py.test || EXIT 1
SET "PATH=%PYTHON%\Scripts;%PATH%"
call xonsh run-tests.xsh || EXIT 1
)

View file

@ -43,7 +43,9 @@ jobs:
pip install . --no-deps
- run:
command: |
/home/circleci/miniconda/envs/py34-xonsh-test/bin/pytest --timeout=10
export PATH="$HOME/miniconda/bin:$PATH"
source activate ${ENV_NAME}
xonsh run-tests.xsh --timeout=10
build_35:
machine: true
environment:
@ -87,7 +89,9 @@ jobs:
pip install . --no-deps
- run:
command: |
/home/circleci/miniconda/envs/py35-xonsh-test/bin/pytest --timeout=10
export PATH="$HOME/miniconda/bin:$PATH"
source activate ${ENV_NAME}
xonsh run-tests.xsh --timeout=10
build_36:
machine: true
environment:
@ -131,7 +135,9 @@ jobs:
pip install . --no-deps
- run:
command: |
/home/circleci/miniconda/envs/py36-xonsh-test/bin/pytest --timeout=10 --flake8 --cov=./xonsh
export PATH="$HOME/miniconda/bin:$PATH"
source activate ${ENV_NAME}
xonsh run-tests.xsh --timeout=10 --flake8 --cov=./xonsh
build_37:
machine: true
environment:
@ -175,7 +181,9 @@ jobs:
pip install . --no-deps
- run:
command: |
/home/circleci/miniconda/envs/py37-xonsh-test/bin/pytest --timeout=10 --flake8 --cov=./xonsh
export PATH="$HOME/miniconda/bin:$PATH"
source activate ${ENV_NAME}
xonsh run-tests.xsh --timeout=10 --flake8 --cov=./xonsh
build_black:
machine: true
steps:

View file

@ -51,6 +51,7 @@ install:
python setup.py install;
else
pip install --upgrade -r requirements-tests.txt;
python setup.py install;
fi
before_script:
@ -71,6 +72,6 @@ script:
cd ..;
doctr deploy --deploy-repo xonsh/xonsh-docs .;
else
py.test --timeout=10;
xonsh run-tests.xsh --timeout=10;
fi

View file

@ -4,6 +4,125 @@ Xonsh Change Log
.. current developments
v0.8.5
====================
**Added:**
* Add alias to `base16 shell <https://github.com/chriskempson/base16-shell>`_
* Installation / Usage
1. To install use pip
.. code-block:: bash
python3 -m pip install xontrib-base16-shell
2. Add on ``~/.xonshrc``
.. code:: python
:number-lines:
$BASE16_SHELL = $HOME + "/.config/base16-shell/"
xontrib load base16_shell
3. See image
.. image:: https://raw.githubusercontent.com/ErickTucto/xontrib-base16-shell/master/docs/terminal.png
:width: 600px
:alt: terminal.png
* New ``DumbShell`` class that kicks in whenever ``$TERM == "dumb"``.
This usually happens in emacs. Currently, this class inherits from
the ``ReadlineShell`` but adds some light customization to make
sure that xonsh looks good in the resultant terminal emulator.
* Aliases from foreign shells (e.g. Bash) that are more than single expressions,
or contain sub-shell executions, are now evaluated and run in the foreign shell.
Previously, xonsh would attempt to translate the alias from sh-lang into
xonsh. These restrictions have been removed. For example, the following now
works:
.. code-block:: sh
$ source-bash 'alias eee="echo aaa \$(echo b)"'
$ eee
aaa b
* New ``ForeignShellBaseAlias``, ``ForeignShellFunctionAlias``, and
``ForeignShellExecAlias`` classes have been added which manage foreign shell
alias execution.
**Changed:**
* String aliases will now first be checked to see if they contain sub-expressions
that require evaluations, such as ``@(expr)``, ``$[cmd]``, etc. If they do,
then an ``ExecAlias`` will be constructed, rather than a simple list-of-strs
substitutiuon alias being used. For example:
.. code-block:: sh
$ aliases['uuu'] = "echo ccc $(echo ddd)"
$ aliases['uuu']
ExecAlias('echo ccc $(echo ddd)\n', filename='<exec-alias:uuu>')
$ uuu
ccc ddd
* The ``parse_aliases()`` function now requires the shell name.
* ``ForeignShellFunctionAlias`` now inherits from ``ForeignShellBaseAlias``
rather than ``object``.
**Fixed:**
* Fixed issues where the prompt-toolkit v2 shell would print an extra newline
after Python evaluations in interactive mode.
v0.8.4
====================
**Added:**
* Added the possibility of arbitrary paths to the help strings in ``vox activate`` and
``vox remove``; also updated the documentation accordingly.
* New ``xonsh.aliases.ExecAlias`` class enables multi-statement aliases.
* New ``xonsh.ast.isexpression()`` function will return a boolean of whether
code is a simple xonsh expression or not.
* Added top-level ``run-tests.xsh`` script for safely running the test suite.
**Changed:**
* String aliases are no longer split with ``shlex.split()``, but instead use
``xonsh.lexer.Lexer.split()``.
* Update xonsh/prompt/cwd.py _collapsed_pwd to print 2 chars if a directory begins with "."
* test which determines whether a directory is a virtualenv
previously it used to check the existence of 'pyvenv.cfg'
now it checks if 'bin/python' is executable
**Fixed:**
* Fixed issue with ``and`` & ``or`` being incorrectly tokenized in implicit
subprocesses. Auto-wrapping of certain subprocesses will now correctly work.
For example::
$ echo x-and-y
x-and-y
* Fix EOFError when press `control+d`
* fix no candidates if no permission files in PATH
* Fixed interpretation of color names with PTK2 and Pygments 2.3.
* Several ResourceWarnings: unclosed file in tests
* AttributeError crash when using --timings flag
* issue #2929
v0.8.3
====================

View file

@ -20,15 +20,16 @@ jobs:
architecture: 'x64'
# Conda Environment
# Create and activate a Conda environment.
# Create and activate a Conda environment.
- task: CondaEnvironment@1
inputs:
packageSpecs: 'python=$(python.version) pygments prompt_toolkit ply pytest pytest-timeout numpy psutil matplotlib flake8 coverage pyflakes pytest-cov pytest-flake8 codecov'
installOptions: '-c conda-forge'
updateConda: false
- script: |
pytest --timeout=10 --junitxml=junit/test-results.xml
displayName: 'pytest'
pip install .
xonsh run-tests.xsh --timeout=10 --junitxml=junit/test-results.xml
displayName: 'Tests'
# Publish build results
- task: PublishTestResults@2

10
docs/api/dumb_shell.rst Normal file
View file

@ -0,0 +1,10 @@
.. _xonsh_dumb_shell:
******************************************************
Dumb Shell (``xonsh.dumb_shell``)
******************************************************
.. automodule:: xonsh.dumb_shell
:members:
:undoc-members:
:inherited-members:

View file

@ -74,6 +74,7 @@ For those of you who want the gritty details.
pyghooks
jupyter_kernel
jupyter_shell
dumb_shell
wizard
xonfig
codecache

View file

@ -1122,7 +1122,14 @@ matching only occurs for the first element of a subprocess command.
The keys of ``aliases`` are strings that act as commands in subprocess-mode.
The values are lists of strings, where the first element is the command, and
the rest are the arguments. You can also set the value to a string, in which
case it will be converted to a list automatically with ``shlex.split``.
one of two things will happen:
1. If the string is a xonsh expression, it will be converted to a list
automatically with xonsh's ``Lexer.split()`` method.
2. If the string is more complex (representing a block of xonsh code),
the alias will be registered as an ``ExecAlias``, which is a callable
alias. This block of code will then be executed whenever the alias is
run.
For example, the following creates several aliases for the ``git``
version control software. Both styles (list of strings and single

View file

@ -1,28 +0,0 @@
**Added:**
* <news item>
**Changed:**
* <news item>
**Deprecated:**
* <news item>
**Removed:**
* <news item>
**Fixed:**
* Fixed issue with ``and`` & ``or`` being incoreectly tokenized in implicit
subprocesses. Auto-wrapping of certain subprocesses will now correctly work.
For example::
$ echo x-and-y
x-and-y
**Security:**
* <news item>

View file

@ -1,24 +0,0 @@
**Added:**
* Added the possibility of arbitrary paths to the help strings in ``vox activate`` and
``vox remove``; also updated the documentation accordingly.
**Changed:**
* <news item>
**Deprecated:**
* <news item>
**Removed:**
* <news item>
**Fixed:**
* <news item>
**Security:**
* <news item>

View file

@ -1,23 +0,0 @@
**Added:**
* <news item>
**Changed:**
* <news item>
**Deprecated:**
* <news item>
**Removed:**
* <news item>
**Fixed:**
* fix no candidates if no permission files in PATH
**Security:**
* <news item>

View file

@ -1,23 +0,0 @@
**Added:**
* <news item>
**Changed:**
* <news item>
**Deprecated:**
* <news item>
**Removed:**
* <news item>
**Fixed:**
* Several ResourceWarnings: unclosed file in tests
**Security:**
* <news item>

View file

@ -1,23 +0,0 @@
**Added:**
* <news item>
**Changed:**
* <news item>
**Deprecated:**
* <news item>
**Removed:**
* <news item>
**Fixed:**
* AttributeError crash when using --timings flag
**Security:**
* <news item>

View file

@ -1,23 +0,0 @@
**Added:**
* <news item>
**Changed:**
* Update xonsh/prompt/cwd.py _collapsed_pwd to print 2 chars if a directory begins with "."
**Deprecated:**
* <news item>
**Removed:**
* <news item>
**Fixed:**
* <news item>
**Security:**
* <news item>

View file

@ -1,26 +0,0 @@
**Added:**
* <news item>
**Changed:**
* test which determines whether a directory is a virtualenv
previously it used to check the existence of 'pyvenv.cfg'
now it checks if 'bin/python' is executable
**Deprecated:**
* <news item>
**Removed:**
* <news item>
**Fixed:**
* issue #2929
**Security:**
* <news item>

View file

@ -12,6 +12,8 @@ $VERSION_BUMP_PATTERNS = [
$CHANGELOG_FILENAME = 'CHANGELOG.rst'
$CHANGELOG_TEMPLATE = 'TEMPLATE.rst'
$PYTEST_COMMAND = "./run-tests.xsh"
$TAG_REMOTE = 'git@github.com:xonsh/xonsh.git'
$TAG_TARGET = 'master'

10
run-tests.xsh Executable file
View file

@ -0,0 +1,10 @@
#!/usr/bin/env xonsh
$RAISE_SUBPROC_ERROR = True
run_separately = [
'tests/test_ptk_highlight.py',
]
![pytest @($ARGS[1:]) --ignore @(run_separately)]
for fname in run_separately:
![pytest @($ARGS[1:]) @(fname)]

View file

@ -33,8 +33,24 @@ def source_path():
return os.path.dirname(pwd)
def ensure_attached_session(session):
def ensure_attached_session(monkeypatch, session):
for i in range(1, 11):
# next try to monkey patch with raising.
try:
monkeypatch.setattr(builtins, "__xonsh__", session, raising=True)
except AttributeError:
pass
if hasattr(builtins, "__xonsh__"):
break
# first try to monkey patch without raising.
try:
monkeypatch.setattr(builtins, "__xonsh__", session, raising=False)
except AttributeError:
pass
if hasattr(builtins, "__xonsh__"):
break
# now just try to apply it
builtins.__xonsh__ = session
if hasattr(builtins, "__xonsh__"):
break
@ -56,11 +72,15 @@ def xonsh_execer(monkeypatch):
"xonsh.built_ins.load_builtins.__code__",
(lambda *args, **kwargs: None).__code__,
)
added_session = False
if not hasattr(builtins, "__xonsh__"):
ensure_attached_session(XonshSession())
added_session = True
ensure_attached_session(monkeypatch, XonshSession())
execer = Execer(unload=False)
builtins.__xonsh__.execer = execer
return execer
yield execer
if added_session:
monkeypatch.delattr(builtins, "__xonsh__", raising=False)
@pytest.fixture
@ -71,7 +91,7 @@ def monkeypatch_stderr(monkeypatch):
yield
@pytest.yield_fixture
@pytest.fixture
def xonsh_events():
yield events
for name, oldevent in vars(events).items():
@ -81,13 +101,13 @@ def xonsh_events():
setattr(events, name, newevent)
@pytest.yield_fixture
def xonsh_builtins(xonsh_events):
@pytest.fixture
def xonsh_builtins(monkeypatch, xonsh_events):
"""Mock out most of the builtins xonsh attributes."""
old_builtins = set(dir(builtins))
execer = getattr(getattr(builtins, "__xonsh__", None), "execer", None)
session = XonshSession(execer=execer, ctx={})
ensure_attached_session(session)
ensure_attached_session(monkeypatch, session)
builtins.__xonsh__.env = DummyEnv()
if ON_WINDOWS:
builtins.__xonsh__.env["PATHEXT"] = [".EXE", ".BAT", ".CMD"]
@ -131,6 +151,7 @@ def xonsh_builtins(xonsh_events):
# be firing events on the global instance.
builtins.events = xonsh_events
yield builtins
monkeypatch.delattr(builtins, "__xonsh__", raising=False)
for attr in set(dir(builtins)) - old_builtins:
if hasattr(builtins, attr):
delattr(builtins, attr)

View file

@ -17,17 +17,19 @@ def cd(args, stdin=None):
return args
ALIASES = Aliases(
{"o": ["omg", "lala"]},
color_ls=["ls", "--color=true"],
ls="ls '- -'",
cd=cd,
indirect_cd="cd ..",
)
RAW = ALIASES._raw
def make_aliases():
ales = Aliases(
{"o": ["omg", "lala"]},
color_ls=["ls", "--color=true"],
ls="ls '- -'",
cd=cd,
indirect_cd="cd ..",
)
return ales
def test_imports():
def test_imports(xonsh_execer, xonsh_builtins):
ales = make_aliases()
expected = {
"o": ["omg", "lala"],
"ls": ["ls", "- -"],
@ -35,22 +37,27 @@ def test_imports():
"cd": cd,
"indirect_cd": ["cd", ".."],
}
assert RAW == expected
raw = ales._raw
assert raw == expected
def test_eval_normal(xonsh_builtins):
assert ALIASES.get("o") == ["omg", "lala"]
def test_eval_normal(xonsh_execer, xonsh_builtins):
ales = make_aliases()
assert ales.get("o") == ["omg", "lala"]
def test_eval_self_reference(xonsh_builtins):
assert ALIASES.get("ls") == ["ls", "- -"]
def test_eval_self_reference(xonsh_execer, xonsh_builtins):
ales = make_aliases()
assert ales.get("ls") == ["ls", "- -"]
def test_eval_recursive(xonsh_builtins):
assert ALIASES.get("color_ls") == ["ls", "- -", "--color=true"]
def test_eval_recursive(xonsh_execer, xonsh_builtins):
ales = make_aliases()
assert ales.get("color_ls") == ["ls", "- -", "--color=true"]
@skip_if_on_windows
def test_eval_recursive_callable_partial(xonsh_builtins):
def test_eval_recursive_callable_partial(xonsh_execer, xonsh_builtins):
ales = make_aliases()
xonsh_builtins.__xonsh__.env = Env(HOME=os.path.expanduser("~"))
assert ALIASES.get("indirect_cd")(["arg2", "arg3"]) == ["..", "arg2", "arg3"]
assert ales.get("indirect_cd")(["arg2", "arg3"]) == ["..", "arg2", "arg3"]

View file

@ -2,7 +2,7 @@
import ast as pyast
from xonsh import ast
from xonsh.ast import Tuple, Name, Store, min_line, Call, BinOp, pdump
from xonsh.ast import Tuple, Name, Store, min_line, Call, BinOp, pdump, isexpression
import pytest
@ -49,7 +49,7 @@ def test_gather_load_store_names_tuple():
"l = 1",
],
)
def test_multilline_num(line1):
def test_multilline_num(xonsh_execer, line1):
code = line1 + "\nls -l\n"
tree = check_parse(code)
lsnode = tree.body[1]
@ -115,10 +115,28 @@ def test_unmodified(inp):
assert nodes_equal(exp, obs)
@pytest.mark.parametrize("test_input", [
"echo; echo && echo\n",
"echo; echo && echo a\n",
"true && false && true\n",
])
@pytest.mark.parametrize(
"test_input",
["echo; echo && echo\n", "echo; echo && echo a\n", "true && false && true\n"],
)
def test_whitespace_subproc(test_input):
assert check_parse(test_input)
@pytest.mark.parametrize(
"inp,exp",
[
("1+1", True),
("1+1;", True),
("1+1\n", True),
("1+1; 2+2", False),
("1+1; 2+2;", False),
("1+1; 2+2\n", False),
("1+1; 2+2;\n", False),
("x = 42", False),
],
)
def test_isexpression(xonsh_execer, inp, exp):
obs = isexpression(inp)
assert exp is obs

View file

@ -37,7 +37,7 @@ pytestmark = pytest.mark.skipif(
)
@pytest.yield_fixture(scope="module")
@pytest.fixture(scope="module")
def shares_setup(tmpdir_factory):
"""create some shares to play with on current machine.
@ -218,8 +218,8 @@ def test_uncpushd_push_base_push_rempath(xonsh_builtins):
pass
# really? Need to cut-and-paste 2 flavors of this? yield_fixture requires yield in defined function body, not callee
@pytest.yield_fixture()
# really? Need to cut-and-paste 2 flavors of this? fixture requires yield in defined function body, not callee
@pytest.fixture()
def with_unc_check_enabled():
if not ON_WINDOWS:
return
@ -251,7 +251,7 @@ def with_unc_check_enabled():
winreg.CloseKey(key)
@pytest.yield_fixture()
@pytest.fixture()
def with_unc_check_disabled(): # just like the above, but value is 1 to *disable* unc check
if not ON_WINDOWS:
return

View file

@ -64,7 +64,7 @@ def test_parse_aliases():
"__XONSH_ALIAS_END__\n"
"more filth"
)
obs = parse_aliases(s)
obs = parse_aliases(s, 'bash')
assert exp == obs

View file

@ -15,7 +15,7 @@ from xonsh.history.main import history_main, _xh_parse_args, construct_history
CMDS = ["ls", "cat hello kitty", "abc", "def", "touch me", "grep from me"]
@pytest.yield_fixture
@pytest.fixture
def hist():
h = JsonHistory(
filename="xonsh-HISTORY-TEST.json", here="yup", sessionid="SESSIONID", gc=False

View file

@ -10,7 +10,7 @@ from xonsh.history.main import history_main
import pytest
@pytest.yield_fixture
@pytest.fixture
def hist():
h = SqliteHistory(
filename="xonsh-HISTORY-TEST.sqlite", sessionid="SESSIONID", gc=False

View file

@ -13,8 +13,8 @@ from xonsh.built_ins import unload_builtins
imphooks.install_import_hooks()
@pytest.yield_fixture(autouse=True)
def imp_env():
@pytest.fixture(autouse=True)
def imp_env(xonsh_builtins):
execer = Execer(unload=False)
builtins.__xonsh__.env = Env({"PATH": [], "PATHEXT": []})
yield

View file

@ -1,9 +1,16 @@
import os
import tempfile
from xonsh.lib.os import indir, rmtree
import pytest
from tools import ON_WINDOWS
def test_indir():
if ON_WINDOWS:
pytest.skip("On Windows")
with tempfile.TemporaryDirectory() as tmpdir:
assert ![pwd].output.strip() != tmpdir
with indir(tmpdir):

View file

@ -4,10 +4,14 @@ import tempfile
from xonsh.lib.os import indir
from xonsh.lib.subprocess import run, check_call, check_output, CalledProcessError
from tools import skip_if_on_windows
import pytest
from tools import ON_WINDOWS
def test_run():
if ON_WINDOWS:
pytest.skip("On Windows")
with tempfile.TemporaryDirectory() as tmpdir:
with indir(tmpdir):
run(['touch', 'hello.txt'])
@ -19,6 +23,8 @@ def test_run():
def test_check_call():
if ON_WINDOWS:
pytest.skip("On Windows")
with tempfile.TemporaryDirectory() as tmpdir:
with indir(tmpdir):
check_call(['touch', 'hello.txt'])
@ -29,8 +35,9 @@ def test_check_call():
assert 'tst_dir/hello.txt' in g`tst_dir/*.txt`
@skip_if_on_windows
def test_check_call_raises():
if ON_WINDOWS:
pytest.skip("On Windows")
try:
check_call('false')
got_raise = False
@ -40,6 +47,8 @@ def test_check_call_raises():
def test_check_output():
if ON_WINDOWS:
pytest.skip("On Windows")
with tempfile.TemporaryDirectory() as tmpdir:
with indir(tmpdir):
check_call(['touch', 'hello.txt'])
@ -49,4 +58,3 @@ def test_check_output():
p = check_output(['touch', 'hello.txt'], cwd='tst_dir')
assert p.decode('utf-8') == ''
assert 'tst_dir/hello.txt' in g`tst_dir/*.txt`

View file

@ -19,11 +19,12 @@ from tools import skip_if_on_windows
from xonsh.platform import ON_WINDOWS
from xonsh.built_ins import load_builtins, unload_builtins
from xonsh.execer import Execer
from xonsh.pyghooks import XonshLexer
@pytest.yield_fixture(autouse=True)
def load_command_cache():
@pytest.fixture(autouse=True)
def load_command_cache(xonsh_builtins):
load_builtins()
if ON_WINDOWS:
for key in ("cd", "bash"):
@ -58,10 +59,12 @@ def test_bin_ls():
check_token("/bin/ls -al", [(Name.Builtin, "/bin/ls")])
@skip_if_on_windows
def test_py_print():
check_token('print("hello")', [(Keyword, "print"), (String.Double, "hello")])
@skip_if_on_windows
def test_invalid_cmd():
check_token("non-existance-cmd -al", [(Name, "non")]) # parse as python
check_token(
@ -71,6 +74,7 @@ def test_invalid_cmd():
check_token("(1, )", [(Punctuation, "("), (Number.Integer, "1")])
@skip_if_on_windows
def test_multi_cmd():
check_token(
"cd && cd", [(Name.Builtin, "cd"), (Operator, "&&"), (Name.Builtin, "cd")]
@ -81,6 +85,7 @@ def test_multi_cmd():
)
@skip_if_on_windows
def test_nested():
check_token(
'echo @("hello")',
@ -117,6 +122,7 @@ def test_nested():
)
@skip_if_on_windows
def test_path(tmpdir):
test_dir = str(tmpdir.mkdir("xonsh-test-highlight-path"))
check_token(
@ -132,10 +138,12 @@ def test_path(tmpdir):
check_token(test_dir, [(Name.Constant, test_dir)])
@skip_if_on_windows
def test_subproc_args():
check_token("cd 192.168.0.1", [(Text, "192.168.0.1")])
@skip_if_on_windows
def test_backtick():
check_token(
r"echo g`.*\w+`",
@ -149,6 +157,7 @@ def test_backtick():
)
@skip_if_on_windows
def test_macro():
check_token(
r"g!(42, *, 65)",

View file

@ -18,7 +18,7 @@ from tools import DummyEnv, skip_if_lt_ptk2
Context = namedtuple("Context", ["indent", "buffer", "accept", "cli", "cr"])
@pytest.yield_fixture(scope="module")
@pytest.fixture(scope="module")
def ctx():
"""Context in which the ptk multiline functionality will be tested."""
builtins.__xonsh__ = XonshSession()

View file

@ -15,7 +15,7 @@ from tools import skip_if_on_darwin
HISTDIR = os.path.join(os.path.dirname(__file__), "histories")
@pytest.yield_fixture(scope="module", autouse=True)
@pytest.fixture(scope="module", autouse=True)
def ctx():
"""Create a global Shell instance to use in all the test."""
ctx = {"PATH": []}

View file

@ -64,6 +64,7 @@ from xonsh.tools import (
is_balanced,
subexpr_before_unbalanced,
swap_values,
get_line_continuation,
get_logical_line,
replace_logical_line,
check_quotes,
@ -521,7 +522,8 @@ def test_replace_logical_line(src, idx, exp_line, exp_n):
idx -= 1
replace_logical_line(lines, logical, idx, exp_n)
exp = src.replace("\\\n", "").strip()
obs = "\n".join(lines).replace("\\\n", "").strip()
lc = get_line_continuation() + "\n"
obs = "\n".join(lines).replace(lc, "").strip()
assert exp == obs

View file

@ -9,7 +9,7 @@ def test_load_xontrib_metadata():
xontrib_metadata()
@pytest.yield_fixture
@pytest.fixture
def tmpmod(tmpdir):
"""
Same as tmpdir but also adds/removes it to the front of sys.path.

View file

@ -30,6 +30,9 @@ ON_CONDA = True in [
conda in pytest.__file__.lower() for conda in ["conda", "anaconda", "miniconda"]
]
ON_TRAVIS = "TRAVIS" in os.environ and "CI" in os.environ
ON_AZURE_PIPELINES = os.environ.get("TF_BUILD", "") == "True"
print("ON_AZURE_PIPELINES", repr(ON_AZURE_PIPELINES))
print("os.environ['TF_BUILD']", repr(os.environ.get("TF_BUILD", "")))
TEST_DIR = os.path.dirname(__file__)
# pytest skip decorators
@ -48,6 +51,8 @@ skip_if_on_msys = pytest.mark.skipif(
skip_if_on_windows = pytest.mark.skipif(ON_WINDOWS, reason="Unix stuff")
skip_if_on_azure_pipelines = pytest.mark.skipif(ON_AZURE_PIPELINES, reason="not suitable for azure")
skip_if_on_unix = pytest.mark.skipif(not ON_WINDOWS, reason="Windows stuff")
skip_if_on_darwin = pytest.mark.skipif(ON_DARWIN, reason="not Mac friendly")

View file

@ -1,4 +1,4 @@
__version__ = "0.8.3"
__version__ = "0.8.5"
# amalgamate exclude jupyter_kernel parser_table parser_test_table pyghooks
@ -86,6 +86,8 @@ else:
_sys.modules["xonsh.tracer"] = __amalgam__
aliases = __amalgam__
_sys.modules["xonsh.aliases"] = __amalgam__
dumb_shell = __amalgam__
_sys.modules["xonsh.dumb_shell"] = __amalgam__
built_ins = __amalgam__
_sys.modules["xonsh.built_ins"] = __amalgam__
execer = __amalgam__

View file

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
"""Aliases for the xonsh shell."""
import os
import re
import sys
import shlex
import inspect
import argparse
import builtins
@ -24,14 +24,26 @@ from xonsh.platform import (
from xonsh.tools import unthreadable, print_color
from xonsh.replay import replay_main
from xonsh.timings import timeit_alias
from xonsh.tools import argvquote, escape_windows_cmd_string, to_bool, swap_values
from xonsh.tools import (
argvquote,
escape_windows_cmd_string,
to_bool,
swap_values,
strip_simple_quotes,
)
from xonsh.xontribs import xontribs_main
from xonsh.ast import isexpression
import xonsh.completers._aliases as xca
import xonsh.history.main as xhm
import xonsh.xoreutils.which as xxw
@lazyobject
def SUB_EXEC_ALIAS_RE():
return re.compile(r"@\(|\$\(|!\(|\$\[|!\[")
class Aliases(cabc.MutableMapping):
"""Represents a location to hold and look up aliases."""
@ -115,7 +127,17 @@ class Aliases(cabc.MutableMapping):
def __setitem__(self, key, val):
if isinstance(val, str):
self._raw[key] = shlex.split(val)
f = "<exec-alias:" + key + ">"
if SUB_EXEC_ALIAS_RE.search(val) is not None:
# We have a sub-command, e.g. $(cmd), to evaluate
self._raw[key] = ExecAlias(val, filename=f)
elif isexpression(val):
# expansion substitution
lexer = builtins.__xonsh__.execer.parser.lexer
self._raw[key] = list(map(strip_simple_quotes, lexer.split(val)))
else:
# need to exec alias
self._raw[key] = ExecAlias(val, filename=f)
else:
self._raw[key] = val
@ -150,6 +172,37 @@ class Aliases(cabc.MutableMapping):
p.pretty(dict(self))
class ExecAlias:
"""Provides a callable alias for xonsh source code."""
def __init__(self, src, filename="<exec-alias>"):
"""
Parameters
----------
src : str
Source code that will be
"""
self.src = src if src.endswith("\n") else src + "\n"
self.filename = filename
def __call__(
self, args, stdin=None, stdout=None, stderr=None, spec=None, stack=None
):
execer = builtins.__xonsh__.execer
frame = stack[0][0] # execute as though we are at the call site
execer.exec(
self.src, glbs=frame.f_globals, locs=frame.f_locals, filename=self.filename
)
def __repr__(self):
return "ExecAlias({0!r}, filename={1!r})".format(self.src, self.filename)
#
# Actual aliases below
#
def xonsh_exit(args, stdin=None):
"""Sends signal to exit shell."""
if not clean_jobs():

View file

@ -3,6 +3,7 @@
# These are imported into our module namespace for the benefit of parser.py.
# pylint: disable=unused-import
import sys
import builtins
from ast import (
Module,
Num,
@ -302,6 +303,26 @@ def isdescendable(node):
return isinstance(node, (UnaryOp, BoolOp))
def isexpression(node, ctx=None, *args, **kwargs):
"""Determines whether a node (or code string) is an expression, and
does not contain any statements. The execution context (ctx) and
other args and kwargs are passed down to the parser, as needed.
"""
# parse string to AST
if isinstance(node, str):
node = node if node.endswith("\n") else node + "\n"
ctx = builtins.__xonsh__.ctx if ctx is None else ctx
node = builtins.__xonsh__.execer.parse(node, ctx, *args, **kwargs)
# determin if expresission-like enough
if isinstance(node, (Expr, Expression)):
isexpr = True
elif isinstance(node, Module) and len(node.body) == 1:
isexpr = isinstance(node.body[0], (Expr, Expression))
else:
isexpr = False
return isexpr
class CtxAwareTransformer(NodeTransformer):
"""Transforms a xonsh AST based to use subprocess calls when
the first name in an expression statement is not known in the context.

12
xonsh/dumb_shell.py Normal file
View file

@ -0,0 +1,12 @@
"""A dumb shell for when $TERM == 'dumb', which usually happens in emacs."""
import builtins
from xonsh.readline_shell import ReadlineShell
class DumbShell(ReadlineShell):
"""A dumb shell for when $TERM == 'dumb', which usually happens in emacs."""
def __init__(self, *args, **kwargs):
builtins.__xonsh__.env["XONSH_COLOR_STYLE"] = "emacs"
super().__init__(*args, **kwargs)

View file

@ -298,7 +298,7 @@ def foreign_shell_data(
if use_tmpfile:
os.remove(tmpfile.name)
env = parse_env(s)
aliases = parse_aliases(s)
aliases = parse_aliases(s, shell=shell, sourcer=sourcer, extra_args=extra_args)
funcs = parse_funcs(s, shell=shell, sourcer=sourcer, extra_args=extra_args)
aliases.update(funcs)
return env, aliases
@ -332,7 +332,12 @@ def ALIAS_RE():
)
def parse_aliases(s):
@lazyobject
def FS_EXEC_ALIAS_RE():
return re.compile(r";|`|\$\(")
def parse_aliases(s, shell, sourcer=None, extra_args=()):
"""Parses the aliases portion of string into a dict."""
m = ALIAS_RE.search(s)
if m is None:
@ -352,7 +357,20 @@ def parse_aliases(s):
# strip one single quote at the start and end of value
if value[0] == "'" and value[-1] == "'":
value = value[1:-1]
value = shlex.split(value)
# now compute actual alias
if FS_EXEC_ALIAS_RE.search(value) is None:
# simple list of args alias
value = shlex.split(value)
else:
# alias is more complex, use ExecAlias, but via shell
filename = "<foreign-shell-exec-alias:" + key + ">"
value = ForeignShellExecAlias(
src=value,
shell=shell,
filename=filename,
sourcer=sourcer,
extra_args=extra_args,
)
except ValueError as exc:
warnings.warn(
'could not parse alias "{0}": {1!r}'.format(key, exc), RuntimeWarning
@ -400,7 +418,7 @@ def parse_funcs(s, shell, sourcer=None, extra_args=()):
if not os.path.isabs(filename):
filename = os.path.abspath(filename)
wrapper = ForeignShellFunctionAlias(
name=funcname,
funcname=funcname,
shell=shell,
sourcer=sourcer,
filename=filename,
@ -410,19 +428,17 @@ def parse_funcs(s, shell, sourcer=None, extra_args=()):
return funcs
class ForeignShellFunctionAlias(object):
class ForeignShellBaseAlias(object):
"""This class is responsible for calling foreign shell functions as if
they were aliases. This does not currently support taking stdin.
"""
INPUT = '{sourcer} "{filename}"\n' "{funcname} {args}\n"
INPUT = "echo ForeignShellBaseAlias {shell} {filename} {args}\n"
def __init__(self, name, shell, filename, sourcer=None, extra_args=()):
def __init__(self, shell, filename, sourcer=None, extra_args=()):
"""
Parameters
----------
name : str
function name
shell : str
Name or path to shell
filename : str
@ -433,36 +449,29 @@ class ForeignShellFunctionAlias(object):
Additional command line options to pass into the shell.
"""
sourcer = DEFAULT_SOURCERS.get(shell, "source") if sourcer is None else sourcer
self.name = name
self.shell = shell
self.filename = filename
self.sourcer = sourcer
self.extra_args = extra_args
def __eq__(self, other):
if (
not hasattr(other, "name")
or not hasattr(other, "filename")
or not hasattr(other, "sourcer")
or not hasattr(other, "exta_args")
):
return NotImplemented
return (
(self.name == other.name)
and (self.shell == other.shell)
and (self.filename == other.filename)
and (self.sourcer == other.sourcer)
and (self.extra_args == other.extra_args)
)
def _input_kwargs(self):
return {
"shell": self.shell,
"filename": self.filename,
"sourcer": self.sourcer,
"extra_args": self.extra_args,
}
def __call__(self, args, stdin=None):
def __eq__(self, other):
if not hasattr(other, "_input_kwargs") or not callable(other._input_kwargs):
return NotImplemented
return self._input_kwargs() == other._input_kwargs()
def __call__(
self, args, stdin=None, stdout=None, stderr=None, spec=None, stack=None
):
args, streaming = self._is_streaming(args)
input = self.INPUT.format(
sourcer=self.sourcer,
filename=self.filename,
funcname=self.name,
args=" ".join(args),
)
input = self.INPUT.format(args=" ".join(args), **self._input_kwargs())
cmd = [self.shell] + list(self.extra_args) + ["-c", input]
env = builtins.__xonsh__.env
denv = env.detype()
@ -478,7 +487,21 @@ class ForeignShellFunctionAlias(object):
out = out.replace("\r\n", "\n")
return out
def _is_streaming(self, args):
def __repr__(self):
return (
self.__class__.__name__
+ "("
+ ", ".join(
[
"{k}={v!r}".format(k=k, v=v)
for k, v in sorted(self._input_kwargs().items())
]
)
+ ")"
)
@staticmethod
def _is_streaming(args):
"""Test and modify args if --xonsh-stream is present."""
if "--xonsh-stream" not in args:
return args, False
@ -487,6 +510,77 @@ class ForeignShellFunctionAlias(object):
return args, True
class ForeignShellFunctionAlias(ForeignShellBaseAlias):
"""This class is responsible for calling foreign shell functions as if
they were aliases. This does not currently support taking stdin.
"""
INPUT = '{sourcer} "{filename}"\n' "{funcname} {args}\n"
def __init__(self, funcname, shell, filename, sourcer=None, extra_args=()):
"""
Parameters
----------
funcname : str
function name
shell : str
Name or path to shell
filename : str
Where the function is defined, path to source.
sourcer : str or None, optional
Command to source foreign files with.
extra_args : tuple of str, optional
Additional command line options to pass into the shell.
"""
super().__init__(
shell=shell, filename=filename, sourcer=sourcer, extra_args=extra_args
)
self.funcname = funcname
def _input_kwargs(self):
inp = super()._input_kwargs()
inp["funcname"] = self.funcname
return inp
class ForeignShellExecAlias(ForeignShellBaseAlias):
"""Provides a callable alias for source code in a foreign shell."""
INPUT = "{src} {args}\n"
def __init__(
self,
src,
shell,
filename="<foreign-shell-exec-alias>",
sourcer=None,
extra_args=(),
):
"""
Parameters
----------
src : str
Source code in the shell language
shell : str
Name or path to shell
filename : str
Where the function is defined, path to source.
sourcer : str or None, optional
Command to source foreign files with.
extra_args : tuple of str, optional
Additional command line options to pass into the shell.
"""
super().__init__(
shell=shell, filename=filename, sourcer=sourcer, extra_args=extra_args
)
self.src = src.strip()
def _input_kwargs(self):
inp = super()._input_kwargs()
inp["src"] = self.src
return inp
@lazyobject
def VALID_SHELL_PARAMS():
return frozenset(

View file

@ -257,7 +257,8 @@ def _pprint_displayhook(value):
printed_val = repr(value)
if HAS_PYGMENTS and env.get("COLOR_RESULTS"):
tokens = list(pygments.lex(printed_val, lexer=pyghooks.XonshLexer()))
print_color(tokens)
end = "" if env.get("SHELL_TYPE") == "prompt_toolkit2" else "\n"
print_color(tokens, end=end)
else:
print(printed_val) # black & white case
builtins._ = value

View file

@ -194,7 +194,9 @@ def ptk_below_max_supported():
@functools.lru_cache(1)
def best_shell_type():
if ON_WINDOWS or has_prompt_toolkit():
if builtins.__xonsh__.env.get("TERM", "") == "dumb":
return "dumb"
elif ON_WINDOWS or has_prompt_toolkit():
return "prompt_toolkit"
else:
return "readline"

View file

@ -148,7 +148,7 @@ def ctrl_d_condition():
empty.
"""
if builtins.__xonsh__.env.get("IGNOREEOF"):
raise EOFError
return False
else:
app = get_app()
buffer_name = app.current_buffer.name

View file

@ -1431,7 +1431,7 @@ def XonshTerminal256Formatter():
ptk_version_info()
and ptk_version_info() > (2, 0)
and pygments_version_info()
and (2, 2) <= pygments_version_info() < (2, 3)
and (2, 2, 0) <= pygments_version_info() <= (2, 3, 0)
):
# Monky patch pygments' dict of console codes
# with the new color names used by PTK2

View file

@ -119,6 +119,8 @@ class Shell(object):
shell_type_aliases = {
"b": "best",
"best": "best",
"d": "dumb",
"dumb": "dumb",
"ptk": "prompt_toolkit",
"ptk1": "prompt_toolkit1",
"ptk2": "prompt_toolkit2",
@ -166,6 +168,8 @@ class Shell(object):
shell_type = self.shell_type_aliases.get(shell_type, shell_type)
if shell_type == "best" or shell_type is None:
shell_type = best_shell_type()
elif env.get("TERM", "") == "dumb":
shell_type = "dumb"
elif shell_type == "random":
shell_type = random.choice(("readline", "prompt_toolkit"))
if shell_type == "prompt_toolkit":
@ -195,6 +199,8 @@ class Shell(object):
from xonsh.readline_shell import ReadlineShell as shell_class
elif shell_type == "jupyter":
from xonsh.jupyter_shell import JupyterShell as shell_class
elif shell_type == "dumb":
from xonsh.dumb_shell import DumbShell as shell_class
else:
raise XonshError("{} is not recognized as a shell type".format(shell_type))
self.shell = shell_class(execer=self.execer, ctx=self.ctx, **kwargs)

View file

@ -1979,6 +1979,38 @@ def RE_COMPLETE_STRING():
return re.compile(ptrn, re.DOTALL)
def strip_simple_quotes(s):
"""Gets rid of single quotes, double quotes, single triple quotes, and
single double quotes from a string, if present front and back of a string.
Otherwiswe, does nothing.
"""
starts_single = s.startswith("'")
starts_double = s.startswith('"')
if not starts_single and not starts_double:
return s
elif starts_single:
ends_single = s.endswith("'")
if not ends_single:
return s
elif s.startswith("'''") and s.endswith("'''") and len(s) >= 6:
return s[3:-3]
elif len(s) >= 2:
return s[1:-1]
else:
return s
else:
# starts double
ends_double = s.endswith('"')
if not ends_double:
return s
elif s.startswith('"""') and s.endswith('"""') and len(s) >= 6:
return s[3:-3]
elif len(s) >= 2:
return s[1:-1]
else:
return s
def check_for_partial_string(x):
"""Returns the starting index (inclusive), ending index (exclusive), and
starting quote string of the most recent Python string found in the input.

View file

@ -28,6 +28,11 @@
"its behavior. To see the modifications as they are applied (in unified diff",
"format), please set ``$XONSH_DEBUG`` to ``2`` or higher."]
},
{"name": "base16_shell",
"package": "xontrib-base16-shell",
"url": "https://github.com/ErickTucto/xontrib-base16-shell",
"description": ["Change base16 shell themes"]
},
{"name": "coreutils",
"package": "xonsh",
"url": "http://xon.sh",

View file

@ -50,18 +50,18 @@ class VoxHandler:
help='Activate virtual environment'
)
activate.add_argument('name', metavar='ENV',
help='The environment to activate. ENV can be '+
'either a name from the venvs shown by '+
'vox list or the path to an arbitrary venv')
help=('The environment to activate. ENV can be '
'either a name from the venvs shown by vox'
'list or the path to an arbitrary venv'))
subparsers.add_parser('deactivate', aliases=['exit'], help='Deactivate current virtual environment')
subparsers.add_parser('list', aliases=['ls'],
help='List environments available in '+
'$VIRTUALENV_HOME')
help=('List environments available in '
'$VIRTUALENV_HOME'))
remove = subparsers.add_parser('remove', aliases=['rm', 'delete', 'del'], help='Remove virtual environment')
remove.add_argument('names', metavar='ENV', nargs='+',
help='The environments to remove. ENV can be '+
'either a name from the venvs shown by '+
'vox list or the path to an arbitrary venv')
help=('The environments to remove. ENV can be '
'either a name from the venvs shown by vox'
'list or the path to an arbitrary venv'))
subparsers.add_parser('help', help='Show this help message')
return parser