mirror of
https://github.com/xonsh/xonsh.git
synced 2025-03-04 16:34:47 +01:00
Merge branch 'master' into readline
This commit is contained in:
commit
723ca952a6
13 changed files with 148 additions and 124 deletions
|
@ -2,6 +2,7 @@ language: python
|
|||
python:
|
||||
- 3.4
|
||||
- 3.5
|
||||
- "nightly"
|
||||
install:
|
||||
- pip install -r requirements-tests.txt
|
||||
script:
|
||||
|
|
|
@ -4,15 +4,26 @@ Xonsh Change Log
|
|||
|
||||
Current Developments
|
||||
====================
|
||||
**Added:** None
|
||||
**Added:**
|
||||
|
||||
**Changed:** None
|
||||
* Question mark literals, ``?``, are now allowed as part of
|
||||
subprocess argument names.
|
||||
|
||||
**Changed:**
|
||||
|
||||
* ``which`` now give a better verbose report of where the executables are found.
|
||||
|
||||
**Deprecated:** None
|
||||
|
||||
**Removed:** None
|
||||
**Removed:**
|
||||
|
||||
**Fixed:** None
|
||||
* Fixed bug on Windows where ``which`` did not include current directory
|
||||
|
||||
**Fixed:**
|
||||
|
||||
* Fixed bug on Windows where tab-completion for executables would return all files.
|
||||
* Fixed bug on Windows which caused the bash $PROMPT variable to be used when no
|
||||
no $PROMPT variable was set in .xonshrc
|
||||
|
||||
**Security:** None
|
||||
|
||||
|
|
|
@ -40,11 +40,14 @@ the xonsh shell
|
|||
"Crustaceanly Yours",
|
||||
"With great shell comes great reproducibility",
|
||||
"None shell pass",
|
||||
"You shell not pass!",
|
||||
"The x-on shell",
|
||||
"It is pronounced <i>コンチ</i>",
|
||||
"It is pronounced <i>コンッチ</i>",
|
||||
"It is pronounced <i>コンシュ</i>",
|
||||
"The carcolh will catch you!",
|
||||
"WHAT...is your favorite shell?",
|
||||
"Exploiting the workers and hanging on to outdated imperialist dogma since 2015."
|
||||
];
|
||||
document.write(taglines[Math.floor(Math.random() * taglines.length)]);
|
||||
</script>
|
||||
|
|
|
@ -422,12 +422,12 @@ terminal, and the resulting object is not displayed. For example
|
|||
Python Evaluation with ``@()``
|
||||
===============================
|
||||
|
||||
The ``@(<expr>)`` operator from will evaluate arbitrary Python code in
|
||||
subprocess mode and the result will be appended to the subprocess command
|
||||
list. If the result is a string, it is appended to the argument list.
|
||||
If the result is a list or other non-string sequence, the contents are
|
||||
converted to strings and appended to the argument list in order. Otherwise, the
|
||||
result is automatically converted to a string. For example,
|
||||
The ``@(<expr>)`` operator form works in subprocess mode, and will evaluate
|
||||
arbitrary Python code. The result is appended to the subprocess command
|
||||
list. If the result is a string, it is appended to the argument list. If the
|
||||
result is a list or other non-string sequence, the contents are converted to
|
||||
strings and appended to the argument list in order. Otherwise, the result is
|
||||
automatically converted to a string. For example,
|
||||
|
||||
.. code-block:: xonshcon
|
||||
|
||||
|
|
2
setup.py
2
setup.py
|
@ -185,7 +185,7 @@ def main():
|
|||
}
|
||||
skw['cmdclass']['develop'] = xdevelop
|
||||
else:
|
||||
skw['scripts'] = ['scripts/xonsh'] if 'win' not in sys.platform else ['scripts/xonsh.bat'],
|
||||
skw['scripts'] = ['scripts/xonsh'] if 'win' not in sys.platform else ['scripts/xonsh.bat']
|
||||
|
||||
setup(**skw)
|
||||
|
||||
|
|
|
@ -1752,6 +1752,10 @@ def test_echo_internal_comma():
|
|||
def test_comment_only():
|
||||
yield check_xonsh_ast, {}, '# hello'
|
||||
|
||||
def test_echo_slash_question():
|
||||
yield check_xonsh_ast, {}, '![echo /?]', False
|
||||
|
||||
|
||||
_error_names = {'e', 'err', '2'}
|
||||
_output_names = {'', 'o', 'out', '1'}
|
||||
_all_names = {'a', 'all', '&'}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Tests the xonsh lexer."""
|
||||
import os
|
||||
import random
|
||||
from tempfile import TemporaryDirectory
|
||||
import stat
|
||||
|
||||
import nose
|
||||
from nose.tools import assert_equal, assert_true, assert_false
|
||||
|
||||
from xonsh.platform import ON_WINDOWS
|
||||
from xonsh.lexer import Lexer
|
||||
from xonsh.tools import (
|
||||
subproc_toks, subexpr_from_unbalanced, is_int, always_true, always_false,
|
||||
|
@ -591,25 +591,38 @@ def test_partial_string():
|
|||
|
||||
|
||||
def test_executables_in():
|
||||
expected = set()
|
||||
with TemporaryDirectory() as test_path:
|
||||
for i in range(random.randint(100, 200)):
|
||||
_type = random.choice(('none', 'file', 'file', 'directory'))
|
||||
if _type == 'none':
|
||||
continue
|
||||
executable = random.choice((True, True, False))
|
||||
if _type == 'file' and executable:
|
||||
expected.add(str(i))
|
||||
path = os.path.join(test_path, str(i))
|
||||
if _type == 'file':
|
||||
open(path, 'w').close()
|
||||
elif _type == 'directory':
|
||||
os.mkdir(path)
|
||||
if executable:
|
||||
os.chmod(path, stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR)
|
||||
|
||||
result = set(executables_in(test_path))
|
||||
assert_equal(expected, result)
|
||||
|
||||
expected = set()
|
||||
types = ('file', 'directory', 'brokensymlink')
|
||||
executables = (True, False)
|
||||
with TemporaryDirectory() as test_path:
|
||||
for _type in types:
|
||||
for executable in executables:
|
||||
fname = '%s_%s' % (_type, executable)
|
||||
if _type == 'none':
|
||||
continue
|
||||
if _type == 'file' and executable:
|
||||
ext = '.exe' if ON_WINDOWS else ''
|
||||
expected.add(fname + ext)
|
||||
else:
|
||||
ext = ''
|
||||
path = os.path.join(test_path, fname + ext)
|
||||
if _type == 'file':
|
||||
with open(path, 'w') as f:
|
||||
f.write(fname)
|
||||
elif _type == 'directory':
|
||||
os.mkdir(path)
|
||||
elif _type == 'brokensymlink':
|
||||
tmp_path = os.path.join(test_path, 'i_wont_exist')
|
||||
with open(tmp_path,'w') as f:
|
||||
f.write('deleteme')
|
||||
os.symlink(tmp_path, path)
|
||||
os.remove(tmp_path)
|
||||
if executable and not _type == 'brokensymlink' :
|
||||
os.chmod(path, stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR)
|
||||
result = set(executables_in(test_path))
|
||||
assert_equal(expected, result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -408,8 +408,13 @@ def which(args, stdin=None, stdout=None, stderr=None):
|
|||
nmatches += 1
|
||||
if not pargs.all:
|
||||
continue
|
||||
matches = _which.whichgen(arg, exts=exts, verbose=pargs.verbose,
|
||||
path=builtins.__xonsh_env__['PATH'])
|
||||
# which.whichgen gives the nicest 'verbose' output if PATH is taken
|
||||
# from os.environ so we temporarily override it with
|
||||
# __xosnh_env__['PATH']
|
||||
original_os_path = os.environ['PATH']
|
||||
os.environ['PATH'] = builtins.__xonsh_env__.detype()['PATH']
|
||||
matches = _which.whichgen(arg, exts=exts, verbose=pargs.verbose)
|
||||
os.environ['PATH'] = original_os_path
|
||||
for abs_name, from_where in matches:
|
||||
if ON_WINDOWS:
|
||||
# Use list dir to get correct case for the filename
|
||||
|
@ -422,8 +427,6 @@ def which(args, stdin=None, stdout=None, stderr=None):
|
|||
if pargs.plain or not pargs.verbose:
|
||||
print(abs_name, file=stdout)
|
||||
else:
|
||||
if 'given path element' in from_where:
|
||||
from_where = from_where.replace('given path', '$PATH')
|
||||
print('{} ({})'.format(abs_name, from_where), file=stdout)
|
||||
nmatches += 1
|
||||
if not pargs.all:
|
||||
|
|
|
@ -35,7 +35,8 @@ from xonsh.tools import (
|
|||
is_bool_or_int, to_bool_or_int, bool_or_int_to_str,
|
||||
csv_to_bool_seq, bool_seq_to_csv, DefaultNotGiven, print_exception,
|
||||
setup_win_unicode_console, intensify_colors_on_win_setter, format_color,
|
||||
is_dynamic_cwd_width, to_dynamic_cwd_tuple, dynamic_cwd_tuple_to_str
|
||||
is_dynamic_cwd_width, to_dynamic_cwd_tuple, dynamic_cwd_tuple_to_str,
|
||||
executables_in
|
||||
)
|
||||
|
||||
|
||||
|
@ -735,65 +736,17 @@ class Env(MutableMapping):
|
|||
p.pretty(dict(self))
|
||||
|
||||
|
||||
def _is_executable_file(path):
|
||||
"""Checks that path is an executable regular file, or a symlink towards one.
|
||||
This is roughly ``os.path isfile(path) and os.access(path, os.X_OK)``.
|
||||
|
||||
This function was forked from pexpect originally:
|
||||
|
||||
Copyright (c) 2013-2014, Pexpect development team
|
||||
Copyright (c) 2012, Noah Spurrier <noah@noah.org>
|
||||
|
||||
PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY
|
||||
PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE
|
||||
COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES.
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
"""
|
||||
# follow symlinks,
|
||||
fpath = os.path.realpath(path)
|
||||
|
||||
if not os.path.isfile(fpath):
|
||||
# non-files (directories, fifo, etc.)
|
||||
return False
|
||||
|
||||
return os.access(fpath, os.X_OK)
|
||||
|
||||
|
||||
def yield_executables_windows(directory, name):
|
||||
normalized_name = os.path.normcase(name)
|
||||
extensions = builtins.__xonsh_env__.get('PATHEXT')
|
||||
try:
|
||||
names = os.listdir(directory)
|
||||
except PermissionError:
|
||||
return
|
||||
for a_file in names:
|
||||
normalized_file_name = os.path.normcase(a_file)
|
||||
base_name, ext = os.path.splitext(normalized_file_name)
|
||||
|
||||
if (
|
||||
normalized_name == base_name or normalized_name == normalized_file_name
|
||||
) and ext.upper() in extensions:
|
||||
yield os.path.join(directory, a_file)
|
||||
|
||||
|
||||
def yield_executables_posix(directory, name):
|
||||
try:
|
||||
names = os.listdir(directory)
|
||||
except PermissionError:
|
||||
return
|
||||
if name in names:
|
||||
path = os.path.join(directory, name)
|
||||
if _is_executable_file(path):
|
||||
yield path
|
||||
|
||||
|
||||
yield_executables = yield_executables_windows if ON_WINDOWS else yield_executables_posix
|
||||
def _yield_executables(directory, name):
|
||||
if ON_WINDOWS:
|
||||
for fname in executables_in(directory):
|
||||
base_name, ext = os.path.splitext(fname)
|
||||
if name.lower() == base_name.lower():
|
||||
yield os.path.join(directory, fname)
|
||||
else:
|
||||
for x in executables_in(directory):
|
||||
if x == name:
|
||||
yield os.path.join(directory, name)
|
||||
return
|
||||
|
||||
|
||||
def locate_binary(name):
|
||||
|
@ -807,7 +760,7 @@ def locate_binary(name):
|
|||
directories = [_get_cwd()] + directories
|
||||
|
||||
try:
|
||||
return next(chain.from_iterable(yield_executables(directory, name) for
|
||||
return next(chain.from_iterable(_yield_executables(directory, name) for
|
||||
directory in directories if os.path.isdir(directory)))
|
||||
except StopIteration:
|
||||
return None
|
||||
|
@ -1343,7 +1296,7 @@ def xonshrc_context(rcfiles=None, execer=None):
|
|||
def windows_foreign_env_fixes(ctx):
|
||||
"""Environment fixes for Windows. Operates in-place."""
|
||||
# remove these bash variables which only cause problems.
|
||||
for ev in ['HOME', 'OLDPWD']:
|
||||
for ev in ['HOME', 'OLDPWD', 'PROMPT']:
|
||||
if ev in ctx:
|
||||
del ctx[ev]
|
||||
# Override path-related bash variables; on Windows bash uses
|
||||
|
|
|
@ -2286,6 +2286,7 @@ class BaseParser(object):
|
|||
| NUMBER
|
||||
| STRING
|
||||
| COMMA
|
||||
| QUESTION
|
||||
"""
|
||||
# Many tokens cannot be part of this list, such as $, ', ", ()
|
||||
# Use a string atom instead.
|
||||
|
|
|
@ -93,13 +93,12 @@ def ptk_version_info():
|
|||
return None
|
||||
|
||||
|
||||
BEST_SHELL_TYPE = None
|
||||
""" The 'best' available shell type, either 'prompt_toollit' or 'readline'. """
|
||||
|
||||
if ON_WINDOWS or has_prompt_toolkit():
|
||||
BEST_SHELL_TYPE = 'prompt_toolkit'
|
||||
else:
|
||||
BEST_SHELL_TYPE = 'readline'
|
||||
@lru_cache(1)
|
||||
def best_shell_type():
|
||||
if ON_WINDOWS or has_prompt_toolkit():
|
||||
return 'prompt_toolkit'
|
||||
else:
|
||||
return 'readline'
|
||||
|
||||
|
||||
@lru_cache(1)
|
||||
|
|
|
@ -7,7 +7,7 @@ from warnings import warn
|
|||
from xonsh import xontribs
|
||||
from xonsh.environ import xonshrc_context
|
||||
from xonsh.execer import Execer
|
||||
from xonsh.platform import (BEST_SHELL_TYPE, has_prompt_toolkit, ptk_version,
|
||||
from xonsh.platform import (best_shell_type, has_prompt_toolkit, ptk_version,
|
||||
ptk_version_info)
|
||||
from xonsh.tools import XonshError
|
||||
|
||||
|
@ -44,17 +44,16 @@ class Shell(object):
|
|||
kwargs.get('cacheall', False))
|
||||
env = builtins.__xonsh_env__
|
||||
# pick a valid shell
|
||||
if shell_type is not None:
|
||||
env['SHELL_TYPE'] = shell_type
|
||||
shell_type = env.get('SHELL_TYPE')
|
||||
if shell_type == 'best' or shell_type is None:
|
||||
shell_type = BEST_SHELL_TYPE
|
||||
shell_type = best_shell_type()
|
||||
elif shell_type == 'random':
|
||||
shell_type = random.choice(('readline', 'prompt_toolkit'))
|
||||
if shell_type == 'prompt_toolkit':
|
||||
if not has_prompt_toolkit():
|
||||
warn('prompt_toolkit is not available, using readline instead.')
|
||||
shell_type = env['SHELL_TYPE'] = 'readline'
|
||||
shell_type = 'readline'
|
||||
env['SHELL_TYPE'] = shell_type
|
||||
# actually make the shell
|
||||
if shell_type == 'none':
|
||||
from xonsh.base_shell import BaseShell as shell_class
|
||||
|
|
|
@ -36,11 +36,6 @@ from collections import OrderedDict, Sequence, Set
|
|||
from xonsh.platform import (has_prompt_toolkit, scandir, win_unicode_console,
|
||||
DEFAULT_ENCODING, ON_LINUX, ON_WINDOWS,
|
||||
PYTHON_VERSION_INFO)
|
||||
if has_prompt_toolkit():
|
||||
import prompt_toolkit
|
||||
else:
|
||||
prompt_toolkit = None
|
||||
|
||||
|
||||
IS_SUPERUSER = ctypes.windll.shell32.IsUserAnAdmin() != 0 if ON_WINDOWS else os.getuid() == 0
|
||||
|
||||
|
@ -317,18 +312,53 @@ class redirect_stderr(_RedirectStream):
|
|||
_stream = "stderr"
|
||||
|
||||
|
||||
def _yield_accessible_unix_file_names(path):
|
||||
"yield file names of executablel files in `path`"
|
||||
|
||||
for file_ in scandir(path):
|
||||
try:
|
||||
if file_.is_file() and os.access(file_.path, os.X_OK):
|
||||
yield file_.name
|
||||
except NotADirectoryError:
|
||||
# broken Symlink are neither dir not files
|
||||
pass
|
||||
|
||||
|
||||
def _executables_in_posix(path):
|
||||
if PYTHON_VERSION_INFO < (3, 5, 0):
|
||||
for fname in os.listdir(path):
|
||||
fpath = os.path.join(path, fname)
|
||||
if (os.path.exists(fpath) and os.access(fpath, os.X_OK) and \
|
||||
(not os.path.isdir(fpath))):
|
||||
yield fname
|
||||
else:
|
||||
yield from _yield_accessible_unix_file_names(path)
|
||||
|
||||
|
||||
def _executables_in_windows(path):
|
||||
extensions = builtins.__xonsh_env__.get('PATHEXT',['.COM', '.EXE', '.BAT'])
|
||||
if PYTHON_VERSION_INFO < (3, 5, 0):
|
||||
for fname in os.listdir(path):
|
||||
fpath = os.path.join(path, fname)
|
||||
if (os.path.exists(fpath) and not os.path.isdir(fpath)):
|
||||
base_name, ext = os.path.splitext(fname)
|
||||
if ext.upper() in extensions:
|
||||
yield fname
|
||||
else:
|
||||
for fname in (x.name for x in scandir(path) if x.is_file()):
|
||||
base_name, ext = os.path.splitext(fname)
|
||||
if ext.upper() in extensions:
|
||||
yield fname
|
||||
|
||||
|
||||
def executables_in(path):
|
||||
"""Returns a generator of files in `path` that the user could execute. """
|
||||
if ON_WINDOWS:
|
||||
func = _executables_in_windows
|
||||
else:
|
||||
func = _executables_in_posix
|
||||
try:
|
||||
if PYTHON_VERSION_INFO < (3, 5, 0):
|
||||
for i in os.listdir(path):
|
||||
name = os.path.join(path, i)
|
||||
if (os.path.exists(name) and os.access(name, os.X_OK) and \
|
||||
(not os.path.isdir(name))):
|
||||
yield i
|
||||
else:
|
||||
yield from (x.name for x in scandir(path)
|
||||
if x.is_file() and os.access(x.path, os.X_OK))
|
||||
yield from func(path)
|
||||
except PermissionError:
|
||||
return
|
||||
|
||||
|
@ -837,6 +867,7 @@ def color_style():
|
|||
|
||||
def _get_color_indexes(style_map):
|
||||
""" Generates the color and windows color index for a style """
|
||||
import prompt_toolkit
|
||||
table = prompt_toolkit.terminal.win32_output.ColorLookupTable()
|
||||
pt_style = prompt_toolkit.styles.style_from_dict(style_map)
|
||||
for token in style_map:
|
||||
|
@ -858,7 +889,10 @@ def intensify_colors_for_cmd_exe(style_map, replace_colors=None):
|
|||
range used by the gray colors
|
||||
"""
|
||||
modified_style = {}
|
||||
if not ON_WINDOWS or prompt_toolkit is None:
|
||||
stype = builtins.__xonsh_env__.get('SHELL_TYPE')
|
||||
if (not ON_WINDOWS or
|
||||
(stype not in ('prompt_toolkit', 'best')) or
|
||||
(stype == 'best' and not has_prompt_toolkit())):
|
||||
return modified_style
|
||||
if replace_colors is None:
|
||||
replace_colors = {1: '#44ffff', # subst blue with bright cyan
|
||||
|
@ -881,7 +915,10 @@ def expand_gray_colors_for_cmd_exe(style_map):
|
|||
in cmd.exe.
|
||||
"""
|
||||
modified_style = {}
|
||||
if not ON_WINDOWS or prompt_toolkit is None:
|
||||
stype = builtins.__xonsh_env__.get('SHELL_TYPE')
|
||||
if (not ON_WINDOWS or
|
||||
(stype not in ('prompt_toolkit', 'best')) or
|
||||
(stype == 'best' and not has_prompt_toolkit())):
|
||||
return modified_style
|
||||
for token, idx, rgb in _get_color_indexes(style_map):
|
||||
if idx == 7 and rgb:
|
||||
|
|
Loading…
Add table
Reference in a new issue