Merge branch 'master' into readline

This commit is contained in:
adam j hartz 2016-06-04 13:27:47 -04:00
commit 723ca952a6
13 changed files with 148 additions and 124 deletions

View file

@ -2,6 +2,7 @@ language: python
python:
- 3.4
- 3.5
- "nightly"
install:
- pip install -r requirements-tests.txt
script:

View file

@ -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

View file

@ -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>

View file

@ -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

View file

@ -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)

View file

@ -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', '&'}

View file

@ -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__':

View file

@ -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:

View file

@ -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

View file

@ -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.

View file

@ -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)

View file

@ -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

View file

@ -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: