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
|
lazyjson
|
||||||
teepty
|
teepty
|
||||||
openpy
|
openpy
|
||||||
|
foreign_shells
|
||||||
main
|
main
|
||||||
pyghooks
|
pyghooks
|
||||||
|
|
197
docs/envvars.rst
197
docs/envvars.rst
|
@ -11,60 +11,68 @@ applicable.
|
||||||
* - variable
|
* - variable
|
||||||
- default
|
- default
|
||||||
- description
|
- description
|
||||||
* - PROMPT
|
* - ANSICON
|
||||||
- xonsh.environ.DEFAULT_PROMPT
|
- No default set
|
||||||
- The prompt text. May contain keyword arguments which are auto-formatted,
|
- This is used on Windows to set the title, if available.
|
||||||
see `Customizing the Prompt <tutorial.html#customizing-the-prompt>`_.
|
* - AUTO_PUSHD
|
||||||
* - MULTILINE_PROMPT
|
- ``False``
|
||||||
- ``'.'``
|
- Flag for automatically pushing directorties onto the directory stack.
|
||||||
- Prompt text for 2nd+ lines of input, may be str or function which returns a str.
|
* - BASH_COMPLETIONS
|
||||||
* - TITLE
|
- Normally this is ``('/etc/bash_completion',
|
||||||
- xonsh.environ.DEFAULT_TITLE
|
'/usr/share/bash-completion/completions/git')``
|
||||||
- The title text for the window in which xonsh is running. Formatted in the same
|
but on Mac is ``'/usr/local/etc/bash_completion',
|
||||||
manner as PROMPT,
|
'/opt/local/etc/profile.d/bash_completion.sh')``.
|
||||||
see `Customizing the Prompt <tutorial.html#customizing-the-prompt>`_.
|
- 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
|
* - FORMATTER_DICT
|
||||||
- xonsh.environ.FORMATTER_DICT
|
- xonsh.environ.FORMATTER_DICT
|
||||||
- Dictionary containing variables to be used when formatting PROMPT and TITLE
|
- Dictionary containing variables to be used when formatting PROMPT and TITLE
|
||||||
see `Customizing the Prompt <tutorial.html#customizing-the-prompt>`_.
|
see `Customizing the Prompt <tutorial.html#customizing-the-prompt>`_.
|
||||||
* - XONSHRC
|
* - INDENT
|
||||||
- ``'~/.xonshrc'``
|
- ``' '``
|
||||||
- Location of run control file.
|
- Indentation string for multiline input
|
||||||
* - XONSH_HISTORY_SIZE
|
* - MULTILINE_PROMPT
|
||||||
- ``(8128, 'commands')`` or ``'8128 commands'``
|
- ``'.'``
|
||||||
- Value and units tuple that sets the size of history after garbage collection.
|
- Prompt text for 2nd+ lines of input, may be str or function which returns
|
||||||
Canonical units are ``'commands'`` for the number of past commands executed,
|
a str.
|
||||||
``'files'`` for the number of history files to keep, ``'s'`` for the number of
|
* - OLDPWD
|
||||||
seconds in the past that are allowed, and ``'b'`` for the number of bytes that
|
- No default
|
||||||
are allowed for history to consume. Common abbreviations, such as ``6 months``
|
- Used to represent a previous present working directory.
|
||||||
or ``1 GB`` are also allowed.
|
* - PATH
|
||||||
* - XONSH_HISTORY_FILE
|
- ``()``
|
||||||
- ``'~/.xonsh_history'``
|
- List of strings representing where to look for executables.
|
||||||
- Location of history file (deprecated).
|
* - PATHEXT
|
||||||
* - XONSH_STORE_STDOUT
|
- ``()``
|
||||||
|
- 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``
|
- ``False``
|
||||||
- Whether or not to store the stdout and stderr streams in the history files.
|
- Flag for directory pushing functionality. False is the normal behaviour.
|
||||||
* - XONSH_INTERACTIVE
|
* - PUSHD_SILENT
|
||||||
-
|
- ``False``
|
||||||
- ``True`` if xonsh is running interactively, and ``False`` otherwise.
|
- Whether or not to supress directory stack manipulation output.
|
||||||
* - 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.
|
|
||||||
* - SHELL_TYPE
|
* - SHELL_TYPE
|
||||||
- ``'readline'``
|
- ``'readline'``
|
||||||
- Which shell is used. Currently two shell types are supported: ``'readline'`` that
|
- 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>`_
|
`prompt_toolkit <https://github.com/jonathanslenders/python-prompt-toolkit>`_
|
||||||
library installed. To specify which shell should be used, do so in the run
|
library installed. To specify which shell should be used, do so in the run
|
||||||
control file.
|
control file.
|
||||||
* - CDPATH
|
* - SUGGEST_COMMANDS
|
||||||
- ``[]``
|
- ``True``
|
||||||
- A list of paths to be used as roots for a ``cd``, breaking compatibility with
|
- When a user types an invalid command, xonsh will try to offer suggestions of
|
||||||
bash, xonsh always prefer an existing relative path.
|
similar valid commands if this is ``True``.
|
||||||
* - XONSH_SHOW_TRACEBACK
|
* - SUGGEST_MAX_NUM
|
||||||
- Not defined
|
- ``5``
|
||||||
- Controls if a traceback is shown exceptions occur in the shell. Set ``'True'``
|
- xonsh will show at most this many suggestions in response to an invalid command.
|
||||||
to always show or ``'False'`` to always hide. If undefined then traceback is
|
If negative, there is no limit to how many suggestions are shown.
|
||||||
hidden but a notice is shown on how to enable the traceback.
|
* - SUGGEST_THRESHOLD
|
||||||
* - CASE_SENSITIVE_COMPLETIONS
|
- ``3``
|
||||||
- ``True`` on Linux, otherwise ``False``
|
- An error threshold. If the Levenshtein distance between the entered command and
|
||||||
- Sets whether completions should be case sensitive or case insensitive.
|
a valid command is less than this value, the valid command will be offered as a
|
||||||
* - FORCE_POSIX_PATHS
|
suggestion.
|
||||||
- Not defined
|
* - TEEPTY_PIPE_DELAY
|
||||||
- Forces forward slashes (``/``) on Windows systems when using auto completion if
|
- ``0.01``
|
||||||
set to anything truthy.
|
- The number of [seconds] to delay a spawned process if it has information
|
||||||
* - XONSH_DATA_DIR
|
being piped in via stdin. This value must be a float. If a value less than
|
||||||
- ``$XDG_DATA_HOME/xonsh``
|
or equal to zero is passed in, no delay is used. This can be used to fix
|
||||||
- This is the location where xonsh data files are stored, such as history.
|
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
|
* - XONSH_CONFIG_DIR
|
||||||
- ``$XDG_CONFIG_HOME/xonsh``
|
- ``$XDG_CONFIG_HOME/xonsh``
|
||||||
- This is location where xonsh configuration information is stored.
|
- 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
|
||||||
tutorial_hist
|
tutorial_hist
|
||||||
xonshrc
|
xonshrc
|
||||||
|
xonshconfig
|
||||||
envvars
|
envvars
|
||||||
aliases
|
aliases
|
||||||
windows
|
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, \
|
from xonsh.tools import subproc_toks, subexpr_from_unbalanced, is_int, \
|
||||||
always_true, always_false, ensure_string, is_env_path, str_to_env_path, \
|
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, \
|
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 = Lexer()
|
||||||
LEXER.build()
|
LEXER.build()
|
||||||
|
@ -154,6 +154,10 @@ def test_is_int():
|
||||||
yield assert_true, is_int(42)
|
yield assert_true, is_int(42)
|
||||||
yield assert_false, 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():
|
def test_always_true():
|
||||||
yield assert_true, always_true(42)
|
yield assert_true, always_true(42)
|
||||||
yield assert_true, always_true('42')
|
yield assert_true, always_true('42')
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
"""Aliases for the xonsh shell."""
|
"""Aliases for the xonsh shell."""
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import shlex
|
import shlex
|
||||||
import builtins
|
import builtins
|
||||||
import subprocess
|
import subprocess
|
||||||
import datetime
|
|
||||||
from warnings import warn
|
from warnings import warn
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
|
|
||||||
|
@ -75,6 +73,7 @@ def xexec(args, stdin=None):
|
||||||
|
|
||||||
_BANG_N_PARSER = None
|
_BANG_N_PARSER = None
|
||||||
|
|
||||||
|
|
||||||
def bang_n(args, stdin=None):
|
def bang_n(args, stdin=None):
|
||||||
"""Re-runs the nth command as specified in the argument."""
|
"""Re-runs the nth command as specified in the argument."""
|
||||||
global _BANG_N_PARSER
|
global _BANG_N_PARSER
|
||||||
|
@ -101,37 +100,6 @@ def bang_bang(args, stdin=None):
|
||||||
return bang_n(['-1'])
|
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 = {
|
DEFAULT_ALIASES = {
|
||||||
'cd': cd,
|
'cd': cd,
|
||||||
'pushd': pushd,
|
'pushd': pushd,
|
||||||
|
@ -186,5 +154,3 @@ elif ON_MAC:
|
||||||
else:
|
else:
|
||||||
DEFAULT_ALIASES['grep'] = ['grep', '--color=auto']
|
DEFAULT_ALIASES['grep'] = ['grep', '--color=auto']
|
||||||
DEFAULT_ALIASES['ls'] = ['ls', '--color=auto', '-v']
|
DEFAULT_ALIASES['ls'] = ['ls', '--color=auto', '-v']
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -119,7 +119,7 @@ class BaseShell(object):
|
||||||
return
|
return
|
||||||
hist = builtins.__xonsh_history__
|
hist = builtins.__xonsh_history__
|
||||||
ts1 = None
|
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()
|
else io.StringIO()
|
||||||
try:
|
try:
|
||||||
ts0 = time.time()
|
ts0 = time.time()
|
||||||
|
@ -181,9 +181,8 @@ class BaseShell(object):
|
||||||
term = env.get('TERM', None)
|
term = env.get('TERM', None)
|
||||||
if term is None or term == 'linux':
|
if term is None or term == 'linux':
|
||||||
return
|
return
|
||||||
if 'TITLE' in env:
|
t = env.get('TITLE')
|
||||||
t = env['TITLE']
|
if t is None:
|
||||||
else:
|
|
||||||
return
|
return
|
||||||
t = format_prompt(t)
|
t = format_prompt(t)
|
||||||
if ON_WINDOWS and 'ANSICON' not in env:
|
if ON_WINDOWS and 'ANSICON' not in env:
|
||||||
|
@ -204,14 +203,11 @@ class BaseShell(object):
|
||||||
self.mlprompt = '<multiline prompt error> '
|
self.mlprompt = '<multiline prompt error> '
|
||||||
return self.mlprompt
|
return self.mlprompt
|
||||||
env = builtins.__xonsh_env__
|
env = builtins.__xonsh_env__
|
||||||
if 'PROMPT' in env:
|
p = env.get('PROMPT')
|
||||||
p = env['PROMPT']
|
try:
|
||||||
try:
|
p = format_prompt(p)
|
||||||
p = format_prompt(p)
|
except Exception:
|
||||||
except Exception:
|
print_exception()
|
||||||
print_exception()
|
|
||||||
else:
|
|
||||||
p = "set '$PROMPT = ...' $ "
|
|
||||||
self.settitle()
|
self.settitle()
|
||||||
return p
|
return p
|
||||||
|
|
||||||
|
|
|
@ -22,10 +22,11 @@ from xonsh.tools import suggest_commands, XonshError, ON_POSIX, ON_WINDOWS, \
|
||||||
string_types
|
string_types
|
||||||
from xonsh.inspectors import Inspector
|
from xonsh.inspectors import Inspector
|
||||||
from xonsh.environ import Env, default_env
|
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.jobs import add_job, wait_for_active_job
|
||||||
from xonsh.proc import ProcProxy, SimpleProcProxy, TeePTYProc
|
from xonsh.proc import ProcProxy, SimpleProcProxy, TeePTYProc
|
||||||
from xonsh.history import History
|
from xonsh.history import History
|
||||||
|
from xonsh.foreign_shells import load_foreign_aliases
|
||||||
|
|
||||||
ENV = None
|
ENV = None
|
||||||
BUILTINS_LOADED = False
|
BUILTINS_LOADED = False
|
||||||
|
@ -268,20 +269,17 @@ RE_SHEBANG = re.compile(r'#![ \t]*(.+?)$')
|
||||||
def _get_runnable_name(fname):
|
def _get_runnable_name(fname):
|
||||||
if os.path.isfile(fname) and fname != os.path.basename(fname):
|
if os.path.isfile(fname) and fname != os.path.basename(fname):
|
||||||
return fname
|
return fname
|
||||||
for d in builtins.__xonsh_env__['PATH']:
|
for d in builtins.__xonsh_env__.get('PATH'):
|
||||||
if os.path.isdir(d):
|
if os.path.isdir(d):
|
||||||
files = os.listdir(d)
|
files = os.listdir(d)
|
||||||
|
|
||||||
if ON_WINDOWS:
|
if ON_WINDOWS:
|
||||||
PATHEXT = builtins.__xonsh_env__.get('PATHEXT', [])
|
PATHEXT = builtins.__xonsh_env__.get('PATHEXT')
|
||||||
for dirfile in files:
|
for dirfile in files:
|
||||||
froot, ext = os.path.splitext(dirfile)
|
froot, ext = os.path.splitext(dirfile)
|
||||||
if fname == froot and ext.upper() in PATHEXT:
|
if fname == froot and ext.upper() in PATHEXT:
|
||||||
return os.path.join(d, dirfile)
|
return os.path.join(d, dirfile)
|
||||||
|
|
||||||
if fname in files:
|
if fname in files:
|
||||||
return os.path.join(d, fname)
|
return os.path.join(d, fname)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@ -666,7 +664,7 @@ def load_builtins(execer=None):
|
||||||
builtins.execx = None if execer is None else execer.exec
|
builtins.execx = None if execer is None else execer.exec
|
||||||
builtins.compilex = None if execer is None else execer.compile
|
builtins.compilex = None if execer is None else execer.compile
|
||||||
builtins.default_aliases = builtins.aliases = Aliases(DEFAULT_ALIASES)
|
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
|
# history needs to be started after env and aliases
|
||||||
# would be nice to actually include non-detyped versions.
|
# would be nice to actually include non-detyped versions.
|
||||||
builtins.__xonsh_history__ = History(env=ENV.detype(), #aliases=builtins.aliases,
|
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
|
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):
|
def startswithlow(x, start, startlow=None):
|
||||||
"""True if x starts with a string or its lowercase version. The lowercase
|
"""True if x starts with a string or its lowercase version. The lowercase
|
||||||
version may be optionally be provided.
|
version may be optionally be provided.
|
||||||
|
@ -73,7 +69,7 @@ def _normpath(p):
|
||||||
if trailing_slash:
|
if trailing_slash:
|
||||||
p = os.path.join(p, '')
|
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)
|
p = p.replace(os.sep, os.altsep)
|
||||||
|
|
||||||
return p
|
return p
|
||||||
|
@ -125,7 +121,7 @@ class Completer(object):
|
||||||
ctx = ctx or {}
|
ctx = ctx or {}
|
||||||
prefixlow = prefix.lower()
|
prefixlow = prefix.lower()
|
||||||
cmd = line.split(' ', 1)[0]
|
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
|
startswither = startswithnorm if csc else startswithlow
|
||||||
if begidx == 0:
|
if begidx == 0:
|
||||||
# the first thing we're typing; could be python or subprocess, so
|
# 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):
|
def _add_env(self, paths, prefix):
|
||||||
if prefix.startswith('$'):
|
if prefix.startswith('$'):
|
||||||
csc = get_env('CASE_SENSITIVE_COMPLETIONS', True)
|
csc = builtins.__xonsh_env__.get('CASE_SENSITIVE_COMPLETIONS')
|
||||||
startswither = startswithnorm if csc else startswithlow
|
startswither = startswithnorm if csc else startswithlow
|
||||||
key = prefix[1:]
|
key = prefix[1:]
|
||||||
keylow = key.lower()
|
keylow = key.lower()
|
||||||
|
@ -186,8 +182,9 @@ class Completer(object):
|
||||||
|
|
||||||
def _add_cdpaths(self, paths, prefix):
|
def _add_cdpaths(self, paths, prefix):
|
||||||
"""Completes current prefix using CDPATH"""
|
"""Completes current prefix using CDPATH"""
|
||||||
csc = get_env('CASE_SENSITIVE_COMPLETIONS', True)
|
env = builtins.__xonsh_env__
|
||||||
for cdp in get_env("CDPATH", []):
|
csc = env.get('CASE_SENSITIVE_COMPLETIONS')
|
||||||
|
for cdp in env.get('CDPATH'):
|
||||||
test_glob = os.path.join(cdp, prefix) + '*'
|
test_glob = os.path.join(cdp, prefix) + '*'
|
||||||
for s in iglobpath(test_glob, ignore_case=(not csc)):
|
for s in iglobpath(test_glob, ignore_case=(not csc)):
|
||||||
if os.path.isdir(s):
|
if os.path.isdir(s):
|
||||||
|
@ -197,7 +194,7 @@ class Completer(object):
|
||||||
"""Completes a command name based on what is on the $PATH"""
|
"""Completes a command name based on what is on the $PATH"""
|
||||||
space = ' '
|
space = ' '
|
||||||
cmdlow = cmd.lower()
|
cmdlow = cmd.lower()
|
||||||
csc = get_env('CASE_SENSITIVE_COMPLETIONS', True)
|
csc = builtins.__xonsh_env__.get('CASE_SENSITIVE_COMPLETIONS')
|
||||||
startswither = startswithnorm if csc else startswithlow
|
startswither = startswithnorm if csc else startswithlow
|
||||||
return {s + space
|
return {s + space
|
||||||
for s in self._all_commands()
|
for s in self._all_commands()
|
||||||
|
@ -207,7 +204,7 @@ class Completer(object):
|
||||||
"""Completes a name of a module to import."""
|
"""Completes a name of a module to import."""
|
||||||
prefixlow = prefix.lower()
|
prefixlow = prefix.lower()
|
||||||
modules = set(sys.modules.keys())
|
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
|
startswither = startswithnorm if csc else startswithlow
|
||||||
return {s for s in modules if startswither(s, prefix, prefixlow)}
|
return {s for s in modules if startswither(s, prefix, prefixlow)}
|
||||||
|
|
||||||
|
@ -217,7 +214,7 @@ class Completer(object):
|
||||||
slash = '/'
|
slash = '/'
|
||||||
tilde = '~'
|
tilde = '~'
|
||||||
paths = set()
|
paths = set()
|
||||||
csc = get_env('CASE_SENSITIVE_COMPLETIONS', True)
|
csc = builtins.__xonsh_env__.get('CASE_SENSITIVE_COMPLETIONS')
|
||||||
if prefix.startswith("'") or prefix.startswith('"'):
|
if prefix.startswith("'") or prefix.startswith('"'):
|
||||||
prefix = prefix[1:]
|
prefix = prefix[1:]
|
||||||
for s in iglobpath(prefix + '*', ignore_case=(not csc)):
|
for s in iglobpath(prefix + '*', ignore_case=(not csc)):
|
||||||
|
@ -279,9 +276,10 @@ class Completer(object):
|
||||||
|
|
||||||
def _source_completions(self):
|
def _source_completions(self):
|
||||||
srcs = []
|
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 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('\\', '/')
|
f = RE_WIN_DRIVE.sub(lambda m: '/{0}/'.format(m.group(1).lower()), f).replace('\\', '/')
|
||||||
srcs.append('source ' + f)
|
srcs.append('source ' + f)
|
||||||
return srcs
|
return srcs
|
||||||
|
@ -346,7 +344,7 @@ class Completer(object):
|
||||||
if len(attr) == 0:
|
if len(attr) == 0:
|
||||||
opts = [o for o in opts if not o.startswith('_')]
|
opts = [o for o in opts if not o.startswith('_')]
|
||||||
else:
|
else:
|
||||||
csc = get_env('CASE_SENSITIVE_COMPLETIONS', True)
|
csc = builtins.__xonsh_env__.get('CASE_SENSITIVE_COMPLETIONS')
|
||||||
startswither = startswithnorm if csc else startswithlow
|
startswither = startswithnorm if csc else startswithlow
|
||||||
attrlow = attr.lower()
|
attrlow = attr.lower()
|
||||||
opts = [o for o in opts if startswither(o, attrlow)]
|
opts = [o for o in opts if startswither(o, attrlow)]
|
||||||
|
@ -407,7 +405,7 @@ class ManCompleter(object):
|
||||||
|
|
||||||
def option_complete(self, prefix, cmd):
|
def option_complete(self, prefix, cmd):
|
||||||
"""Completes an option name, basing on content of man page."""
|
"""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
|
startswither = startswithnorm if csc else startswithlow
|
||||||
if cmd not in self._options.keys():
|
if cmd not in self._options.keys():
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -6,10 +6,7 @@ from glob import iglob
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
|
|
||||||
DIRSTACK = []
|
DIRSTACK = []
|
||||||
"""
|
"""A list containing the currently remembered directories."""
|
||||||
A list containing the currently remembered directories.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def _get_cwd():
|
def _get_cwd():
|
||||||
try:
|
try:
|
||||||
|
@ -42,7 +39,7 @@ def _try_cdpath(apath):
|
||||||
# in bash a full $ cd ./xonsh is needed.
|
# in bash a full $ cd ./xonsh is needed.
|
||||||
# In xonsh a relative folder is allways preferred.
|
# In xonsh a relative folder is allways preferred.
|
||||||
env = builtins.__xonsh_env__
|
env = builtins.__xonsh_env__
|
||||||
cdpaths = env.get('CDPATH', [])
|
cdpaths = env.get('CDPATH')
|
||||||
for cdp in cdpaths:
|
for cdp in cdpaths:
|
||||||
for cdpath_prefixed_path in iglob(os.path.join(cdp, apath)):
|
for cdpath_prefixed_path in iglob(os.path.join(cdp, apath)):
|
||||||
return cdpath_prefixed_path
|
return cdpath_prefixed_path
|
||||||
|
@ -92,15 +89,14 @@ def cd(args, stdin=None):
|
||||||
if not os.path.isdir(d):
|
if not os.path.isdir(d):
|
||||||
return '', 'cd: {0} is not a directory\n'.format(d)
|
return '', 'cd: {0} is not a directory\n'.format(d)
|
||||||
# now, push the directory onto the dirstack if AUTO_PUSHD is set
|
# 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])
|
pushd(['-n', '-q', cwd])
|
||||||
_change_working_directory(os.path.abspath(d))
|
_change_working_directory(os.path.abspath(d))
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
def pushd(args, stdin=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,
|
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.
|
making the new top of the stack the current working directory.
|
||||||
|
@ -165,11 +161,11 @@ def pushd(args, stdin=None):
|
||||||
else:
|
else:
|
||||||
DIRSTACK.insert(0, os.path.expanduser(os.path.abspath(new_pwd)))
|
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:
|
if len(DIRSTACK) > maxsize:
|
||||||
DIRSTACK = 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 dirs([], None)
|
||||||
|
|
||||||
return None, None
|
return None, None
|
||||||
|
@ -190,7 +186,7 @@ def popd(args, stdin=None):
|
||||||
|
|
||||||
env = builtins.__xonsh_env__
|
env = builtins.__xonsh_env__
|
||||||
|
|
||||||
if env.get('PUSHD_MINUS', False):
|
if env.get('PUSHD_MINUS'):
|
||||||
BACKWARD = '-'
|
BACKWARD = '-'
|
||||||
FORWARD = '+'
|
FORWARD = '+'
|
||||||
else:
|
else:
|
||||||
|
@ -238,15 +234,14 @@ def popd(args, stdin=None):
|
||||||
if args.cd:
|
if args.cd:
|
||||||
_change_working_directory(os.path.abspath(new_pwd))
|
_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 dirs([], None)
|
||||||
|
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
def dirs(args, stdin=None):
|
def dirs(args, stdin=None):
|
||||||
"""
|
"""xonsh command: dirs
|
||||||
xonsh command: dirs
|
|
||||||
|
|
||||||
Displays the list of currently remembered directories. Can also be used
|
Displays the list of currently remembered directories. Can also be used
|
||||||
to clear the directory stack.
|
to clear the directory stack.
|
||||||
|
@ -261,7 +256,7 @@ def dirs(args, stdin=None):
|
||||||
|
|
||||||
env = builtins.__xonsh_env__
|
env = builtins.__xonsh_env__
|
||||||
|
|
||||||
if env.get('PUSHD_MINUS', False):
|
if env.get('PUSHD_MINUS'):
|
||||||
BACKWARD = '-'
|
BACKWARD = '-'
|
||||||
FORWARD = '+'
|
FORWARD = '+'
|
||||||
else:
|
else:
|
||||||
|
|
243
xonsh/environ.py
243
xonsh/environ.py
|
@ -1,6 +1,7 @@
|
||||||
"""Environment for the xonsh shell."""
|
"""Environment for the xonsh shell."""
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import json
|
||||||
import socket
|
import socket
|
||||||
import string
|
import string
|
||||||
import locale
|
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, \
|
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, \
|
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, \
|
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.dirstack import _get_cwd
|
||||||
|
from xonsh.foreign_shells import DEFAULT_SHELLS, load_foreign_envs
|
||||||
|
|
||||||
LOCALE_CATS = {
|
LOCALE_CATS = {
|
||||||
'LC_CTYPE': locale.LC_CTYPE,
|
'LC_CTYPE': locale.LC_CTYPE,
|
||||||
|
@ -59,8 +61,103 @@ DEFAULT_ENSURERS = {
|
||||||
'XONSH_STORE_STDOUT': (is_bool, to_bool, bool_to_str),
|
'XONSH_STORE_STDOUT': (is_bool, to_bool, bool_to_str),
|
||||||
'CASE_SENSITIVE_COMPLETIONS': (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),
|
'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):
|
class Env(MutableMapping):
|
||||||
"""A xonsh environment, whose variables have limited typing
|
"""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."""
|
"""If no initial environment is given, os.environ is used."""
|
||||||
self._d = {}
|
self._d = {}
|
||||||
self.ensurers = {k: Ensurer(*v) for k, v in DEFAULT_ENSURERS.items()}
|
self.ensurers = {k: Ensurer(*v) for k, v in DEFAULT_ENSURERS.items()}
|
||||||
|
self.defaults = DEFAULT_VALUES
|
||||||
if len(args) == 0 and len(kwargs) == 0:
|
if len(args) == 0 and len(kwargs) == 0:
|
||||||
args = (os.environ, )
|
args = (os.environ, )
|
||||||
for key, val in dict(*args, **kwargs).items():
|
for key, val in dict(*args, **kwargs).items():
|
||||||
|
@ -168,6 +266,20 @@ class Env(MutableMapping):
|
||||||
del self._d[key]
|
del self._d[key]
|
||||||
self._detyped = None
|
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):
|
def __iter__(self):
|
||||||
yield from self._d
|
yield from self._d
|
||||||
|
|
||||||
|
@ -382,12 +494,6 @@ def branch_color():
|
||||||
TERM_COLORS['BOLD_GREEN'])
|
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):
|
def _replace_home(x):
|
||||||
if ON_WINDOWS:
|
if ON_WINDOWS:
|
||||||
home = (builtins.__xonsh_env__['HOMEDRIVE'] +
|
home = (builtins.__xonsh_env__['HOMEDRIVE'] +
|
||||||
|
@ -419,10 +525,10 @@ FORMATTER_DICT = dict(
|
||||||
curr_branch=current_branch,
|
curr_branch=current_branch,
|
||||||
branch_color=branch_color,
|
branch_color=branch_color,
|
||||||
**TERM_COLORS)
|
**TERM_COLORS)
|
||||||
|
DEFAULT_VALUES['FORMATTER_DICT'] = dict(FORMATTER_DICT)
|
||||||
|
|
||||||
_FORMATTER = string.Formatter()
|
_FORMATTER = string.Formatter()
|
||||||
|
|
||||||
|
|
||||||
def format_prompt(template=DEFAULT_PROMPT, formatter_dict=None):
|
def format_prompt(template=DEFAULT_PROMPT, formatter_dict=None):
|
||||||
"""Formats a xonsh prompt template string."""
|
"""Formats a xonsh prompt template string."""
|
||||||
template = template() if callable(template) else template
|
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')
|
RE_HIDDEN = re.compile('\001.*?\002')
|
||||||
|
|
||||||
|
|
||||||
def multiline_prompt():
|
def multiline_prompt():
|
||||||
"""Returns the filler text for the prompt in multiline scenarios."""
|
"""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)
|
curr = format_prompt(curr)
|
||||||
line = curr.rsplit('\n', 1)[1] if '\n' in curr else curr
|
line = curr.rsplit('\n', 1)[1] if '\n' in curr else curr
|
||||||
line = RE_HIDDEN.sub('', line) # gets rid of colors
|
line = RE_HIDDEN.sub('', line) # gets rid of colors
|
||||||
|
@ -460,7 +565,7 @@ def multiline_prompt():
|
||||||
# tail is the trailing whitespace
|
# tail is the trailing whitespace
|
||||||
tail = line if headlen == 0 else line.rsplit(head[-1], 1)[1]
|
tail = line if headlen == 0 else line.rsplit(head[-1], 1)[1]
|
||||||
# now to constuct the actual string
|
# 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
|
dots = dots() if callable(dots) else dots
|
||||||
if dots is None or len(dots) == 0:
|
if dots is None or len(dots) == 0:
|
||||||
return ''
|
return ''
|
||||||
|
@ -469,58 +574,37 @@ def multiline_prompt():
|
||||||
|
|
||||||
BASE_ENV = {
|
BASE_ENV = {
|
||||||
'XONSH_VERSION': XONSH_VERSION,
|
'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_CTYPE': locale.setlocale(locale.LC_CTYPE),
|
||||||
'LC_COLLATE': locale.setlocale(locale.LC_COLLATE),
|
'LC_COLLATE': locale.setlocale(locale.LC_COLLATE),
|
||||||
'LC_TIME': locale.setlocale(locale.LC_TIME),
|
'LC_TIME': locale.setlocale(locale.LC_TIME),
|
||||||
'LC_MONETARY': locale.setlocale(locale.LC_MONETARY),
|
'LC_MONETARY': locale.setlocale(locale.LC_MONETARY),
|
||||||
'LC_NUMERIC': locale.setlocale(locale.LC_NUMERIC),
|
'LC_NUMERIC': locale.setlocale(locale.LC_NUMERIC),
|
||||||
'SHELL_TYPE': 'readline',
|
|
||||||
'CASE_SENSITIVE_COMPLETIONS': ON_LINUX,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
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:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def load_static_config(ctx):
|
||||||
if ON_MAC:
|
"""Loads a static configuration file from a given context, rather than the
|
||||||
BASE_ENV['BASH_COMPLETIONS'] = [
|
current environment.
|
||||||
'/usr/local/etc/bash_completion',
|
"""
|
||||||
'/opt/local/etc/profile.d/bash_completion.sh'
|
env = {}
|
||||||
]
|
env['XDG_CONFIG_HOME'] = ctx.get('XDG_CONFIG_HOME',
|
||||||
else:
|
DEFAULT_VALUES['XDG_CONFIG_HOME'])
|
||||||
BASE_ENV['BASH_COMPLETIONS'] = [
|
env['XONSH_CONFIG_DIR'] = ctx['XONSH_CONFIG_DIR'] if 'XONSH_CONFIG_DIR' in ctx \
|
||||||
'/etc/bash_completion', '/usr/share/bash-completion/completions/git'
|
else xonsh_config_dir(env)
|
||||||
]
|
env['XONSHCONFIG'] = ctx['XONSHCONFIG'] if 'XONSHCONFIG' in ctx \
|
||||||
|
else xonshconfig(env)
|
||||||
|
config = env['XONSHCONFIG']
|
||||||
def bash_env():
|
if os.path.isfile(config):
|
||||||
"""Attempts to compute the bash envinronment variables."""
|
with open(config, 'r') as f:
|
||||||
currenv = None
|
conf = json.load(f)
|
||||||
if hasattr(builtins, '__xonsh_env__'):
|
else:
|
||||||
currenv = builtins.__xonsh_env__.detype()
|
conf = {}
|
||||||
try:
|
return conf
|
||||||
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 xonshrc_context(rcfile=None, execer=None):
|
def xonshrc_context(rcfile=None, execer=None):
|
||||||
|
@ -544,19 +628,23 @@ def xonshrc_context(rcfile=None, execer=None):
|
||||||
return env
|
return env
|
||||||
|
|
||||||
|
|
||||||
def recursive_base_env_update(env):
|
def windows_env_fixes(ctx):
|
||||||
"""Updates the environment with members that may rely on previously defined
|
"""Environment fixes for Windows. Operates in-place."""
|
||||||
members. Takes an env as its argument.
|
# Windows default prompt doesn't work.
|
||||||
"""
|
ctx['PROMPT'] = DEFAULT_PROMPT
|
||||||
home = os.path.expanduser('~')
|
# remove these bash variables which only cause problems.
|
||||||
if 'XONSH_DATA_DIR' not in env:
|
for ev in ['HOME', 'OLDPWD']:
|
||||||
xdgdh = env.get('XDG_DATA_HOME', os.path.join(home, '.local', 'share'))
|
if ev in ctx:
|
||||||
env['XONSH_DATA_DIR'] = xdd = os.path.join(xdgdh, 'xonsh')
|
del ctx[ev]
|
||||||
os.makedirs(xdd, exist_ok=True)
|
# Override path-related bash variables; on Windows bash uses
|
||||||
if 'XONSH_CONFIG_DIR' not in env:
|
# /c/Windows/System32 syntax instead of C:\\Windows\\System32
|
||||||
xdgch = env.get('XDG_CONFIG_HOME', os.path.join(home, '.config'))
|
# which messes up these environment variables for xonsh.
|
||||||
env['XONSH_CONFIG_DIR'] = xcd = os.path.join(xdgch, 'xonsh')
|
for ev in ['PATH', 'TEMP', 'TMP']:
|
||||||
os.makedirs(xcd, exist_ok=True)
|
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):
|
def default_env(env=None):
|
||||||
|
@ -564,28 +652,13 @@ def default_env(env=None):
|
||||||
# in order of increasing precedence
|
# in order of increasing precedence
|
||||||
ctx = dict(BASE_ENV)
|
ctx = dict(BASE_ENV)
|
||||||
ctx.update(os.environ)
|
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:
|
if ON_WINDOWS:
|
||||||
# Windows default prompt doesn't work.
|
windows_env_fixes(ctx)
|
||||||
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()
|
|
||||||
# finalize env
|
# finalize env
|
||||||
recursive_base_env_update(ctx)
|
|
||||||
if env is not None:
|
if env is not None:
|
||||||
ctx.update(env)
|
ctx.update(env)
|
||||||
return ctx
|
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)
|
time.sleep(0.01)
|
||||||
env = builtins.__xonsh_env__
|
env = builtins.__xonsh_env__
|
||||||
if self.size is None:
|
if self.size is None:
|
||||||
hsize, units = env.get('XONSH_HISTORY_SIZE', (8128, 'commands'))
|
hsize, units = env.get('XONSH_HISTORY_SIZE')
|
||||||
else:
|
else:
|
||||||
hsize, units = to_history_tuple(self.size)
|
hsize, units = to_history_tuple(self.size)
|
||||||
files = self.unlocked_files()
|
files = self.unlocked_files()
|
||||||
|
@ -79,7 +79,7 @@ class HistoryGC(Thread):
|
||||||
"""Finds the history files and returns the ones that are unlocked, this is
|
"""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.
|
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'))]
|
fs = [f for f in iglob(os.path.join(xdd, 'xonsh-*.json'))]
|
||||||
files = []
|
files = []
|
||||||
for f in fs:
|
for f in fs:
|
||||||
|
@ -209,7 +209,7 @@ class History(object):
|
||||||
"""
|
"""
|
||||||
self.sessionid = sid = uuid.uuid4() if sessionid is None else sessionid
|
self.sessionid = sid = uuid.uuid4() if sessionid is None else sessionid
|
||||||
if filename is None:
|
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))
|
'xonsh-{0}.json'.format(sid))
|
||||||
else:
|
else:
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
|
|
|
@ -10,6 +10,7 @@ import io
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
import builtins
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from collections import Sequence
|
from collections import Sequence
|
||||||
from subprocess import Popen, PIPE, DEVNULL, STDOUT, TimeoutExpired
|
from subprocess import Popen, PIPE, DEVNULL, STDOUT, TimeoutExpired
|
||||||
|
@ -370,7 +371,9 @@ class TeePTYProc(object):
|
||||||
self._tpty = tpty = TeePTY()
|
self._tpty = tpty = TeePTY()
|
||||||
if preexec_fn is not None:
|
if preexec_fn is not None:
|
||||||
preexec_fn()
|
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
|
@property
|
||||||
def pid(self):
|
def pid(self):
|
||||||
|
|
|
@ -18,8 +18,7 @@ from xonsh.prompt_toolkit_key_bindings import load_xonsh_bindings
|
||||||
def setup_history():
|
def setup_history():
|
||||||
"""Creates history object."""
|
"""Creates history object."""
|
||||||
env = builtins.__xonsh_env__
|
env = builtins.__xonsh_env__
|
||||||
hfile = env.get('XONSH_HISTORY_FILE',
|
hfile = env.get('XONSH_HISTORY_FILE')
|
||||||
os.path.expanduser('~/.xonsh_history'))
|
|
||||||
history = LimitedFileHistory()
|
history = LimitedFileHistory()
|
||||||
try:
|
try:
|
||||||
history.read_history_file(hfile)
|
history.read_history_file(hfile)
|
||||||
|
@ -31,9 +30,8 @@ def setup_history():
|
||||||
def teardown_history(history):
|
def teardown_history(history):
|
||||||
"""Tears down the history object."""
|
"""Tears down the history object."""
|
||||||
env = builtins.__xonsh_env__
|
env = builtins.__xonsh_env__
|
||||||
hsize = env.get('XONSH_HISTORY_SIZE', (8128, 'commands'))[0]
|
hsize = env.get('XONSH_HISTORY_SIZE')[0]
|
||||||
hfile = env.get('XONSH_HISTORY_FILE',
|
hfile = env.get('XONSH_HISTORY_FILE')
|
||||||
os.path.expanduser('~/.xonsh_history'))
|
|
||||||
try:
|
try:
|
||||||
history.save_history_to_file(hfile, hsize)
|
history.save_history_to_file(hfile, hsize)
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
|
@ -97,7 +95,8 @@ class PromptToolkitShell(BaseShell):
|
||||||
# update with the prompt styles
|
# update with the prompt styles
|
||||||
styles.update({t: s for (t, s) in zip(tokens, cstyles)})
|
styles.update({t: s for (t, s) in zip(tokens, cstyles)})
|
||||||
# Update with with any user styles
|
# Update with with any user styles
|
||||||
userstyle = builtins.__xonsh_env__.get('PROMPT_TOOLKIT_STYLES', {})
|
userstyle = builtins.__xonsh_env__.get('PROMPT_TOOLKIT_STYLES')
|
||||||
styles.update(userstyle)
|
if userstyle is not None:
|
||||||
|
styles.update(userstyle)
|
||||||
|
|
||||||
return get_tokens, CustomStyle
|
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[B": history-search-forward')
|
||||||
readline.parse_and_bind('"\e[A": history-search-backward')
|
readline.parse_and_bind('"\e[A": history-search-backward')
|
||||||
# Setup Shift-Tab to indent
|
# 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
|
# handle tab completion differences found in libedit readline compatibility
|
||||||
# as discussed at http://stackoverflow.com/a/7116997
|
# as discussed at http://stackoverflow.com/a/7116997
|
||||||
|
@ -139,12 +139,12 @@ class ReadlineShell(BaseShell, Cmd):
|
||||||
self._current_indent = ''
|
self._current_indent = ''
|
||||||
elif line.rstrip()[-1] == ':':
|
elif line.rstrip()[-1] == ':':
|
||||||
ind = line[:len(line) - len(line.lstrip())]
|
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))
|
readline.set_pre_input_hook(_insert_text_func(ind, readline))
|
||||||
self._current_indent = ind
|
self._current_indent = ind
|
||||||
elif line.split(maxsplit=1)[0] in DEDENT_TOKENS:
|
elif line.split(maxsplit=1)[0] in DEDENT_TOKENS:
|
||||||
env = builtins.__xonsh_env__
|
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))
|
readline.set_pre_input_hook(_insert_text_func(ind, readline))
|
||||||
self._current_indent = ind
|
self._current_indent = ind
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -26,25 +26,26 @@ class Shell(object):
|
||||||
def __init__(self, ctx=None, shell_type=None, **kwargs):
|
def __init__(self, ctx=None, shell_type=None, **kwargs):
|
||||||
self._init_environ(ctx)
|
self._init_environ(ctx)
|
||||||
env = builtins.__xonsh_env__
|
env = builtins.__xonsh_env__
|
||||||
|
# pick a valid shell
|
||||||
if shell_type is not None:
|
if shell_type is not None:
|
||||||
env['SHELL_TYPE'] = shell_type
|
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():
|
if not is_prompt_toolkit_available():
|
||||||
warn('prompt_toolkit is not available, using readline instead.')
|
warn('prompt_toolkit is not available, using readline instead.')
|
||||||
env['SHELL_TYPE'] = 'readline'
|
shell_type = env['SHELL_TYPE'] = 'readline'
|
||||||
|
# actually make the shell
|
||||||
if env['SHELL_TYPE'] == 'prompt_toolkit':
|
if shell_type == 'prompt_toolkit':
|
||||||
from xonsh.prompt_toolkit_shell import PromptToolkitShell
|
from xonsh.prompt_toolkit_shell import PromptToolkitShell
|
||||||
self.shell = PromptToolkitShell(execer=self.execer,
|
self.shell = PromptToolkitShell(execer=self.execer,
|
||||||
ctx=self.ctx, **kwargs)
|
ctx=self.ctx, **kwargs)
|
||||||
elif env['SHELL_TYPE'] == 'readline':
|
elif shell_type == 'readline':
|
||||||
from xonsh.readline_shell import ReadlineShell
|
from xonsh.readline_shell import ReadlineShell
|
||||||
self.shell = ReadlineShell(execer=self.execer,
|
self.shell = ReadlineShell(execer=self.execer,
|
||||||
ctx=self.ctx, **kwargs)
|
ctx=self.ctx, **kwargs)
|
||||||
else:
|
else:
|
||||||
raise XonshError('{} is not recognized as a shell type'.format(
|
raise XonshError('{} is not recognized as a shell type'.format(
|
||||||
env['SHELL_TYPE']))
|
shell_type))
|
||||||
# allows history garbace colector to start running
|
# allows history garbace colector to start running
|
||||||
builtins.__xonsh_history__.gc.wait_for_shell = False
|
builtins.__xonsh_history__.gc.wait_for_shell = False
|
||||||
|
|
||||||
|
@ -58,7 +59,7 @@ class Shell(object):
|
||||||
if ctx is not None:
|
if ctx is not None:
|
||||||
self.ctx = ctx
|
self.ctx = ctx
|
||||||
else:
|
else:
|
||||||
rc = env.get('XONSHRC', None)
|
rc = env.get('XONSHRC')
|
||||||
self.ctx = xonshrc_context(rcfile=rc, execer=self.execer)
|
self.ctx = xonshrc_context(rcfile=rc, execer=self.execer)
|
||||||
builtins.__xonsh_ctx__ = self.ctx
|
builtins.__xonsh_ctx__ = self.ctx
|
||||||
self.ctx['__name__'] = '__main__'
|
self.ctx['__name__'] = '__main__'
|
||||||
|
|
|
@ -11,6 +11,7 @@ import os
|
||||||
import sys
|
import sys
|
||||||
import tty
|
import tty
|
||||||
import pty
|
import pty
|
||||||
|
import time
|
||||||
import array
|
import array
|
||||||
import fcntl
|
import fcntl
|
||||||
import select
|
import select
|
||||||
|
@ -78,7 +79,7 @@ class TeePTY(object):
|
||||||
self._temp_stdin.close()
|
self._temp_stdin.close()
|
||||||
self._temp_stdin = None
|
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().
|
"""Create a spawned process. Based on the code for pty.spawn().
|
||||||
This cannot be used except from the main thread.
|
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.
|
Arguments to pass in as subprocess. In None, will execute $SHELL.
|
||||||
env : Mapping, optional
|
env : Mapping, optional
|
||||||
Environment to pass execute in.
|
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
|
Returns
|
||||||
-------
|
-------
|
||||||
|
@ -104,6 +111,10 @@ class TeePTY(object):
|
||||||
self.pid = pid
|
self.pid = pid
|
||||||
self.master_fd = master_fd
|
self.master_fd = master_fd
|
||||||
if pid == pty.CHILD:
|
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:
|
if env is None:
|
||||||
os.execvp(argv[0], argv)
|
os.execvp(argv[0], argv)
|
||||||
else:
|
else:
|
||||||
|
@ -255,8 +266,35 @@ class TeePTY(object):
|
||||||
tsi.flush()
|
tsi.flush()
|
||||||
else:
|
else:
|
||||||
raise ValueError('stdin not understood {0!r}'.format(stdin))
|
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__':
|
if __name__ == '__main__':
|
||||||
tpty = TeePTY()
|
tpty = TeePTY()
|
||||||
|
@ -266,4 +304,4 @@ if __name__ == '__main__':
|
||||||
print('-=-'*10)
|
print('-=-'*10)
|
||||||
print(tpty)
|
print(tpty)
|
||||||
print('-=-'*10)
|
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):
|
def suggest_commands(cmd, env, aliases):
|
||||||
"""Suggests alternative commands given an environment and 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:
|
if not suggest_cmds:
|
||||||
return
|
return
|
||||||
thresh = env.get('SUGGEST_THRESHOLD', 3)
|
thresh = env.get('SUGGEST_THRESHOLD')
|
||||||
max_sugg = env.get('SUGGEST_MAX_NUM', 5)
|
max_sugg = env.get('SUGGEST_MAX_NUM')
|
||||||
if max_sugg < 0:
|
if max_sugg < 0:
|
||||||
max_sugg = float('inf')
|
max_sugg = float('inf')
|
||||||
|
|
||||||
|
@ -357,7 +357,7 @@ def suggest_commands(cmd, env, aliases):
|
||||||
if levenshtein(a.lower(), cmd, thresh) < thresh:
|
if levenshtein(a.lower(), cmd, thresh) < thresh:
|
||||||
suggested[a] = 'Alias'
|
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):
|
for f in os.listdir(d):
|
||||||
if f not in suggested:
|
if f not in suggested:
|
||||||
if levenshtein(f.lower(), cmd, thresh) < thresh:
|
if levenshtein(f.lower(), cmd, thresh) < thresh:
|
||||||
|
@ -387,8 +387,8 @@ def print_exception():
|
||||||
"""Print exceptions with/without traceback."""
|
"""Print exceptions with/without traceback."""
|
||||||
if 'XONSH_SHOW_TRACEBACK' not in builtins.__xonsh_env__:
|
if 'XONSH_SHOW_TRACEBACK' not in builtins.__xonsh_env__:
|
||||||
sys.stderr.write('xonsh: For full traceback set: '
|
sys.stderr.write('xonsh: For full traceback set: '
|
||||||
'$XONSH_SHOW_TRACEBACK=True\n')
|
'$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()
|
traceback.print_exc()
|
||||||
else:
|
else:
|
||||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||||
|
@ -401,15 +401,12 @@ def print_exception():
|
||||||
def levenshtein(a, b, max_dist=float('inf')):
|
def levenshtein(a, b, max_dist=float('inf')):
|
||||||
"""Calculates the Levenshtein distance between a and b."""
|
"""Calculates the Levenshtein distance between a and b."""
|
||||||
n, m = len(a), len(b)
|
n, m = len(a), len(b)
|
||||||
|
|
||||||
if abs(n - m) > max_dist:
|
if abs(n - m) > max_dist:
|
||||||
return float('inf')
|
return float('inf')
|
||||||
|
|
||||||
if n > m:
|
if n > m:
|
||||||
# Make sure n <= m, to use O(min(n,m)) space
|
# Make sure n <= m, to use O(min(n,m)) space
|
||||||
a, b = b, a
|
a, b = b, a
|
||||||
n, m = m, n
|
n, m = m, n
|
||||||
|
|
||||||
current = range(n + 1)
|
current = range(n + 1)
|
||||||
for i in range(1, m + 1):
|
for i in range(1, m + 1):
|
||||||
previous, current = current, [i] + [0] * n
|
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]:
|
if a[j - 1] != b[i - 1]:
|
||||||
change = change + 1
|
change = change + 1
|
||||||
current[j] = min(add, delete, change)
|
current[j] = min(add, delete, change)
|
||||||
|
|
||||||
return current[n]
|
return current[n]
|
||||||
|
|
||||||
|
|
||||||
|
@ -441,7 +437,6 @@ def escape_windows_title_string(s):
|
||||||
"""
|
"""
|
||||||
for c in '^&<>|':
|
for c in '^&<>|':
|
||||||
s = s.replace(c, '^' + c)
|
s = s.replace(c, '^' + c)
|
||||||
|
|
||||||
s = s.replace('/?', '/.')
|
s = s.replace('/?', '/.')
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
@ -474,6 +469,11 @@ def is_int(x):
|
||||||
return isinstance(x, int)
|
return isinstance(x, int)
|
||||||
|
|
||||||
|
|
||||||
|
def is_float(x):
|
||||||
|
"""Tests if something is a float"""
|
||||||
|
return isinstance(x, float)
|
||||||
|
|
||||||
|
|
||||||
def always_true(x):
|
def always_true(x):
|
||||||
"""Returns True"""
|
"""Returns True"""
|
||||||
return True
|
return True
|
||||||
|
|
Loading…
Add table
Reference in a new issue