mirror of
https://github.com/xonsh/xonsh.git
synced 2025-03-06 09:20:57 +01:00
Merge branch 'teepipe'
This commit is contained in:
commit
bcd9f0cce0
23 changed files with 811 additions and 276 deletions
10
docs/api/foreign_shells.rst
Normal file
10
docs/api/foreign_shells.rst
Normal file
|
@ -0,0 +1,10 @@
|
|||
.. _xonsh_foreign_shells:
|
||||
|
||||
******************************************************
|
||||
Foreign Shell Tools (``xonsh.foreign_shells``)
|
||||
******************************************************
|
||||
|
||||
.. automodule:: xonsh.foreign_shells
|
||||
:members:
|
||||
:undoc-members:
|
||||
:inherited-members:
|
|
@ -51,5 +51,6 @@ For those of you who want the gritty details.
|
|||
lazyjson
|
||||
teepty
|
||||
openpy
|
||||
foreign_shells
|
||||
main
|
||||
pyghooks
|
||||
|
|
197
docs/envvars.rst
197
docs/envvars.rst
|
@ -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.
|
||||
|
||||
|
|
|
@ -67,6 +67,7 @@ Contents
|
|||
tutorial
|
||||
tutorial_hist
|
||||
xonshrc
|
||||
xonshconfig
|
||||
envvars
|
||||
aliases
|
||||
windows
|
||||
|
|
12
docs/xonshconfig.json
Normal file
12
docs/xonshconfig.json
Normal 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
84
docs/xonshconfig.rst
Normal 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
5
tests/bashrc.sh
Normal file
|
@ -0,0 +1,5 @@
|
|||
export EMERALD="SWORD"
|
||||
alias ll='ls -a -lF'
|
||||
alias la='ls -A'
|
||||
export MIGHTY=WARRIOR
|
||||
alias l='ls -CF'
|
56
tests/test_foreign_shells.py
Normal file
56
tests/test_foreign_shells.py
Normal 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()
|
|
@ -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')
|
||||
|
|
|
@ -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']
|
||||
|
||||
|
||||
|
|
|
@ -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']
|
||||
try:
|
||||
p = format_prompt(p)
|
||||
except Exception:
|
||||
print_exception()
|
||||
else:
|
||||
p = "set '$PROMPT = ...' $ "
|
||||
p = env.get('PROMPT')
|
||||
try:
|
||||
p = format_prompt(p)
|
||||
except Exception:
|
||||
print_exception()
|
||||
self.settitle()
|
||||
return p
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
243
xonsh/environ.py
243
xonsh/environ.py
|
@ -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'
|
||||
]
|
||||
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
|
||||
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:
|
||||
conf = {}
|
||||
return conf
|
||||
|
||||
|
||||
def xonshrc_context(rcfile=None, execer=None):
|
||||
|
@ -544,19 +628,23 @@ 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 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.
|
||||
for ev in ['PATH', 'TEMP', 'TMP']:
|
||||
if ev in os.environ:
|
||||
ctx[ev] = os.environ[ev]
|
||||
elif ev in ctx:
|
||||
del ctx[ev]
|
||||
ctx['PWD'] = _get_cwd()
|
||||
|
||||
|
||||
def default_env(env=None):
|
||||
|
@ -564,28 +652,13 @@ def default_env(env=None):
|
|||
# in order of increasing precedence
|
||||
ctx = dict(BASE_ENV)
|
||||
ctx.update(os.environ)
|
||||
ctx.update(bash_env())
|
||||
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 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.
|
||||
for ev in ['PATH', 'TEMP', 'TMP']:
|
||||
if ev in os.environ:
|
||||
ctx[ev] = os.environ[ev]
|
||||
elif ev in ctx:
|
||||
del ctx[ev]
|
||||
|
||||
ctx['PWD'] = _get_cwd()
|
||||
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
236
xonsh/foreign_shells.py
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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', {})
|
||||
styles.update(userstyle)
|
||||
userstyle = builtins.__xonsh_env__.get('PROMPT_TOOLKIT_STYLES')
|
||||
if userstyle is not None:
|
||||
styles.update(userstyle)
|
||||
|
||||
return get_tokens, CustomStyle
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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__'
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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:
|
||||
|
@ -387,8 +387,8 @@ def print_exception():
|
|||
"""Print exceptions with/without traceback."""
|
||||
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):
|
||||
'$XONSH_SHOW_TRACEBACK = True\n')
|
||||
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
|
||||
|
|
Loading…
Add table
Reference in a new issue