Merge branch 'teepipe'

This commit is contained in:
Anthony Scopatz 2015-10-12 00:24:35 -04:00
commit bcd9f0cce0
23 changed files with 811 additions and 276 deletions

View file

@ -0,0 +1,10 @@
.. _xonsh_foreign_shells:
******************************************************
Foreign Shell Tools (``xonsh.foreign_shells``)
******************************************************
.. automodule:: xonsh.foreign_shells
:members:
:undoc-members:
:inherited-members:

View file

@ -51,5 +51,6 @@ For those of you who want the gritty details.
lazyjson
teepty
openpy
foreign_shells
main
pyghooks

View file

@ -11,60 +11,68 @@ applicable.
* - variable
- default
- description
* - PROMPT
- xonsh.environ.DEFAULT_PROMPT
- The prompt text. May contain keyword arguments which are auto-formatted,
see `Customizing the Prompt <tutorial.html#customizing-the-prompt>`_.
* - MULTILINE_PROMPT
- ``'.'``
- Prompt text for 2nd+ lines of input, may be str or function which returns a str.
* - TITLE
- xonsh.environ.DEFAULT_TITLE
- The title text for the window in which xonsh is running. Formatted in the same
manner as PROMPT,
see `Customizing the Prompt <tutorial.html#customizing-the-prompt>`_.
* - ANSICON
- No default set
- This is used on Windows to set the title, if available.
* - AUTO_PUSHD
- ``False``
- Flag for automatically pushing directorties onto the directory stack.
* - BASH_COMPLETIONS
- Normally this is ``('/etc/bash_completion',
'/usr/share/bash-completion/completions/git')``
but on Mac is ``'/usr/local/etc/bash_completion',
'/opt/local/etc/profile.d/bash_completion.sh')``.
- This is a list (or tuple) of strings that specifies where the BASH completion
files may be found. The default values are platform dependent, but sane.
To specify an alternate list, do so in the run control file.
* - CASE_SENSITIVE_COMPLETIONS
- ``True`` on Linux, otherwise ``False``
- Sets whether completions should be case sensitive or case insensitive.
* - CDPATH
- ``[]``
- A list of paths to be used as roots for a ``cd``, breaking compatibility with
bash, xonsh always prefer an existing relative path.
* - DIRSTACK_SIZE
- ``20``
- Maximum size of the directory stack.
* - FORCE_POSIX_PATHS
- ``False``
- Forces forward slashes (``/``) on Windows systems when using auto completion if
set to anything truthy.
* - FORMATTER_DICT
- xonsh.environ.FORMATTER_DICT
- Dictionary containing variables to be used when formatting PROMPT and TITLE
see `Customizing the Prompt <tutorial.html#customizing-the-prompt>`_.
* - XONSHRC
- ``'~/.xonshrc'``
- Location of run control file.
* - XONSH_HISTORY_SIZE
- ``(8128, 'commands')`` or ``'8128 commands'``
- Value and units tuple that sets the size of history after garbage collection.
Canonical units are ``'commands'`` for the number of past commands executed,
``'files'`` for the number of history files to keep, ``'s'`` for the number of
seconds in the past that are allowed, and ``'b'`` for the number of bytes that
are allowed for history to consume. Common abbreviations, such as ``6 months``
or ``1 GB`` are also allowed.
* - XONSH_HISTORY_FILE
- ``'~/.xonsh_history'``
- Location of history file (deprecated).
* - XONSH_STORE_STDOUT
* - INDENT
- ``' '``
- Indentation string for multiline input
* - MULTILINE_PROMPT
- ``'.'``
- Prompt text for 2nd+ lines of input, may be str or function which returns
a str.
* - OLDPWD
- No default
- Used to represent a previous present working directory.
* - PATH
- ``()``
- List of strings representing where to look for executables.
* - PATHEXT
- ``()``
- List of strings for filtering valid exeutables by.
* - PROMPT
- xonsh.environ.DEFAULT_PROMPT
- The prompt text. May contain keyword arguments which are auto-formatted,
see `Customizing the Prompt <tutorial.html#customizing-the-prompt>`_.
* - PROMPT_TOOLKIT_STYLES
- ``None``
- This is a mapping of user-specified styles for prompt-toolkit. See the
prompt-toolkit documentation for more details. If None, this is skipped.
* - PUSHD_MINUS
- ``False``
- Whether or not to store the stdout and stderr streams in the history files.
* - XONSH_INTERACTIVE
-
- ``True`` if xonsh is running interactively, and ``False`` otherwise.
* - BASH_COMPLETIONS
- ``[] or ['/etc/...']``
- This is a list of strings that specifies where the BASH completion files may
be found. The default values are platform dependent, but sane. To specify an
alternate list, do so in the run control file.
* - SUGGEST_COMMANDS
- ``True``
- When a user types an invalid command, xonsh will try to offer suggestions of
similar valid commands if this is ``True``.
* - SUGGEST_THRESHOLD
- ``3``
- An error threshold. If the Levenshtein distance between the entered command and
a valid command is less than this value, the valid command will be offered as a
suggestion.
* - SUGGEST_MAX_NUM
- ``5``
- xonsh will show at most this many suggestions in response to an invalid command.
If negative, there is no limit to how many suggestions are shown.
- Flag for directory pushing functionality. False is the normal behaviour.
* - PUSHD_SILENT
- ``False``
- Whether or not to supress directory stack manipulation output.
* - SHELL_TYPE
- ``'readline'``
- Which shell is used. Currently two shell types are supported: ``'readline'`` that
@ -74,27 +82,78 @@ applicable.
`prompt_toolkit <https://github.com/jonathanslenders/python-prompt-toolkit>`_
library installed. To specify which shell should be used, do so in the run
control file.
* - CDPATH
- ``[]``
- A list of paths to be used as roots for a ``cd``, breaking compatibility with
bash, xonsh always prefer an existing relative path.
* - XONSH_SHOW_TRACEBACK
- Not defined
- Controls if a traceback is shown exceptions occur in the shell. Set ``'True'``
to always show or ``'False'`` to always hide. If undefined then traceback is
hidden but a notice is shown on how to enable the traceback.
* - CASE_SENSITIVE_COMPLETIONS
- ``True`` on Linux, otherwise ``False``
- Sets whether completions should be case sensitive or case insensitive.
* - FORCE_POSIX_PATHS
- Not defined
- Forces forward slashes (``/``) on Windows systems when using auto completion if
set to anything truthy.
* - XONSH_DATA_DIR
- ``$XDG_DATA_HOME/xonsh``
- This is the location where xonsh data files are stored, such as history.
* - SUGGEST_COMMANDS
- ``True``
- When a user types an invalid command, xonsh will try to offer suggestions of
similar valid commands if this is ``True``.
* - SUGGEST_MAX_NUM
- ``5``
- xonsh will show at most this many suggestions in response to an invalid command.
If negative, there is no limit to how many suggestions are shown.
* - SUGGEST_THRESHOLD
- ``3``
- An error threshold. If the Levenshtein distance between the entered command and
a valid command is less than this value, the valid command will be offered as a
suggestion.
* - TEEPTY_PIPE_DELAY
- ``0.01``
- The number of [seconds] to delay a spawned process if it has information
being piped in via stdin. This value must be a float. If a value less than
or equal to zero is passed in, no delay is used. This can be used to fix
situations where a spawned process, such as piping into ``grep``, exits
too quickly for the piping operation itself. TeePTY (and thus this variable)
are currently only used when ``$XONSH_STORE_STDOUT`` is ``True``.
* - TERM
- No default
- TERM is sometimes set by the terminal emulator. This is used (when valid)
to determine whether or not to set the title. Users shouldn't need to
set this themselves.
* - TITLE
- xonsh.environ.DEFAULT_TITLE
- The title text for the window in which xonsh is running. Formatted in the same
manner as PROMPT,
see `Customizing the Prompt <tutorial.html#customizing-the-prompt>`_.
* - XDG_CONFIG_HOME
- ``~/.config``
- Open desktop standard configuration home dir. This is the same default as
used in the standard.
* - XDG_DATA_HOME
- ``~/.local/share``
- Open desktop standard data home dir. This is the same default as used
in the standard.
* - XONSHCONFIG
- ``$XONSH_CONFIG_DIR/config.json``
- The location of the static xonsh configuration file, if it exists. This is
in JSON format.
* - XONSHRC
- ``~/.xonshrc``
- Location of run control file.
* - XONSH_CONFIG_DIR
- ``$XDG_CONFIG_HOME/xonsh``
- This is location where xonsh configuration information is stored.
* - XONSH_DATA_DIR
- ``$XDG_DATA_HOME/xonsh``
- This is the location where xonsh data files are stored, such as history.
* - XONSH_HISTORY_FILE
- ``'~/.xonsh_history'``
- Location of history file (deprecated).
* - XONSH_HISTORY_SIZE
- ``(8128, 'commands')`` or ``'8128 commands'``
- Value and units tuple that sets the size of history after garbage collection.
Canonical units are ``'commands'`` for the number of past commands executed,
``'files'`` for the number of history files to keep, ``'s'`` for the number of
seconds in the past that are allowed, and ``'b'`` for the number of bytes that
are allowed for history to consume. Common abbreviations, such as ``6 months``
or ``1 GB`` are also allowed.
* - XONSH_INTERACTIVE
-
- ``True`` if xonsh is running interactively, and ``False`` otherwise.
* - XONSH_SHOW_TRACEBACK
- ``False`` but not set
- Controls if a traceback is shown exceptions occur in the shell. Set ``True``
to always show or ``False`` to always hide. If undefined then traceback is
hidden but a notice is shown on how to enable the traceback.
* - XONSH_STORE_STDOUT
- ``False``
- Whether or not to store the stdout and stderr streams in the history files.

View file

@ -67,6 +67,7 @@ Contents
tutorial
tutorial_hist
xonshrc
xonshconfig
envvars
aliases
windows

12
docs/xonshconfig.json Normal file
View file

@ -0,0 +1,12 @@
{"env": {
"EDITOR": "xo",
"PAGER": "more"
},
"foreign_shells": [
{"shell": "bash",
"login": true,
"extra_args": ["--rcfile", "/path/to/rcfile"]
},
{"shell": "zsh"}
]
}

84
docs/xonshconfig.rst Normal file
View file

@ -0,0 +1,84 @@
Static Configuration File
=========================
In addition to the run control file, xonsh allows you to have a static config file.
This JSON-formatted file lives at ``$XONSH_CONFIG_DIR/config.json``, which is
normally ``~/.config/xonsh/config.json``. The purpose of this file is to allow
users to set runtime parameters *before* anything else happens. This inlcudes
loading data from various foreign shells or setting critical environment
variables.
This is a dictionary or JSON object at its top-level. It has the following
top-level keys. All top-level keys are optional.
``env``
--------
This is a simple string-keyed dictionary that lets you set environment
variables. For example,
.. code:: json
{"env": {
"EDITOR": "xo",
"PAGER": "more"
}
}
``foreign_shells``
--------------------
This is a list (JSON Array) of dicts (JSON objects) that represent the
foreign shells to inspect for extra start up information, such as environment
variables and aliases. The suite of data gathered may be expanded in the
future. Each shell dictionary unpacked and passed into the
``xonsh.foreign_shells.foreign_shell_data()`` function. Thus these dictionaries
have the following structure:
:shell: *str, required* - The name or path of the shell, such as "bash" or "/bin/sh".
:interactive: *bool, optional* - Whether the shell should be run in interactive mode.
``default=true``
:login: *bool, optional* - Whether the shell should be a login shell.
``default=false``
:envcmd: *str, optional* - The command to generate environment output with.
``default="env"``
:aliascmd: *str, optional* - The command to generate alais output with.
``default="alias"``
:extra_args: *list of str, optional* - Addtional command line options to pass
into the shell. ``default=[]``
:currenv: *dict or null, optional* - Manual override for the current environment.
``default=null``
:safe: *bool, optional* - Flag for whether or not to safely handle exceptions
and other errors. ``default=true``
Some examples can be seen below:
.. code:: json
# load bash then zsh
{"foreign_shells": [
{"shell": "/bin/bash"},
{"shell": "zsh"}
]
}
# load bash as a login shell with custom rcfile
{"foreign_shells": [
{"shell": "bash",
"login": true,
"extra_args": ["--rcfile", "/path/to/rcfile"]
}
]
}
# disable all foreign shell loading via an empty list
{"foreign_shells": []}
Putting it all together
-----------------------
The following ecample shows a fully fleshed out config file.
:download:`Download config.json <xonshxonfig.json>`
.. include:: xonshconfig.json
:code: json

5
tests/bashrc.sh Normal file
View file

@ -0,0 +1,5 @@
export EMERALD="SWORD"
alias ll='ls -a -lF'
alias la='ls -A'
export MIGHTY=WARRIOR
alias l='ls -CF'

View file

@ -0,0 +1,56 @@
"""Tests foreign shells."""
from __future__ import unicode_literals, print_function
import os
import subprocess
import nose
from nose.plugins.skip import SkipTest
from nose.tools import assert_equal, assert_true, assert_false
from xonsh.foreign_shells import foreign_shell_data, parse_env, parse_aliases
def test_parse_env():
exp = {'X': 'YES', 'Y': 'NO'}
s = ('some garbage\n'
'__XONSH_ENV_BEG__\n'
'Y=NO\n'
'X=YES\n'
'__XONSH_ENV_END__\n'
'more filth')
obs = parse_env(s)
assert_equal(exp, obs)
def test_parse_aliases():
exp = {'x': ['yes', '-1'], 'y': ['echo', 'no']}
s = ('some garbage\n'
'__XONSH_ALIAS_BEG__\n'
"alias x='yes -1'\n"
"alias y='echo no'\n"
'__XONSH_ALIAS_END__\n'
'more filth')
obs = parse_aliases(s)
assert_equal(exp, obs)
def test_foreign_bash_data():
expenv = {"EMERALD": "SWORD", 'MIGHTY': 'WARRIOR'}
expaliases = {
'l': ['ls', '-CF'],
'la': ['ls', '-A'],
'll': ['ls', '-a', '-lF'],
}
rcfile = os.path.join(os.path.dirname(__file__), 'bashrc.sh')
try:
obsenv, obsaliases = foreign_shell_data('bash', currenv=(),
extra_args=('--rcfile', rcfile),
safe=False)
except (subprocess.CalledProcessError, FileNotFoundError):
raise SkipTest
for key, expval in expenv.items():
yield assert_equal, expval, obsenv.get(key, False)
yield assert_equal, expaliases, obsaliases
if __name__ == '__main__':
nose.runmodule()

View file

@ -9,7 +9,7 @@ from xonsh.lexer import Lexer
from xonsh.tools import subproc_toks, subexpr_from_unbalanced, is_int, \
always_true, always_false, ensure_string, is_env_path, str_to_env_path, \
env_path_to_str, escape_windows_title_string, is_bool, to_bool, bool_to_str, \
ensure_int_or_slice
ensure_int_or_slice, is_float
LEXER = Lexer()
LEXER.build()
@ -154,6 +154,10 @@ def test_is_int():
yield assert_true, is_int(42)
yield assert_false, is_int('42')
def test_is_float():
yield assert_true, is_float(42.0)
yield assert_false, is_float('42.0')
def test_always_true():
yield assert_true, always_true(42)
yield assert_true, always_true('42')

View file

@ -1,10 +1,8 @@
"""Aliases for the xonsh shell."""
import os
import sys
import shlex
import builtins
import subprocess
import datetime
from warnings import warn
from argparse import ArgumentParser
@ -75,6 +73,7 @@ def xexec(args, stdin=None):
_BANG_N_PARSER = None
def bang_n(args, stdin=None):
"""Re-runs the nth command as specified in the argument."""
global _BANG_N_PARSER
@ -101,37 +100,6 @@ def bang_bang(args, stdin=None):
return bang_n(['-1'])
def bash_aliases():
"""Computes a dictionary of aliases based on Bash's aliases."""
try:
s = subprocess.check_output(['bash', '-i', '-l'],
input='alias',
stderr=subprocess.PIPE,
universal_newlines=True)
except (subprocess.CalledProcessError, FileNotFoundError):
s = ''
items = [line.split('=', 1) for line in s.splitlines() if '=' in line]
aliases = {}
for key, value in items:
try:
key = key[6:] # lstrip 'alias '
# undo bash's weird quoting of single quotes (sh_single_quote)
value = value.replace('\'\\\'\'', '\'')
# 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)
except ValueError as exc:
warn('could not parse Bash alias "{0}": {1!r}'.format(key, exc),
RuntimeWarning)
continue
aliases[key] = value
return aliases
DEFAULT_ALIASES = {
'cd': cd,
'pushd': pushd,
@ -186,5 +154,3 @@ elif ON_MAC:
else:
DEFAULT_ALIASES['grep'] = ['grep', '--color=auto']
DEFAULT_ALIASES['ls'] = ['ls', '--color=auto', '-v']

View file

@ -119,7 +119,7 @@ class BaseShell(object):
return
hist = builtins.__xonsh_history__
ts1 = None
tee = Tee() if builtins.__xonsh_env__.get('XONSH_STORE_STDOUT', False) \
tee = Tee() if builtins.__xonsh_env__.get('XONSH_STORE_STDOUT') \
else io.StringIO()
try:
ts0 = time.time()
@ -181,9 +181,8 @@ class BaseShell(object):
term = env.get('TERM', None)
if term is None or term == 'linux':
return
if 'TITLE' in env:
t = env['TITLE']
else:
t = env.get('TITLE')
if t is None:
return
t = format_prompt(t)
if ON_WINDOWS and 'ANSICON' not in env:
@ -204,14 +203,11 @@ class BaseShell(object):
self.mlprompt = '<multiline prompt error> '
return self.mlprompt
env = builtins.__xonsh_env__
if 'PROMPT' in env:
p = env['PROMPT']
p = env.get('PROMPT')
try:
p = format_prompt(p)
except Exception:
print_exception()
else:
p = "set '$PROMPT = ...' $ "
self.settitle()
return p

View file

@ -22,10 +22,11 @@ from xonsh.tools import suggest_commands, XonshError, ON_POSIX, ON_WINDOWS, \
string_types
from xonsh.inspectors import Inspector
from xonsh.environ import Env, default_env
from xonsh.aliases import DEFAULT_ALIASES, bash_aliases
from xonsh.aliases import DEFAULT_ALIASES
from xonsh.jobs import add_job, wait_for_active_job
from xonsh.proc import ProcProxy, SimpleProcProxy, TeePTYProc
from xonsh.history import History
from xonsh.foreign_shells import load_foreign_aliases
ENV = None
BUILTINS_LOADED = False
@ -268,20 +269,17 @@ RE_SHEBANG = re.compile(r'#![ \t]*(.+?)$')
def _get_runnable_name(fname):
if os.path.isfile(fname) and fname != os.path.basename(fname):
return fname
for d in builtins.__xonsh_env__['PATH']:
for d in builtins.__xonsh_env__.get('PATH'):
if os.path.isdir(d):
files = os.listdir(d)
if ON_WINDOWS:
PATHEXT = builtins.__xonsh_env__.get('PATHEXT', [])
PATHEXT = builtins.__xonsh_env__.get('PATHEXT')
for dirfile in files:
froot, ext = os.path.splitext(dirfile)
if fname == froot and ext.upper() in PATHEXT:
return os.path.join(d, dirfile)
if fname in files:
return os.path.join(d, fname)
return None
@ -666,7 +664,7 @@ def load_builtins(execer=None):
builtins.execx = None if execer is None else execer.exec
builtins.compilex = None if execer is None else execer.compile
builtins.default_aliases = builtins.aliases = Aliases(DEFAULT_ALIASES)
builtins.aliases.update(bash_aliases())
builtins.aliases.update(load_foreign_aliases(issue_warning=False))
# history needs to be started after env and aliases
# would be nice to actually include non-detyped versions.
builtins.__xonsh_history__ = History(env=ENV.detype(), #aliases=builtins.aliases,

View file

@ -38,10 +38,6 @@ COMP_CWORD={n}
for ((i=0;i<${{#COMPREPLY[*]}};i++)) do echo ${{COMPREPLY[i]}}; done
"""
get_env = lambda name, default=None: builtins.__xonsh_env__.get(name, default)
def startswithlow(x, start, startlow=None):
"""True if x starts with a string or its lowercase version. The lowercase
version may be optionally be provided.
@ -73,7 +69,7 @@ def _normpath(p):
if trailing_slash:
p = os.path.join(p, '')
if ON_WINDOWS and get_env('FORCE_POSIX_PATHS', False):
if ON_WINDOWS and builtins.__xonsh_env__.get('FORCE_POSIX_PATHS'):
p = p.replace(os.sep, os.altsep)
return p
@ -125,7 +121,7 @@ class Completer(object):
ctx = ctx or {}
prefixlow = prefix.lower()
cmd = line.split(' ', 1)[0]
csc = get_env('CASE_SENSITIVE_COMPLETIONS', True)
csc = builtins.__xonsh_env__.get('CASE_SENSITIVE_COMPLETIONS')
startswither = startswithnorm if csc else startswithlow
if begidx == 0:
# the first thing we're typing; could be python or subprocess, so
@ -172,7 +168,7 @@ class Completer(object):
def _add_env(self, paths, prefix):
if prefix.startswith('$'):
csc = get_env('CASE_SENSITIVE_COMPLETIONS', True)
csc = builtins.__xonsh_env__.get('CASE_SENSITIVE_COMPLETIONS')
startswither = startswithnorm if csc else startswithlow
key = prefix[1:]
keylow = key.lower()
@ -186,8 +182,9 @@ class Completer(object):
def _add_cdpaths(self, paths, prefix):
"""Completes current prefix using CDPATH"""
csc = get_env('CASE_SENSITIVE_COMPLETIONS', True)
for cdp in get_env("CDPATH", []):
env = builtins.__xonsh_env__
csc = env.get('CASE_SENSITIVE_COMPLETIONS')
for cdp in env.get('CDPATH'):
test_glob = os.path.join(cdp, prefix) + '*'
for s in iglobpath(test_glob, ignore_case=(not csc)):
if os.path.isdir(s):
@ -197,7 +194,7 @@ class Completer(object):
"""Completes a command name based on what is on the $PATH"""
space = ' '
cmdlow = cmd.lower()
csc = get_env('CASE_SENSITIVE_COMPLETIONS', True)
csc = builtins.__xonsh_env__.get('CASE_SENSITIVE_COMPLETIONS')
startswither = startswithnorm if csc else startswithlow
return {s + space
for s in self._all_commands()
@ -207,7 +204,7 @@ class Completer(object):
"""Completes a name of a module to import."""
prefixlow = prefix.lower()
modules = set(sys.modules.keys())
csc = get_env('CASE_SENSITIVE_COMPLETIONS', True)
csc = builtins.__xonsh_env__.get('CASE_SENSITIVE_COMPLETIONS')
startswither = startswithnorm if csc else startswithlow
return {s for s in modules if startswither(s, prefix, prefixlow)}
@ -217,7 +214,7 @@ class Completer(object):
slash = '/'
tilde = '~'
paths = set()
csc = get_env('CASE_SENSITIVE_COMPLETIONS', True)
csc = builtins.__xonsh_env__.get('CASE_SENSITIVE_COMPLETIONS')
if prefix.startswith("'") or prefix.startswith('"'):
prefix = prefix[1:]
for s in iglobpath(prefix + '*', ignore_case=(not csc)):
@ -279,9 +276,10 @@ class Completer(object):
def _source_completions(self):
srcs = []
for f in builtins.__xonsh_env__.get('BASH_COMPLETIONS', ()):
for f in builtins.__xonsh_env__.get('BASH_COMPLETIONS'):
if os.path.isfile(f):
if ON_WINDOWS: # We need to "Unixify" Windows paths for Bash to understand
# We need to "Unixify" Windows paths for Bash to understand
if ON_WINDOWS:
f = RE_WIN_DRIVE.sub(lambda m: '/{0}/'.format(m.group(1).lower()), f).replace('\\', '/')
srcs.append('source ' + f)
return srcs
@ -346,7 +344,7 @@ class Completer(object):
if len(attr) == 0:
opts = [o for o in opts if not o.startswith('_')]
else:
csc = get_env('CASE_SENSITIVE_COMPLETIONS', True)
csc = builtins.__xonsh_env__.get('CASE_SENSITIVE_COMPLETIONS')
startswither = startswithnorm if csc else startswithlow
attrlow = attr.lower()
opts = [o for o in opts if startswither(o, attrlow)]
@ -407,7 +405,7 @@ class ManCompleter(object):
def option_complete(self, prefix, cmd):
"""Completes an option name, basing on content of man page."""
csc = get_env('CASE_SENSITIVE_COMPLETIONS', True)
csc = builtins.__xonsh_env__.get('CASE_SENSITIVE_COMPLETIONS')
startswither = startswithnorm if csc else startswithlow
if cmd not in self._options.keys():
try:

View file

@ -6,10 +6,7 @@ from glob import iglob
from argparse import ArgumentParser
DIRSTACK = []
"""
A list containing the currently remembered directories.
"""
"""A list containing the currently remembered directories."""
def _get_cwd():
try:
@ -42,7 +39,7 @@ def _try_cdpath(apath):
# in bash a full $ cd ./xonsh is needed.
# In xonsh a relative folder is allways preferred.
env = builtins.__xonsh_env__
cdpaths = env.get('CDPATH', [])
cdpaths = env.get('CDPATH')
for cdp in cdpaths:
for cdpath_prefixed_path in iglob(os.path.join(cdp, apath)):
return cdpath_prefixed_path
@ -92,15 +89,14 @@ def cd(args, stdin=None):
if not os.path.isdir(d):
return '', 'cd: {0} is not a directory\n'.format(d)
# now, push the directory onto the dirstack if AUTO_PUSHD is set
if cwd is not None and env.get('AUTO_PUSHD', False):
if cwd is not None and env.get('AUTO_PUSHD'):
pushd(['-n', '-q', cwd])
_change_working_directory(os.path.abspath(d))
return None, None
def pushd(args, stdin=None):
"""
xonsh command: pushd
"""xonsh command: pushd
Adds a directory to the top of the directory stack, or rotates the stack,
making the new top of the stack the current working directory.
@ -165,11 +161,11 @@ def pushd(args, stdin=None):
else:
DIRSTACK.insert(0, os.path.expanduser(os.path.abspath(new_pwd)))
maxsize = env.get('DIRSTACK_SIZE', 20)
maxsize = env.get('DIRSTACK_SIZE')
if len(DIRSTACK) > maxsize:
DIRSTACK = DIRSTACK[:maxsize]
if not args.quiet and not env.get('PUSHD_SILENT', False):
if not args.quiet and not env.get('PUSHD_SILENT'):
return dirs([], None)
return None, None
@ -190,7 +186,7 @@ def popd(args, stdin=None):
env = builtins.__xonsh_env__
if env.get('PUSHD_MINUS', False):
if env.get('PUSHD_MINUS'):
BACKWARD = '-'
FORWARD = '+'
else:
@ -238,15 +234,14 @@ def popd(args, stdin=None):
if args.cd:
_change_working_directory(os.path.abspath(new_pwd))
if not args.quiet and not env.get('PUSHD_SILENT', False):
if not args.quiet and not env.get('PUSHD_SILENT'):
return dirs([], None)
return None, None
def dirs(args, stdin=None):
"""
xonsh command: dirs
"""xonsh command: dirs
Displays the list of currently remembered directories. Can also be used
to clear the directory stack.
@ -261,7 +256,7 @@ def dirs(args, stdin=None):
env = builtins.__xonsh_env__
if env.get('PUSHD_MINUS', False):
if env.get('PUSHD_MINUS'):
BACKWARD = '-'
FORWARD = '+'
else:

View file

@ -1,6 +1,7 @@
"""Environment for the xonsh shell."""
import os
import re
import json
import socket
import string
import locale
@ -14,8 +15,9 @@ from xonsh import __version__ as XONSH_VERSION
from xonsh.tools import TERM_COLORS, ON_WINDOWS, ON_MAC, ON_LINUX, string_types, \
is_int, always_true, always_false, ensure_string, is_env_path, str_to_env_path, \
env_path_to_str, is_bool, to_bool, bool_to_str, is_history_tuple, to_history_tuple, \
history_tuple_to_str
history_tuple_to_str, is_float
from xonsh.dirstack import _get_cwd
from xonsh.foreign_shells import DEFAULT_SHELLS, load_foreign_envs
LOCALE_CATS = {
'LC_CTYPE': locale.LC_CTYPE,
@ -59,8 +61,103 @@ DEFAULT_ENSURERS = {
'XONSH_STORE_STDOUT': (is_bool, to_bool, bool_to_str),
'CASE_SENSITIVE_COMPLETIONS': (is_bool, to_bool, bool_to_str),
'BASH_COMPLETIONS': (is_env_path, str_to_env_path, env_path_to_str),
'TEEPTY_PIPE_DELAY': (is_float, float, str),
}
#
# Defaults
#
def default_value(f):
"""Decorator for making callable default values."""
f._xonsh_callable_default = True
return f
def is_callable_default(x):
"""Checks if a value is a callable default."""
return callable(x) and getattr(x, '_xonsh_callable_default', False)
DEFAULT_PROMPT = ('{BOLD_GREEN}{user}@{hostname}{BOLD_BLUE} '
'{cwd}{branch_color}{curr_branch} '
'{BOLD_BLUE}${NO_COLOR} ')
DEFAULT_TITLE = '{user}@{hostname}: {cwd} | xonsh'
@default_value
def xonsh_data_dir(env):
"""Ensures and returns the $XONSH_DATA_DIR"""
xdd = os.path.join(env.get('XDG_DATA_HOME'), 'xonsh')
os.makedirs(xdd, exist_ok=True)
return xdd
@default_value
def xonsh_config_dir(env):
"""Ensures and returns the $XONSH_CONFIG_DIR"""
xcd = os.path.join(env.get('XDG_CONFIG_HOME'), 'xonsh')
os.makedirs(xcd, exist_ok=True)
return xcd
@default_value
def xonshconfig(env):
"""Ensures and returns the $XONSHCONFIG"""
xcd = env.get('XONSH_CONFIG_DIR')
xc = os.path.join(xcd, 'config.json')
return xc
# Default values should generally be immutable, that way if a user wants
# to set them they have to do a copy and write them to the environment.
# try to keep this sorted.
DEFAULT_VALUES = {
'AUTO_PUSHD': False,
'BASH_COMPLETIONS': ('/usr/local/etc/bash_completion',
'/opt/local/etc/profile.d/bash_completion.sh') if ON_MAC \
else ('/etc/bash_completion',
'/usr/share/bash-completion/completions/git'),
'CASE_SENSITIVE_COMPLETIONS': ON_LINUX,
'CDPATH': (),
'DIRSTACK_SIZE': 20,
'FORCE_POSIX_PATHS': False,
'INDENT': ' ',
'LC_CTYPE': locale.setlocale(locale.LC_CTYPE),
'LC_COLLATE': locale.setlocale(locale.LC_COLLATE),
'LC_TIME': locale.setlocale(locale.LC_TIME),
'LC_MONETARY': locale.setlocale(locale.LC_MONETARY),
'LC_NUMERIC': locale.setlocale(locale.LC_NUMERIC),
'MULTILINE_PROMPT': '.',
'PATH': (),
'PATHEXT': (),
'PROMPT': DEFAULT_PROMPT,
'PROMPT_TOOLKIT_STYLES': None,
'PUSHD_MINUS': False,
'PUSHD_SILENT': False,
'SHELL_TYPE': 'readline',
'SUGGEST_COMMANDS': True,
'SUGGEST_MAX_NUM': 5,
'SUGGEST_THRESHOLD': 3,
'TEEPTY_PIPE_DELAY': 0.01,
'TITLE': DEFAULT_TITLE,
'XDG_CONFIG_HOME': os.path.expanduser(os.path.join('~', '.config')),
'XDG_DATA_HOME': os.path.expanduser(os.path.join('~', '.local', 'share')),
'XONSHCONFIG': xonshconfig,
'XONSHRC': os.path.expanduser('~/.xonshrc'),
'XONSH_CONFIG_DIR': xonsh_config_dir,
'XONSH_DATA_DIR': xonsh_data_dir,
'XONSH_HISTORY_FILE': os.path.expanduser('~/.xonsh_history.json'),
'XONSH_HISTORY_SIZE': (8128, 'commands'),
'XONSH_SHOW_TRACEBACK': False,
'XONSH_STORE_STDOUT': False,
}
class DefaultNotGivenType(object):
"""Singleton for representing when no default value is given."""
DefaultNotGiven = DefaultNotGivenType()
#
# actual environment
#
class Env(MutableMapping):
"""A xonsh environment, whose variables have limited typing
@ -82,6 +179,7 @@ class Env(MutableMapping):
"""If no initial environment is given, os.environ is used."""
self._d = {}
self.ensurers = {k: Ensurer(*v) for k, v in DEFAULT_ENSURERS.items()}
self.defaults = DEFAULT_VALUES
if len(args) == 0 and len(kwargs) == 0:
args = (os.environ, )
for key, val in dict(*args, **kwargs).items():
@ -168,6 +266,20 @@ class Env(MutableMapping):
del self._d[key]
self._detyped = None
def get(self, key, default=DefaultNotGiven):
"""The environment will look up default values from its own defaults if a
default is not given here.
"""
if key in self:
val = self[key]
elif default is DefaultNotGiven:
val = self.defaults.get(key, None)
if is_callable_default(val):
val = val(self)
else:
val = default
return val
def __iter__(self):
yield from self._d
@ -382,12 +494,6 @@ def branch_color():
TERM_COLORS['BOLD_GREEN'])
DEFAULT_PROMPT = ('{BOLD_GREEN}{user}@{hostname}{BOLD_BLUE} '
'{cwd}{branch_color}{curr_branch} '
'{BOLD_BLUE}${NO_COLOR} ')
DEFAULT_TITLE = '{user}@{hostname}: {cwd} | xonsh'
def _replace_home(x):
if ON_WINDOWS:
home = (builtins.__xonsh_env__['HOMEDRIVE'] +
@ -419,10 +525,10 @@ FORMATTER_DICT = dict(
curr_branch=current_branch,
branch_color=branch_color,
**TERM_COLORS)
DEFAULT_VALUES['FORMATTER_DICT'] = dict(FORMATTER_DICT)
_FORMATTER = string.Formatter()
def format_prompt(template=DEFAULT_PROMPT, formatter_dict=None):
"""Formats a xonsh prompt template string."""
template = template() if callable(template) else template
@ -447,10 +553,9 @@ def format_prompt(template=DEFAULT_PROMPT, formatter_dict=None):
RE_HIDDEN = re.compile('\001.*?\002')
def multiline_prompt():
"""Returns the filler text for the prompt in multiline scenarios."""
curr = builtins.__xonsh_env__.get('PROMPT', "set '$PROMPT = ...' $ ")
curr = builtins.__xonsh_env__.get('PROMPT')
curr = format_prompt(curr)
line = curr.rsplit('\n', 1)[1] if '\n' in curr else curr
line = RE_HIDDEN.sub('', line) # gets rid of colors
@ -460,7 +565,7 @@ def multiline_prompt():
# tail is the trailing whitespace
tail = line if headlen == 0 else line.rsplit(head[-1], 1)[1]
# now to constuct the actual string
dots = builtins.__xonsh_env__.get('MULTILINE_PROMPT', '.')
dots = builtins.__xonsh_env__.get('MULTILINE_PROMPT')
dots = dots() if callable(dots) else dots
if dots is None or len(dots) == 0:
return ''
@ -469,58 +574,37 @@ def multiline_prompt():
BASE_ENV = {
'XONSH_VERSION': XONSH_VERSION,
'INDENT': ' ',
'FORMATTER_DICT': dict(FORMATTER_DICT),
'PROMPT': DEFAULT_PROMPT,
'TITLE': DEFAULT_TITLE,
'MULTILINE_PROMPT': '.',
'XONSHRC': os.path.expanduser('~/.xonshrc'),
'XONSH_HISTORY_SIZE': (8128, 'commands'),
'XONSH_HISTORY_FILE': os.path.expanduser('~/.xonsh_history.json'),
'XONSH_STORE_STDOUT': False,
'LC_CTYPE': locale.setlocale(locale.LC_CTYPE),
'LC_COLLATE': locale.setlocale(locale.LC_COLLATE),
'LC_TIME': locale.setlocale(locale.LC_TIME),
'LC_MONETARY': locale.setlocale(locale.LC_MONETARY),
'LC_NUMERIC': locale.setlocale(locale.LC_NUMERIC),
'SHELL_TYPE': 'readline',
'CASE_SENSITIVE_COMPLETIONS': ON_LINUX,
}
try:
BASE_ENV['LC_MESSAGES'] = locale.setlocale(locale.LC_MESSAGES)
BASE_ENV['LC_MESSAGES'] = DEFAULT_VALUES['LC_MESSAGES'] = \
locale.setlocale(locale.LC_MESSAGES)
except AttributeError:
pass
if ON_MAC:
BASE_ENV['BASH_COMPLETIONS'] = [
'/usr/local/etc/bash_completion',
'/opt/local/etc/profile.d/bash_completion.sh'
]
def load_static_config(ctx):
"""Loads a static configuration file from a given context, rather than the
current environment.
"""
env = {}
env['XDG_CONFIG_HOME'] = ctx.get('XDG_CONFIG_HOME',
DEFAULT_VALUES['XDG_CONFIG_HOME'])
env['XONSH_CONFIG_DIR'] = ctx['XONSH_CONFIG_DIR'] if 'XONSH_CONFIG_DIR' in ctx \
else xonsh_config_dir(env)
env['XONSHCONFIG'] = ctx['XONSHCONFIG'] if 'XONSHCONFIG' in ctx \
else xonshconfig(env)
config = env['XONSHCONFIG']
if os.path.isfile(config):
with open(config, 'r') as f:
conf = json.load(f)
else:
BASE_ENV['BASH_COMPLETIONS'] = [
'/etc/bash_completion', '/usr/share/bash-completion/completions/git'
]
def bash_env():
"""Attempts to compute the bash envinronment variables."""
currenv = None
if hasattr(builtins, '__xonsh_env__'):
currenv = builtins.__xonsh_env__.detype()
try:
s = subprocess.check_output(['bash', '-i', '-l'],
input='env',
env=currenv,
stderr=subprocess.PIPE,
universal_newlines=True)
except (subprocess.CalledProcessError, FileNotFoundError):
s = ''
items = [line.split('=', 1) for line in s.splitlines() if '=' in line]
env = dict(items)
return env
conf = {}
return conf
def xonshrc_context(rcfile=None, execer=None):
@ -544,36 +628,14 @@ def xonshrc_context(rcfile=None, execer=None):
return env
def recursive_base_env_update(env):
"""Updates the environment with members that may rely on previously defined
members. Takes an env as its argument.
"""
home = os.path.expanduser('~')
if 'XONSH_DATA_DIR' not in env:
xdgdh = env.get('XDG_DATA_HOME', os.path.join(home, '.local', 'share'))
env['XONSH_DATA_DIR'] = xdd = os.path.join(xdgdh, 'xonsh')
os.makedirs(xdd, exist_ok=True)
if 'XONSH_CONFIG_DIR' not in env:
xdgch = env.get('XDG_CONFIG_HOME', os.path.join(home, '.config'))
env['XONSH_CONFIG_DIR'] = xcd = os.path.join(xdgch, 'xonsh')
os.makedirs(xcd, exist_ok=True)
def default_env(env=None):
"""Constructs a default xonsh environment."""
# in order of increasing precedence
ctx = dict(BASE_ENV)
ctx.update(os.environ)
ctx.update(bash_env())
if ON_WINDOWS:
def windows_env_fixes(ctx):
"""Environment fixes for Windows. Operates in-place."""
# Windows default prompt doesn't work.
ctx['PROMPT'] = DEFAULT_PROMPT
# remove these bash variables which only cause problems.
for ev in ['HOME', 'OLDPWD']:
if ev in ctx:
del ctx[ev]
# Override path-related bash variables; on Windows bash uses
# /c/Windows/System32 syntax instead of C:\\Windows\\System32
# which messes up these environment variables for xonsh.
@ -582,10 +644,21 @@ def default_env(env=None):
ctx[ev] = os.environ[ev]
elif ev in ctx:
del ctx[ev]
ctx['PWD'] = _get_cwd()
def default_env(env=None):
"""Constructs a default xonsh environment."""
# in order of increasing precedence
ctx = dict(BASE_ENV)
ctx.update(os.environ)
conf = load_static_config(ctx)
ctx.update(conf.get('env', ()))
ctx.update(load_foreign_envs(shells=conf.get('foreign_shells', DEFAULT_SHELLS),
issue_warning=False))
if ON_WINDOWS:
windows_env_fixes(ctx)
# finalize env
recursive_base_env_update(ctx)
if env is not None:
ctx.update(env)
return ctx

236
xonsh/foreign_shells.py Normal file
View file

@ -0,0 +1,236 @@
"""Tools to help interface with foreign shells, such as Bash."""
import os
import re
import json
import shlex
import builtins
import subprocess
from warnings import warn
from functools import lru_cache
from collections import MutableMapping, Mapping, Sequence
from xonsh.tools import to_bool, ensure_string
COMMAND = """
echo __XONSH_ENV_BEG__
{envcmd}
echo __XONSH_ENV_END__
echo __XONSH_ALIAS_BEG__
{aliascmd}
echo __XONSH_ALIAS_END__
""".strip()
@lru_cache()
def foreign_shell_data(shell, interactive=True, login=False, envcmd='env',
aliascmd='alias', extra_args=(), currenv=None,
safe=True):
"""Extracts data from a foreign (non-xonsh) shells. Currently this gets
the environment and aliases, but may be extended in the future.
Parameters
----------
shell : str
The name of the shell, such as 'bash' or '/bin/sh'.
interactive : bool, optional
Whether the shell should be run in interactive mode.
login : bool, optional
Whether the shell should be a login shell.
envcmd : str, optional
The command to generate environment output with.
aliascmd : str, optional
The command to generate alais output with.
extra_args : tuple of str, optional
Addtional command line options to pass into the shell.
currenv : tuple of items or None, optional
Manual override for the current environment.
safe : bool, optional
Flag for whether or not to safely handle exceptions and other errors.
Returns
-------
env : dict
Dictionary of shell's environment
aliases : dict
Dictionary of shell's alaiases.
"""
cmd = [shell]
cmd.extend(extra_args) # needs to come here for GNU long options
if interactive:
cmd.append('-i')
if login:
cmd.append('-l')
cmd.append('-c')
cmd.append(COMMAND.format(envcmd=envcmd, aliascmd=aliascmd))
if currenv is None and hasattr(builtins, '__xonsh_env__'):
currenv = builtins.__xonsh_env__.detype()
elif currenv is not None:
currenv = dict(currenv)
try:
s = subprocess.check_output(cmd,stderr=subprocess.PIPE, env=currenv,
universal_newlines=True)
except (subprocess.CalledProcessError, FileNotFoundError):
if not safe:
raise
return {}, {}
env = parse_env(s)
aliases = parse_aliases(s)
return env, aliases
ENV_RE = re.compile('__XONSH_ENV_BEG__\n(.*)__XONSH_ENV_END__', flags=re.DOTALL)
def parse_env(s):
"""Parses the environment portion of string into a dict."""
m = ENV_RE.search(s)
if m is None:
return {}
g1 = m.group(1)
items = [line.split('=', 1) for line in g1.splitlines() if '=' in line]
env = dict(items)
return env
ALIAS_RE = re.compile('__XONSH_ALIAS_BEG__\n(.*)__XONSH_ALIAS_END__',
flags=re.DOTALL)
def parse_aliases(s):
"""Parses the aliases portion of string into a dict."""
m = ALIAS_RE.search(s)
if m is None:
return {}
g1 = m.group(1)
items = [line.split('=', 1) for line in g1.splitlines() if \
line.startswith('alias ') and '=' in line]
aliases = {}
for key, value in items:
try:
key = key[6:] # lstrip 'alias '
# undo bash's weird quoting of single quotes (sh_single_quote)
value = value.replace('\'\\\'\'', '\'')
# 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)
except ValueError as exc:
warn('could not parse alias "{0}": {1!r}'.format(key, exc),
RuntimeWarning)
continue
aliases[key] = value
return aliases
VALID_SHELL_PARAMS = frozenset(['shell', 'interactive', 'login', 'envcmd',
'aliascmd', 'extra_args', 'currenv', 'safe'])
def ensure_shell(shell):
"""Ensures that a mapping follows the shell specification."""
if not isinstance(shell, MutableMapping):
shell = dict(shell)
shell_keys = set(shell.keys())
if not (shell_keys <= VALID_SHELL_PARAMS):
msg = 'unknown shell keys: {0}'
raise KeyError(msg.format(shell_keys - VALID_SHELL_PARAMS))
shell['shell'] = ensure_string(shell['shell'])
if 'interactive' in shell_keys:
shell['interactive'] = to_bool(shell['interactive'])
if 'login' in shell_keys:
shell['login'] = to_bool(shell['login'])
if 'envcmd' in shell_keys:
shell['envcmd'] = eunsure_string(shell['envcmd'])
if 'aliascmd' in shell_keys:
shell['aliascmd'] = eunsure_string(shell['aliascmd'])
if 'extra_args' in shell_keys and not isinstance(shell['extra_args'], tuple):
shell['extra_args'] = tuple(map(ensure_string, shell['extra_args']))
if 'currenv' in shell_keys and not isinstance(shell['currenv'], tuple):
ce = shell['currenv']
if isinstance(ce, Mapping):
ce = tuple([(ensure_string(k), v) for k, v in ce.items()])
elif isinstance(ce, Sequence):
ce = tuple([(ensure_string(k), v) for k, v in ce])
else:
raise RuntimeError('unrecognized type for currenv')
shell['currenv'] = ce
if 'safe' in shell_keys:
shell['safe'] = to_bool(shell['safe'])
return shell
DEFAULT_SHELLS = ({'shell': 'bash'},)
def _get_shells(shells=None, config=None, issue_warning=True):
if shells is not None and config is not None:
raise RuntimeError('Only one of shells and config may be non-None.')
elif shells is not None:
pass
else:
if config is None:
config = builtins.__xonsh_env__.get('XONSHCONFIG')
if os.path.isfile(config):
with open(config, 'r') as f:
conf = json.load(f)
shells = conf.get('foreign_shells', DEFAULT_SHELLS)
else:
if issue_warning:
msg = 'could not find xonsh config file ($XONSHCONFIG) at {0!r}'
warn(msg.format(config), RuntimeWarning)
shells = DEFAULT_SHELLS
return shells
def load_foreign_envs(shells=None, config=None, issue_warning=True):
"""Loads environments from foreign shells.
Parameters
----------
shells : sequence of dicts, optional
An iterable of dicts that can be passed into foreign_shell_data() as
keyword arguments. Not compatible with config not being None.
config : str of None, optional
Path to the static config file. Not compatible with shell not being None.
If both shell and config is None, then it will be read from the
$XONSHCONFIG environment variable.
issue_warning : bool, optional
Issues warnings if config file cannot be found.
Returns
-------
env : dict
A dictionary of the merged environments.
"""
shells = _get_shells(shells=shells, config=config, issue_warning=issue_warning)
env = {}
for shell in shells:
shell = ensure_shell(shell)
shenv, _ = foreign_shell_data(**shell)
env.update(shenv)
return env
def load_foreign_aliases(shells=None, config=None, issue_warning=True):
"""Loads aliases from foreign shells.
Parameters
----------
shells : sequence of dicts, optional
An iterable of dicts that can be passed into foreign_shell_data() as
keyword arguments. Not compatible with config not being None.
config : str of None, optional
Path to the static config file. Not compatible with shell not being None.
If both shell and config is None, then it will be read from the
$XONSHCONFIG environment variable.
issue_warning : bool, optional
Issues warnings if config file cannot be found.
Returns
-------
aliases : dict
A dictionary of the merged aliases.
"""
shells = _get_shells(shells=shells, config=config, issue_warning=issue_warning)
aliases = {}
for shell in shells:
shell = ensure_shell(shell)
_, shaliases = foreign_shell_data(**shell)
aliases.update(shaliases)
return aliases

View file

@ -29,7 +29,7 @@ class HistoryGC(Thread):
time.sleep(0.01)
env = builtins.__xonsh_env__
if self.size is None:
hsize, units = env.get('XONSH_HISTORY_SIZE', (8128, 'commands'))
hsize, units = env.get('XONSH_HISTORY_SIZE')
else:
hsize, units = to_history_tuple(self.size)
files = self.unlocked_files()
@ -79,7 +79,7 @@ class HistoryGC(Thread):
"""Finds the history files and returns the ones that are unlocked, this is
sorted by the last closed time. Returns a list of (timestamp, file) tuples.
"""
xdd = os.path.abspath(builtins.__xonsh_env__['XONSH_DATA_DIR'])
xdd = os.path.abspath(builtins.__xonsh_env__.get('XONSH_DATA_DIR'))
fs = [f for f in iglob(os.path.join(xdd, 'xonsh-*.json'))]
files = []
for f in fs:
@ -209,7 +209,7 @@ class History(object):
"""
self.sessionid = sid = uuid.uuid4() if sessionid is None else sessionid
if filename is None:
self.filename = os.path.join(builtins.__xonsh_env__['XONSH_DATA_DIR'],
self.filename = os.path.join(builtins.__xonsh_env__.get('XONSH_DATA_DIR'),
'xonsh-{0}.json'.format(sid))
else:
self.filename = filename

View file

@ -10,6 +10,7 @@ import io
import os
import sys
import time
import builtins
from threading import Thread
from collections import Sequence
from subprocess import Popen, PIPE, DEVNULL, STDOUT, TimeoutExpired
@ -370,7 +371,9 @@ class TeePTYProc(object):
self._tpty = tpty = TeePTY()
if preexec_fn is not None:
preexec_fn()
tpty.spawn(args, env=env, stdin=stdin)
delay = builtins.__xonsh_env__.get('TEEPTY_PIPE_DELAY') if \
hasattr(builtins, '__xonsh_env__') else None
tpty.spawn(args, env=env, stdin=stdin, delay=delay)
@property
def pid(self):

View file

@ -18,8 +18,7 @@ from xonsh.prompt_toolkit_key_bindings import load_xonsh_bindings
def setup_history():
"""Creates history object."""
env = builtins.__xonsh_env__
hfile = env.get('XONSH_HISTORY_FILE',
os.path.expanduser('~/.xonsh_history'))
hfile = env.get('XONSH_HISTORY_FILE')
history = LimitedFileHistory()
try:
history.read_history_file(hfile)
@ -31,9 +30,8 @@ def setup_history():
def teardown_history(history):
"""Tears down the history object."""
env = builtins.__xonsh_env__
hsize = env.get('XONSH_HISTORY_SIZE', (8128, 'commands'))[0]
hfile = env.get('XONSH_HISTORY_FILE',
os.path.expanduser('~/.xonsh_history'))
hsize = env.get('XONSH_HISTORY_SIZE')[0]
hfile = env.get('XONSH_HISTORY_FILE')
try:
history.save_history_to_file(hfile, hsize)
except PermissionError:
@ -97,7 +95,8 @@ class PromptToolkitShell(BaseShell):
# update with the prompt styles
styles.update({t: s for (t, s) in zip(tokens, cstyles)})
# Update with with any user styles
userstyle = builtins.__xonsh_env__.get('PROMPT_TOOLKIT_STYLES', {})
userstyle = builtins.__xonsh_env__.get('PROMPT_TOOLKIT_STYLES')
if userstyle is not None:
styles.update(userstyle)
return get_tokens, CustomStyle

View file

@ -47,7 +47,7 @@ def setup_readline():
readline.parse_and_bind('"\e[B": history-search-forward')
readline.parse_and_bind('"\e[A": history-search-backward')
# Setup Shift-Tab to indent
readline.parse_and_bind('"\e[Z": "{0}"'.format(env.get('INDENT', '')))
readline.parse_and_bind('"\e[Z": "{0}"'.format(env.get('INDENT')))
# handle tab completion differences found in libedit readline compatibility
# as discussed at http://stackoverflow.com/a/7116997
@ -139,12 +139,12 @@ class ReadlineShell(BaseShell, Cmd):
self._current_indent = ''
elif line.rstrip()[-1] == ':':
ind = line[:len(line) - len(line.lstrip())]
ind += builtins.__xonsh_env__.get('INDENT', '')
ind += builtins.__xonsh_env__.get('INDENT')
readline.set_pre_input_hook(_insert_text_func(ind, readline))
self._current_indent = ind
elif line.split(maxsplit=1)[0] in DEDENT_TOKENS:
env = builtins.__xonsh_env__
ind = self._current_indent[:-len(env.get('INDENT', ''))]
ind = self._current_indent[:-len(env.get('INDENT'))]
readline.set_pre_input_hook(_insert_text_func(ind, readline))
self._current_indent = ind
else:

View file

@ -26,25 +26,26 @@ class Shell(object):
def __init__(self, ctx=None, shell_type=None, **kwargs):
self._init_environ(ctx)
env = builtins.__xonsh_env__
# pick a valid shell
if shell_type is not None:
env['SHELL_TYPE'] = shell_type
if env['SHELL_TYPE'] == 'prompt_toolkit':
shell_type = env.get('SHELL_TYPE')
if shell_type == 'prompt_toolkit':
if not is_prompt_toolkit_available():
warn('prompt_toolkit is not available, using readline instead.')
env['SHELL_TYPE'] = 'readline'
if env['SHELL_TYPE'] == 'prompt_toolkit':
shell_type = env['SHELL_TYPE'] = 'readline'
# actually make the shell
if shell_type == 'prompt_toolkit':
from xonsh.prompt_toolkit_shell import PromptToolkitShell
self.shell = PromptToolkitShell(execer=self.execer,
ctx=self.ctx, **kwargs)
elif env['SHELL_TYPE'] == 'readline':
elif shell_type == 'readline':
from xonsh.readline_shell import ReadlineShell
self.shell = ReadlineShell(execer=self.execer,
ctx=self.ctx, **kwargs)
else:
raise XonshError('{} is not recognized as a shell type'.format(
env['SHELL_TYPE']))
shell_type))
# allows history garbace colector to start running
builtins.__xonsh_history__.gc.wait_for_shell = False
@ -58,7 +59,7 @@ class Shell(object):
if ctx is not None:
self.ctx = ctx
else:
rc = env.get('XONSHRC', None)
rc = env.get('XONSHRC')
self.ctx = xonshrc_context(rcfile=rc, execer=self.execer)
builtins.__xonsh_ctx__ = self.ctx
self.ctx['__name__'] = '__main__'

View file

@ -11,6 +11,7 @@ import os
import sys
import tty
import pty
import time
import array
import fcntl
import select
@ -78,7 +79,7 @@ class TeePTY(object):
self._temp_stdin.close()
self._temp_stdin = None
def spawn(self, argv=None, env=None, stdin=None):
def spawn(self, argv=None, env=None, stdin=None, delay=None):
"""Create a spawned process. Based on the code for pty.spawn().
This cannot be used except from the main thread.
@ -88,6 +89,12 @@ class TeePTY(object):
Arguments to pass in as subprocess. In None, will execute $SHELL.
env : Mapping, optional
Environment to pass execute in.
delay : float, optional
Delay timing before executing process if piping in data. The value
is passed into time.sleep() so it is in [seconds]. If delay is None,
its value will attempted to be looked up from the environment
variable $TEEPTY_PIPE_DELAY, from the passed in env or os.environ.
If not present or not positive valued, no delay is used.
Returns
-------
@ -104,6 +111,10 @@ class TeePTY(object):
self.pid = pid
self.master_fd = master_fd
if pid == pty.CHILD:
# determine if a piping delay is needed.
if self._temp_stdin is not None:
self._delay_for_pipe(env=env, delay=delay)
# ok, go
if env is None:
os.execvp(argv[0], argv)
else:
@ -256,6 +267,33 @@ class TeePTY(object):
else:
raise ValueError('stdin not understood {0!r}'.format(stdin))
def _delay_for_pipe(self, env=None, delay=None):
# This delay is sometimes needed because the temporary stdin file that
# is being written (the pipe) may not have even hits its first flush()
# call by the time the spawned process starts up and determines there
# is nothing in the file. The spawn can thus exit, without doing any
# real work. Consider the case of piping something into grep:
#
# $ ps aux | grep root
#
# grep will exit on EOF and so there is a race between the buffersize
# and flushing the temporary file and grep. However, this race is not
# always meaningful. Pagers, for example, update when the file is written
# to. So what is important is that we start the spawned process ASAP:
#
# $ ps aux | less
#
# So there is a push-and-pull between the the competing objectives of
# not blocking and letting the spawned process have enough to work with
# such that it doesn't exit prematurely. Unfortunately, there is no
# way to know a priori how big the file is, how long the spawned process
# will run for, etc. Thus as user-definable delay let's the user
# find something that works for them.
if delay is None:
delay = (env or os.environ).get('TEEPTY_PIPE_DELAY', -1.0)
delay = float(delay)
if 0.0 < delay:
time.sleep(delay)
if __name__ == '__main__':
@ -266,4 +304,4 @@ if __name__ == '__main__':
print('-=-'*10)
print(tpty)
print('-=-'*10)
print('Returned with status {0}'.format(tpty.rtn))
print('Returned with status {0}'.format(tpty.returncode))

View file

@ -342,11 +342,11 @@ def command_not_found(cmd):
def suggest_commands(cmd, env, aliases):
"""Suggests alternative commands given an environment and aliases."""
suggest_cmds = env.get('SUGGEST_COMMANDS', True)
suggest_cmds = env.get('SUGGEST_COMMANDS')
if not suggest_cmds:
return
thresh = env.get('SUGGEST_THRESHOLD', 3)
max_sugg = env.get('SUGGEST_MAX_NUM', 5)
thresh = env.get('SUGGEST_THRESHOLD')
max_sugg = env.get('SUGGEST_MAX_NUM')
if max_sugg < 0:
max_sugg = float('inf')
@ -357,7 +357,7 @@ def suggest_commands(cmd, env, aliases):
if levenshtein(a.lower(), cmd, thresh) < thresh:
suggested[a] = 'Alias'
for d in filter(os.path.isdir, env.get('PATH', [])):
for d in filter(os.path.isdir, env.get('PATH')):
for f in os.listdir(d):
if f not in suggested:
if levenshtein(f.lower(), cmd, thresh) < thresh:
@ -388,7 +388,7 @@ def print_exception():
if 'XONSH_SHOW_TRACEBACK' not in builtins.__xonsh_env__:
sys.stderr.write('xonsh: For full traceback set: '
'$XONSH_SHOW_TRACEBACK = True\n')
if builtins.__xonsh_env__.get('XONSH_SHOW_TRACEBACK', False):
if builtins.__xonsh_env__.get('XONSH_SHOW_TRACEBACK'):
traceback.print_exc()
else:
exc_type, exc_value, exc_traceback = sys.exc_info()
@ -401,15 +401,12 @@ def print_exception():
def levenshtein(a, b, max_dist=float('inf')):
"""Calculates the Levenshtein distance between a and b."""
n, m = len(a), len(b)
if abs(n - m) > max_dist:
return float('inf')
if n > m:
# Make sure n <= m, to use O(min(n,m)) space
a, b = b, a
n, m = m, n
current = range(n + 1)
for i in range(1, m + 1):
previous, current = current, [i] + [0] * n
@ -419,7 +416,6 @@ def levenshtein(a, b, max_dist=float('inf')):
if a[j - 1] != b[i - 1]:
change = change + 1
current[j] = min(add, delete, change)
return current[n]
@ -441,7 +437,6 @@ def escape_windows_title_string(s):
"""
for c in '^&<>|':
s = s.replace(c, '^' + c)
s = s.replace('/?', '/.')
return s
@ -474,6 +469,11 @@ def is_int(x):
return isinstance(x, int)
def is_float(x):
"""Tests if something is a float"""
return isinstance(x, float)
def always_true(x):
"""Returns True"""
return True