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 os: Windows Server 2012 R2
environment: 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 -Syu --noconfirm
%MSYS2_PATH%\usr\bin\pacman.exe -S --noconfirm python3 python3-pip %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/pip install -r requirements-tests.txt"
%MSYS2_PATH%\usr\bin\bash.exe -c "/usr/bin/python setup.py install"
) ELSE ( ) ELSE (
echo "Windows Environment" echo "Windows Environment"
%PYTHON%\Scripts\pip install -r requirements-tests.txt --upgrade --upgrade-strategy eager %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 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 allow to create symlinks which are not supported by MSYS2 anyway. As a
REM result the other pytest code uses a workaround. 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 ( ) ELSE (
echo "Windows Environment" 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 pip install . --no-deps
- run: - run:
command: | 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: build_35:
machine: true machine: true
environment: environment:
@ -87,7 +89,9 @@ jobs:
pip install . --no-deps pip install . --no-deps
- run: - run:
command: | 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: build_36:
machine: true machine: true
environment: environment:
@ -131,7 +135,9 @@ jobs:
pip install . --no-deps pip install . --no-deps
- run: - run:
command: | 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: build_37:
machine: true machine: true
environment: environment:
@ -175,7 +181,9 @@ jobs:
pip install . --no-deps pip install . --no-deps
- run: - run:
command: | 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: build_black:
machine: true machine: true
steps: steps:

View file

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

View file

@ -4,6 +4,125 @@ Xonsh Change Log
.. current developments .. 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 v0.8.3
==================== ====================

View file

@ -20,15 +20,16 @@ jobs:
architecture: 'x64' architecture: 'x64'
# Conda Environment # Conda Environment
# Create and activate a Conda environment. # Create and activate a Conda environment.
- task: CondaEnvironment@1 - task: CondaEnvironment@1
inputs: inputs:
packageSpecs: 'python=$(python.version) pygments prompt_toolkit ply pytest pytest-timeout numpy psutil matplotlib flake8 coverage pyflakes pytest-cov pytest-flake8 codecov' 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' installOptions: '-c conda-forge'
updateConda: false updateConda: false
- script: | - script: |
pytest --timeout=10 --junitxml=junit/test-results.xml pip install .
displayName: 'pytest' xonsh run-tests.xsh --timeout=10 --junitxml=junit/test-results.xml
displayName: 'Tests'
# Publish build results # Publish build results
- task: PublishTestResults@2 - 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 pyghooks
jupyter_kernel jupyter_kernel
jupyter_shell jupyter_shell
dumb_shell
wizard wizard
xonfig xonfig
codecache 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 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 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 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`` For example, the following creates several aliases for the ``git``
version control software. Both styles (list of strings and single 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_FILENAME = 'CHANGELOG.rst'
$CHANGELOG_TEMPLATE = 'TEMPLATE.rst' $CHANGELOG_TEMPLATE = 'TEMPLATE.rst'
$PYTEST_COMMAND = "./run-tests.xsh"
$TAG_REMOTE = 'git@github.com:xonsh/xonsh.git' $TAG_REMOTE = 'git@github.com:xonsh/xonsh.git'
$TAG_TARGET = 'master' $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) return os.path.dirname(pwd)
def ensure_attached_session(session): def ensure_attached_session(monkeypatch, session):
for i in range(1, 11): 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 builtins.__xonsh__ = session
if hasattr(builtins, "__xonsh__"): if hasattr(builtins, "__xonsh__"):
break break
@ -56,11 +72,15 @@ def xonsh_execer(monkeypatch):
"xonsh.built_ins.load_builtins.__code__", "xonsh.built_ins.load_builtins.__code__",
(lambda *args, **kwargs: None).__code__, (lambda *args, **kwargs: None).__code__,
) )
added_session = False
if not hasattr(builtins, "__xonsh__"): if not hasattr(builtins, "__xonsh__"):
ensure_attached_session(XonshSession()) added_session = True
ensure_attached_session(monkeypatch, XonshSession())
execer = Execer(unload=False) execer = Execer(unload=False)
builtins.__xonsh__.execer = execer builtins.__xonsh__.execer = execer
return execer yield execer
if added_session:
monkeypatch.delattr(builtins, "__xonsh__", raising=False)
@pytest.fixture @pytest.fixture
@ -71,7 +91,7 @@ def monkeypatch_stderr(monkeypatch):
yield yield
@pytest.yield_fixture @pytest.fixture
def xonsh_events(): def xonsh_events():
yield events yield events
for name, oldevent in vars(events).items(): for name, oldevent in vars(events).items():
@ -81,13 +101,13 @@ def xonsh_events():
setattr(events, name, newevent) setattr(events, name, newevent)
@pytest.yield_fixture @pytest.fixture
def xonsh_builtins(xonsh_events): def xonsh_builtins(monkeypatch, xonsh_events):
"""Mock out most of the builtins xonsh attributes.""" """Mock out most of the builtins xonsh attributes."""
old_builtins = set(dir(builtins)) old_builtins = set(dir(builtins))
execer = getattr(getattr(builtins, "__xonsh__", None), "execer", None) execer = getattr(getattr(builtins, "__xonsh__", None), "execer", None)
session = XonshSession(execer=execer, ctx={}) session = XonshSession(execer=execer, ctx={})
ensure_attached_session(session) ensure_attached_session(monkeypatch, session)
builtins.__xonsh__.env = DummyEnv() builtins.__xonsh__.env = DummyEnv()
if ON_WINDOWS: if ON_WINDOWS:
builtins.__xonsh__.env["PATHEXT"] = [".EXE", ".BAT", ".CMD"] builtins.__xonsh__.env["PATHEXT"] = [".EXE", ".BAT", ".CMD"]
@ -131,6 +151,7 @@ def xonsh_builtins(xonsh_events):
# be firing events on the global instance. # be firing events on the global instance.
builtins.events = xonsh_events builtins.events = xonsh_events
yield builtins yield builtins
monkeypatch.delattr(builtins, "__xonsh__", raising=False)
for attr in set(dir(builtins)) - old_builtins: for attr in set(dir(builtins)) - old_builtins:
if hasattr(builtins, attr): if hasattr(builtins, attr):
delattr(builtins, attr) delattr(builtins, attr)

View file

@ -17,17 +17,19 @@ def cd(args, stdin=None):
return args return args
ALIASES = Aliases( def make_aliases():
{"o": ["omg", "lala"]}, ales = Aliases(
color_ls=["ls", "--color=true"], {"o": ["omg", "lala"]},
ls="ls '- -'", color_ls=["ls", "--color=true"],
cd=cd, ls="ls '- -'",
indirect_cd="cd ..", cd=cd,
) indirect_cd="cd ..",
RAW = ALIASES._raw )
return ales
def test_imports(): def test_imports(xonsh_execer, xonsh_builtins):
ales = make_aliases()
expected = { expected = {
"o": ["omg", "lala"], "o": ["omg", "lala"],
"ls": ["ls", "- -"], "ls": ["ls", "- -"],
@ -35,22 +37,27 @@ def test_imports():
"cd": cd, "cd": cd,
"indirect_cd": ["cd", ".."], "indirect_cd": ["cd", ".."],
} }
assert RAW == expected raw = ales._raw
assert raw == expected
def test_eval_normal(xonsh_builtins): def test_eval_normal(xonsh_execer, xonsh_builtins):
assert ALIASES.get("o") == ["omg", "lala"] ales = make_aliases()
assert ales.get("o") == ["omg", "lala"]
def test_eval_self_reference(xonsh_builtins): def test_eval_self_reference(xonsh_execer, xonsh_builtins):
assert ALIASES.get("ls") == ["ls", "- -"] ales = make_aliases()
assert ales.get("ls") == ["ls", "- -"]
def test_eval_recursive(xonsh_builtins): def test_eval_recursive(xonsh_execer, xonsh_builtins):
assert ALIASES.get("color_ls") == ["ls", "- -", "--color=true"] ales = make_aliases()
assert ales.get("color_ls") == ["ls", "- -", "--color=true"]
@skip_if_on_windows @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("~")) 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 import ast as pyast
from xonsh import ast 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 import pytest
@ -49,7 +49,7 @@ def test_gather_load_store_names_tuple():
"l = 1", "l = 1",
], ],
) )
def test_multilline_num(line1): def test_multilline_num(xonsh_execer, line1):
code = line1 + "\nls -l\n" code = line1 + "\nls -l\n"
tree = check_parse(code) tree = check_parse(code)
lsnode = tree.body[1] lsnode = tree.body[1]
@ -115,10 +115,28 @@ def test_unmodified(inp):
assert nodes_equal(exp, obs) assert nodes_equal(exp, obs)
@pytest.mark.parametrize("test_input", [
"echo; echo && echo\n", @pytest.mark.parametrize(
"echo; echo && echo a\n", "test_input",
"true && false && true\n", ["echo; echo && echo\n", "echo; echo && echo a\n", "true && false && true\n"],
]) )
def test_whitespace_subproc(test_input): def test_whitespace_subproc(test_input):
assert check_parse(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): def shares_setup(tmpdir_factory):
"""create some shares to play with on current machine. """create some shares to play with on current machine.
@ -218,8 +218,8 @@ def test_uncpushd_push_base_push_rempath(xonsh_builtins):
pass pass
# really? Need to cut-and-paste 2 flavors of this? yield_fixture requires yield in defined function body, not callee # really? Need to cut-and-paste 2 flavors of this? fixture requires yield in defined function body, not callee
@pytest.yield_fixture() @pytest.fixture()
def with_unc_check_enabled(): def with_unc_check_enabled():
if not ON_WINDOWS: if not ON_WINDOWS:
return return
@ -251,7 +251,7 @@ def with_unc_check_enabled():
winreg.CloseKey(key) 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 def with_unc_check_disabled(): # just like the above, but value is 1 to *disable* unc check
if not ON_WINDOWS: if not ON_WINDOWS:
return return

View file

@ -64,7 +64,7 @@ def test_parse_aliases():
"__XONSH_ALIAS_END__\n" "__XONSH_ALIAS_END__\n"
"more filth" "more filth"
) )
obs = parse_aliases(s) obs = parse_aliases(s, 'bash')
assert exp == obs 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"] CMDS = ["ls", "cat hello kitty", "abc", "def", "touch me", "grep from me"]
@pytest.yield_fixture @pytest.fixture
def hist(): def hist():
h = JsonHistory( h = JsonHistory(
filename="xonsh-HISTORY-TEST.json", here="yup", sessionid="SESSIONID", gc=False 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 import pytest
@pytest.yield_fixture @pytest.fixture
def hist(): def hist():
h = SqliteHistory( h = SqliteHistory(
filename="xonsh-HISTORY-TEST.sqlite", sessionid="SESSIONID", gc=False 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() imphooks.install_import_hooks()
@pytest.yield_fixture(autouse=True) @pytest.fixture(autouse=True)
def imp_env(): def imp_env(xonsh_builtins):
execer = Execer(unload=False) execer = Execer(unload=False)
builtins.__xonsh__.env = Env({"PATH": [], "PATHEXT": []}) builtins.__xonsh__.env = Env({"PATH": [], "PATHEXT": []})
yield yield

View file

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

View file

@ -4,10 +4,14 @@ import tempfile
from xonsh.lib.os import indir from xonsh.lib.os import indir
from xonsh.lib.subprocess import run, check_call, check_output, CalledProcessError 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(): def test_run():
if ON_WINDOWS:
pytest.skip("On Windows")
with tempfile.TemporaryDirectory() as tmpdir: with tempfile.TemporaryDirectory() as tmpdir:
with indir(tmpdir): with indir(tmpdir):
run(['touch', 'hello.txt']) run(['touch', 'hello.txt'])
@ -19,6 +23,8 @@ def test_run():
def test_check_call(): def test_check_call():
if ON_WINDOWS:
pytest.skip("On Windows")
with tempfile.TemporaryDirectory() as tmpdir: with tempfile.TemporaryDirectory() as tmpdir:
with indir(tmpdir): with indir(tmpdir):
check_call(['touch', 'hello.txt']) check_call(['touch', 'hello.txt'])
@ -29,8 +35,9 @@ def test_check_call():
assert 'tst_dir/hello.txt' in g`tst_dir/*.txt` assert 'tst_dir/hello.txt' in g`tst_dir/*.txt`
@skip_if_on_windows
def test_check_call_raises(): def test_check_call_raises():
if ON_WINDOWS:
pytest.skip("On Windows")
try: try:
check_call('false') check_call('false')
got_raise = False got_raise = False
@ -40,6 +47,8 @@ def test_check_call_raises():
def test_check_output(): def test_check_output():
if ON_WINDOWS:
pytest.skip("On Windows")
with tempfile.TemporaryDirectory() as tmpdir: with tempfile.TemporaryDirectory() as tmpdir:
with indir(tmpdir): with indir(tmpdir):
check_call(['touch', 'hello.txt']) check_call(['touch', 'hello.txt'])
@ -49,4 +58,3 @@ def test_check_output():
p = check_output(['touch', 'hello.txt'], cwd='tst_dir') p = check_output(['touch', 'hello.txt'], cwd='tst_dir')
assert p.decode('utf-8') == '' assert p.decode('utf-8') == ''
assert 'tst_dir/hello.txt' in g`tst_dir/*.txt` 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.platform import ON_WINDOWS
from xonsh.built_ins import load_builtins, unload_builtins from xonsh.built_ins import load_builtins, unload_builtins
from xonsh.execer import Execer
from xonsh.pyghooks import XonshLexer from xonsh.pyghooks import XonshLexer
@pytest.yield_fixture(autouse=True) @pytest.fixture(autouse=True)
def load_command_cache(): def load_command_cache(xonsh_builtins):
load_builtins() load_builtins()
if ON_WINDOWS: if ON_WINDOWS:
for key in ("cd", "bash"): for key in ("cd", "bash"):
@ -58,10 +59,12 @@ def test_bin_ls():
check_token("/bin/ls -al", [(Name.Builtin, "/bin/ls")]) check_token("/bin/ls -al", [(Name.Builtin, "/bin/ls")])
@skip_if_on_windows
def test_py_print(): def test_py_print():
check_token('print("hello")', [(Keyword, "print"), (String.Double, "hello")]) check_token('print("hello")', [(Keyword, "print"), (String.Double, "hello")])
@skip_if_on_windows
def test_invalid_cmd(): def test_invalid_cmd():
check_token("non-existance-cmd -al", [(Name, "non")]) # parse as python check_token("non-existance-cmd -al", [(Name, "non")]) # parse as python
check_token( check_token(
@ -71,6 +74,7 @@ def test_invalid_cmd():
check_token("(1, )", [(Punctuation, "("), (Number.Integer, "1")]) check_token("(1, )", [(Punctuation, "("), (Number.Integer, "1")])
@skip_if_on_windows
def test_multi_cmd(): def test_multi_cmd():
check_token( check_token(
"cd && cd", [(Name.Builtin, "cd"), (Operator, "&&"), (Name.Builtin, "cd")] "cd && cd", [(Name.Builtin, "cd"), (Operator, "&&"), (Name.Builtin, "cd")]
@ -81,6 +85,7 @@ def test_multi_cmd():
) )
@skip_if_on_windows
def test_nested(): def test_nested():
check_token( check_token(
'echo @("hello")', 'echo @("hello")',
@ -117,6 +122,7 @@ def test_nested():
) )
@skip_if_on_windows
def test_path(tmpdir): def test_path(tmpdir):
test_dir = str(tmpdir.mkdir("xonsh-test-highlight-path")) test_dir = str(tmpdir.mkdir("xonsh-test-highlight-path"))
check_token( check_token(
@ -132,10 +138,12 @@ def test_path(tmpdir):
check_token(test_dir, [(Name.Constant, test_dir)]) check_token(test_dir, [(Name.Constant, test_dir)])
@skip_if_on_windows
def test_subproc_args(): def test_subproc_args():
check_token("cd 192.168.0.1", [(Text, "192.168.0.1")]) check_token("cd 192.168.0.1", [(Text, "192.168.0.1")])
@skip_if_on_windows
def test_backtick(): def test_backtick():
check_token( check_token(
r"echo g`.*\w+`", r"echo g`.*\w+`",
@ -149,6 +157,7 @@ def test_backtick():
) )
@skip_if_on_windows
def test_macro(): def test_macro():
check_token( check_token(
r"g!(42, *, 65)", 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"]) Context = namedtuple("Context", ["indent", "buffer", "accept", "cli", "cr"])
@pytest.yield_fixture(scope="module") @pytest.fixture(scope="module")
def ctx(): def ctx():
"""Context in which the ptk multiline functionality will be tested.""" """Context in which the ptk multiline functionality will be tested."""
builtins.__xonsh__ = XonshSession() 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") HISTDIR = os.path.join(os.path.dirname(__file__), "histories")
@pytest.yield_fixture(scope="module", autouse=True) @pytest.fixture(scope="module", autouse=True)
def ctx(): def ctx():
"""Create a global Shell instance to use in all the test.""" """Create a global Shell instance to use in all the test."""
ctx = {"PATH": []} ctx = {"PATH": []}

View file

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

View file

@ -9,7 +9,7 @@ def test_load_xontrib_metadata():
xontrib_metadata() xontrib_metadata()
@pytest.yield_fixture @pytest.fixture
def tmpmod(tmpdir): def tmpmod(tmpdir):
""" """
Same as tmpdir but also adds/removes it to the front of sys.path. 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"] conda in pytest.__file__.lower() for conda in ["conda", "anaconda", "miniconda"]
] ]
ON_TRAVIS = "TRAVIS" in os.environ and "CI" in os.environ 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__) TEST_DIR = os.path.dirname(__file__)
# pytest skip decorators # 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_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_unix = pytest.mark.skipif(not ON_WINDOWS, reason="Windows stuff")
skip_if_on_darwin = pytest.mark.skipif(ON_DARWIN, reason="not Mac friendly") 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 # amalgamate exclude jupyter_kernel parser_table parser_test_table pyghooks
@ -86,6 +86,8 @@ else:
_sys.modules["xonsh.tracer"] = __amalgam__ _sys.modules["xonsh.tracer"] = __amalgam__
aliases = __amalgam__ aliases = __amalgam__
_sys.modules["xonsh.aliases"] = __amalgam__ _sys.modules["xonsh.aliases"] = __amalgam__
dumb_shell = __amalgam__
_sys.modules["xonsh.dumb_shell"] = __amalgam__
built_ins = __amalgam__ built_ins = __amalgam__
_sys.modules["xonsh.built_ins"] = __amalgam__ _sys.modules["xonsh.built_ins"] = __amalgam__
execer = __amalgam__ execer = __amalgam__

View file

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Aliases for the xonsh shell.""" """Aliases for the xonsh shell."""
import os import os
import re
import sys import sys
import shlex
import inspect import inspect
import argparse import argparse
import builtins import builtins
@ -24,14 +24,26 @@ from xonsh.platform import (
from xonsh.tools import unthreadable, print_color from xonsh.tools import unthreadable, print_color
from xonsh.replay import replay_main from xonsh.replay import replay_main
from xonsh.timings import timeit_alias 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.xontribs import xontribs_main
from xonsh.ast import isexpression
import xonsh.completers._aliases as xca import xonsh.completers._aliases as xca
import xonsh.history.main as xhm import xonsh.history.main as xhm
import xonsh.xoreutils.which as xxw import xonsh.xoreutils.which as xxw
@lazyobject
def SUB_EXEC_ALIAS_RE():
return re.compile(r"@\(|\$\(|!\(|\$\[|!\[")
class Aliases(cabc.MutableMapping): class Aliases(cabc.MutableMapping):
"""Represents a location to hold and look up aliases.""" """Represents a location to hold and look up aliases."""
@ -115,7 +127,17 @@ class Aliases(cabc.MutableMapping):
def __setitem__(self, key, val): def __setitem__(self, key, val):
if isinstance(val, str): 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: else:
self._raw[key] = val self._raw[key] = val
@ -150,6 +172,37 @@ class Aliases(cabc.MutableMapping):
p.pretty(dict(self)) 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): def xonsh_exit(args, stdin=None):
"""Sends signal to exit shell.""" """Sends signal to exit shell."""
if not clean_jobs(): if not clean_jobs():

View file

@ -3,6 +3,7 @@
# These are imported into our module namespace for the benefit of parser.py. # These are imported into our module namespace for the benefit of parser.py.
# pylint: disable=unused-import # pylint: disable=unused-import
import sys import sys
import builtins
from ast import ( from ast import (
Module, Module,
Num, Num,
@ -302,6 +303,26 @@ def isdescendable(node):
return isinstance(node, (UnaryOp, BoolOp)) 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): class CtxAwareTransformer(NodeTransformer):
"""Transforms a xonsh AST based to use subprocess calls when """Transforms a xonsh AST based to use subprocess calls when
the first name in an expression statement is not known in the context. 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: if use_tmpfile:
os.remove(tmpfile.name) os.remove(tmpfile.name)
env = parse_env(s) 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) funcs = parse_funcs(s, shell=shell, sourcer=sourcer, extra_args=extra_args)
aliases.update(funcs) aliases.update(funcs)
return env, aliases 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.""" """Parses the aliases portion of string into a dict."""
m = ALIAS_RE.search(s) m = ALIAS_RE.search(s)
if m is None: if m is None:
@ -352,7 +357,20 @@ def parse_aliases(s):
# strip one single quote at the start and end of value # strip one single quote at the start and end of value
if value[0] == "'" and value[-1] == "'": if value[0] == "'" and value[-1] == "'":
value = value[1:-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: except ValueError as exc:
warnings.warn( warnings.warn(
'could not parse alias "{0}": {1!r}'.format(key, exc), RuntimeWarning '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): if not os.path.isabs(filename):
filename = os.path.abspath(filename) filename = os.path.abspath(filename)
wrapper = ForeignShellFunctionAlias( wrapper = ForeignShellFunctionAlias(
name=funcname, funcname=funcname,
shell=shell, shell=shell,
sourcer=sourcer, sourcer=sourcer,
filename=filename, filename=filename,
@ -410,19 +428,17 @@ def parse_funcs(s, shell, sourcer=None, extra_args=()):
return funcs return funcs
class ForeignShellFunctionAlias(object): class ForeignShellBaseAlias(object):
"""This class is responsible for calling foreign shell functions as if """This class is responsible for calling foreign shell functions as if
they were aliases. This does not currently support taking stdin. 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 Parameters
---------- ----------
name : str
function name
shell : str shell : str
Name or path to shell Name or path to shell
filename : str filename : str
@ -433,36 +449,29 @@ class ForeignShellFunctionAlias(object):
Additional command line options to pass into the shell. Additional command line options to pass into the shell.
""" """
sourcer = DEFAULT_SOURCERS.get(shell, "source") if sourcer is None else sourcer sourcer = DEFAULT_SOURCERS.get(shell, "source") if sourcer is None else sourcer
self.name = name
self.shell = shell self.shell = shell
self.filename = filename self.filename = filename
self.sourcer = sourcer self.sourcer = sourcer
self.extra_args = extra_args self.extra_args = extra_args
def __eq__(self, other): def _input_kwargs(self):
if ( return {
not hasattr(other, "name") "shell": self.shell,
or not hasattr(other, "filename") "filename": self.filename,
or not hasattr(other, "sourcer") "sourcer": self.sourcer,
or not hasattr(other, "exta_args") "extra_args": self.extra_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 __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) args, streaming = self._is_streaming(args)
input = self.INPUT.format( input = self.INPUT.format(args=" ".join(args), **self._input_kwargs())
sourcer=self.sourcer,
filename=self.filename,
funcname=self.name,
args=" ".join(args),
)
cmd = [self.shell] + list(self.extra_args) + ["-c", input] cmd = [self.shell] + list(self.extra_args) + ["-c", input]
env = builtins.__xonsh__.env env = builtins.__xonsh__.env
denv = env.detype() denv = env.detype()
@ -478,7 +487,21 @@ class ForeignShellFunctionAlias(object):
out = out.replace("\r\n", "\n") out = out.replace("\r\n", "\n")
return out 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.""" """Test and modify args if --xonsh-stream is present."""
if "--xonsh-stream" not in args: if "--xonsh-stream" not in args:
return args, False return args, False
@ -487,6 +510,77 @@ class ForeignShellFunctionAlias(object):
return args, True 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 @lazyobject
def VALID_SHELL_PARAMS(): def VALID_SHELL_PARAMS():
return frozenset( return frozenset(

View file

@ -257,7 +257,8 @@ def _pprint_displayhook(value):
printed_val = repr(value) printed_val = repr(value)
if HAS_PYGMENTS and env.get("COLOR_RESULTS"): if HAS_PYGMENTS and env.get("COLOR_RESULTS"):
tokens = list(pygments.lex(printed_val, lexer=pyghooks.XonshLexer())) 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: else:
print(printed_val) # black & white case print(printed_val) # black & white case
builtins._ = value builtins._ = value

View file

@ -194,7 +194,9 @@ def ptk_below_max_supported():
@functools.lru_cache(1) @functools.lru_cache(1)
def best_shell_type(): 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" return "prompt_toolkit"
else: else:
return "readline" return "readline"

View file

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

View file

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

View file

@ -119,6 +119,8 @@ class Shell(object):
shell_type_aliases = { shell_type_aliases = {
"b": "best", "b": "best",
"best": "best", "best": "best",
"d": "dumb",
"dumb": "dumb",
"ptk": "prompt_toolkit", "ptk": "prompt_toolkit",
"ptk1": "prompt_toolkit1", "ptk1": "prompt_toolkit1",
"ptk2": "prompt_toolkit2", "ptk2": "prompt_toolkit2",
@ -166,6 +168,8 @@ class Shell(object):
shell_type = self.shell_type_aliases.get(shell_type, shell_type) shell_type = self.shell_type_aliases.get(shell_type, shell_type)
if shell_type == "best" or shell_type is None: if shell_type == "best" or shell_type is None:
shell_type = best_shell_type() shell_type = best_shell_type()
elif env.get("TERM", "") == "dumb":
shell_type = "dumb"
elif shell_type == "random": elif shell_type == "random":
shell_type = random.choice(("readline", "prompt_toolkit")) shell_type = random.choice(("readline", "prompt_toolkit"))
if shell_type == "prompt_toolkit": if shell_type == "prompt_toolkit":
@ -195,6 +199,8 @@ class Shell(object):
from xonsh.readline_shell import ReadlineShell as shell_class from xonsh.readline_shell import ReadlineShell as shell_class
elif shell_type == "jupyter": elif shell_type == "jupyter":
from xonsh.jupyter_shell import JupyterShell as shell_class from xonsh.jupyter_shell import JupyterShell as shell_class
elif shell_type == "dumb":
from xonsh.dumb_shell import DumbShell as shell_class
else: else:
raise XonshError("{} is not recognized as a shell type".format(shell_type)) raise XonshError("{} is not recognized as a shell type".format(shell_type))
self.shell = shell_class(execer=self.execer, ctx=self.ctx, **kwargs) 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) 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): def check_for_partial_string(x):
"""Returns the starting index (inclusive), ending index (exclusive), and """Returns the starting index (inclusive), ending index (exclusive), and
starting quote string of the most recent Python string found in the input. 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", "its behavior. To see the modifications as they are applied (in unified diff",
"format), please set ``$XONSH_DEBUG`` to ``2`` or higher."] "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", {"name": "coreutils",
"package": "xonsh", "package": "xonsh",
"url": "http://xon.sh", "url": "http://xon.sh",

View file

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