From 1387573c122d3f1f7b617c830b94c868444fe2fa Mon Sep 17 00:00:00 2001 From: David Date: Sat, 10 Oct 2015 17:08:27 +1100 Subject: [PATCH 01/21] aliases: make bash_aliases() more robust * Call bash with `-c alias` rather than passing `alias` through stdin. * Check each alias read in is really an alias, to make doubly-sure we don't get garbage in our aliases. Fixes: #204 --- xonsh/aliases.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/xonsh/aliases.py b/xonsh/aliases.py index b7c179938..6e2f4a568 100644 --- a/xonsh/aliases.py +++ b/xonsh/aliases.py @@ -104,13 +104,12 @@ def bang_bang(args, stdin=None): def bash_aliases(): """Computes a dictionary of aliases based on Bash's aliases.""" try: - s = subprocess.check_output(['bash', '-i', '-l'], - input='alias', + s = subprocess.check_output(['bash', '-i', '-l', '-c', 'alias'], stderr=subprocess.PIPE, universal_newlines=True) except (subprocess.CalledProcessError, FileNotFoundError): s = '' - items = [line.split('=', 1) for line in s.splitlines() if '=' in line] + items = [line.split('=', 1) for line in s.splitlines() if line.startswith('alias ') and '=' in line] aliases = {} for key, value in items: try: From a3393c6136487dbd77975f7582b3b19a138fb840 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 11 Oct 2015 00:49:37 +1100 Subject: [PATCH 02/21] aliases: bash_aliases: stop spawning login shells There should be no need to run a login shell to get aliases. Any self-respecting bash user wouldn't set aliases in their .profile. --- xonsh/aliases.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xonsh/aliases.py b/xonsh/aliases.py index 6e2f4a568..ca9b9e3c6 100644 --- a/xonsh/aliases.py +++ b/xonsh/aliases.py @@ -104,7 +104,7 @@ def bang_bang(args, stdin=None): def bash_aliases(): """Computes a dictionary of aliases based on Bash's aliases.""" try: - s = subprocess.check_output(['bash', '-i', '-l', '-c', 'alias'], + s = subprocess.check_output(['bash', '-i', '-c', 'alias'], stderr=subprocess.PIPE, universal_newlines=True) except (subprocess.CalledProcessError, FileNotFoundError): From 0328dea45d57d326f5206ca70834b973692798ce Mon Sep 17 00:00:00 2001 From: Anthony Scopatz Date: Sat, 10 Oct 2015 14:39:50 -0400 Subject: [PATCH 03/21] moved some defaults around --- xonsh/environ.py | 125 ++++++++++++++++++++++++++++------------------- 1 file changed, 74 insertions(+), 51 deletions(-) diff --git a/xonsh/environ.py b/xonsh/environ.py index b2607d8ad..db5e5ef20 100644 --- a/xonsh/environ.py +++ b/xonsh/environ.py @@ -61,6 +61,44 @@ DEFAULT_ENSURERS = { 'BASH_COMPLETIONS': (is_env_path, str_to_env_path, env_path_to_str), } +# +# Defaults +# +DEFAULT_PROMPT = ('{BOLD_GREEN}{user}@{hostname}{BOLD_BLUE} ' + '{cwd}{branch_color}{curr_branch} ' + '{BOLD_BLUE}${NO_COLOR} ') +DEFAULT_TITLE = '{user}@{hostname}: {cwd} | xonsh' +# 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. +DEFAULT_VALUES = { + 'INDENT': ' ', + '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, + 'SHELL_TYPE': 'readline', + 'CASE_SENSITIVE_COMPLETIONS': ON_LINUX, + '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), + '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'), + 'FORCE_POSIX_PATHS': False, +} + +class DefaultNotGivenType(object): + """Singleton for representing when no default value is given.""" + + +DefaultNotGiven = DefaultNotGivenType() + class Env(MutableMapping): """A xonsh environment, whose variables have limited typing @@ -73,7 +111,7 @@ class Env(MutableMapping): locale via locale.getlocale() and locale.setlocale() functions. An Env instance may be converted to an untyped version suitable for - use in a subprocess. + use in a subprocess. """ _arg_regex = re.compile(r'ARG(\d+)') @@ -82,6 +120,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 +207,18 @@ 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) + else: + val = default + return val + def __iter__(self): yield from self._d @@ -382,12 +433,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 +464,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,7 +492,6 @@ 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 = ...' $ ") @@ -469,41 +513,19 @@ 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 @@ -543,6 +565,24 @@ def xonshrc_context(rcfile=None, execer=None): execer.filename = fname return env +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 recursive_base_env_update(env): """Updates the environment with members that may rely on previously defined @@ -566,24 +606,7 @@ def default_env(env=None): ctx.update(os.environ) ctx.update(bash_env()) 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: From 6e33f7a80d2b031d21492a0a90ae15d2c78aa21f Mon Sep 17 00:00:00 2001 From: Anthony Scopatz Date: Sat, 10 Oct 2015 14:51:28 -0400 Subject: [PATCH 04/21] shell type fixes --- xonsh/shell.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/xonsh/shell.py b/xonsh/shell.py index c3ea6528b..d54c9cc9d 100644 --- a/xonsh/shell.py +++ b/xonsh/shell.py @@ -26,25 +26,26 @@ class Shell(object): def __init__(self, ctx=None, shell_type=None, **kwargs): self._init_environ(ctx) env = builtins.__xonsh_env__ - + # piclk 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 From eaf96afb3c6e6585b6837e677aaf02697944d090 Mon Sep 17 00:00:00 2001 From: Anthony Scopatz Date: Sat, 10 Oct 2015 15:10:34 -0400 Subject: [PATCH 05/21] more environ updates --- xonsh/environ.py | 51 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/xonsh/environ.py b/xonsh/environ.py index db5e5ef20..06a8d7f49 100644 --- a/xonsh/environ.py +++ b/xonsh/environ.py @@ -64,10 +64,36 @@ DEFAULT_ENSURERS = { # # 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(xdgch, 'xonsh') + os.makedirs(xcd, exist_ok=True) + return xcd + + # 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. DEFAULT_VALUES = { @@ -91,6 +117,10 @@ DEFAULT_VALUES = { else ('/etc/bash_completion', '/usr/share/bash-completion/completions/git'), 'FORCE_POSIX_PATHS': False, + 'XDG_DATA_HOME': os.path.expanduser(os.path.join('~', '.local', 'share')), + 'XONSH_DATA_DIR': xonsh_data_dir, + 'XDG_CONFIG_HOME': os.path.expanduser(os.path.join('~', '.config')), + 'XONSH_CONFIG_DIR': xonsh_config_dir, } class DefaultNotGivenType(object): @@ -99,6 +129,9 @@ class DefaultNotGivenType(object): DefaultNotGiven = DefaultNotGivenType() +# +# actual environment +# class Env(MutableMapping): """A xonsh environment, whose variables have limited typing @@ -215,6 +248,8 @@ class Env(MutableMapping): 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 @@ -584,21 +619,6 @@ def windows_env_fixes(ctx): ctx['PWD'] = _get_cwd() -def recursive_base_env_update(env): - """Updates the environment with members that may rely on previously defined - members. Takes an env as its argument. - """ - home = os.path.expanduser('~') - if 'XONSH_DATA_DIR' not in env: - xdgdh = env.get('XDG_DATA_HOME', os.path.join(home, '.local', 'share')) - env['XONSH_DATA_DIR'] = xdd = os.path.join(xdgdh, 'xonsh') - os.makedirs(xdd, exist_ok=True) - if 'XONSH_CONFIG_DIR' not in env: - xdgch = env.get('XDG_CONFIG_HOME', os.path.join(home, '.config')) - env['XONSH_CONFIG_DIR'] = xcd = os.path.join(xdgch, 'xonsh') - os.makedirs(xcd, exist_ok=True) - - def default_env(env=None): """Constructs a default xonsh environment.""" # in order of increasing precedence @@ -608,7 +628,6 @@ def default_env(env=None): if ON_WINDOWS: windows_env_fixes(ctx) # finalize env - recursive_base_env_update(ctx) if env is not None: ctx.update(env) return ctx From aa64655666172f0c9ab4fd598e679120709bfb92 Mon Sep 17 00:00:00 2001 From: Anthony Scopatz Date: Sat, 10 Oct 2015 15:20:51 -0400 Subject: [PATCH 06/21] more default changes --- xonsh/base_shell.py | 18 +++++++----------- xonsh/environ.py | 4 ++-- xonsh/history.py | 4 ++-- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/xonsh/base_shell.py b/xonsh/base_shell.py index 845b6e75f..718c41bcb 100644 --- a/xonsh/base_shell.py +++ b/xonsh/base_shell.py @@ -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 = ' ' 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 diff --git a/xonsh/environ.py b/xonsh/environ.py index 06a8d7f49..21b9b7d74 100644 --- a/xonsh/environ.py +++ b/xonsh/environ.py @@ -529,7 +529,7 @@ 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 @@ -539,7 +539,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 '' diff --git a/xonsh/history.py b/xonsh/history.py index 6c858d1eb..e1518b826 100644 --- a/xonsh/history.py +++ b/xonsh/history.py @@ -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 From 67bdf05b1465bb1e0a1f4d8f28d78442dcc56928 Mon Sep 17 00:00:00 2001 From: Anthony Scopatz Date: Sat, 10 Oct 2015 15:35:54 -0400 Subject: [PATCH 07/21] some doc rearrangements --- docs/envvars.rst | 139 ++++++++++++++++++++++++----------------------- 1 file changed, 71 insertions(+), 68 deletions(-) diff --git a/docs/envvars.rst b/docs/envvars.rst index 312d795bd..e7fa7d8ec 100644 --- a/docs/envvars.rst +++ b/docs/envvars.rst @@ -11,60 +11,37 @@ applicable. * - variable - default - description - * - PROMPT - - xonsh.environ.DEFAULT_PROMPT - - The prompt text. May contain keyword arguments which are auto-formatted, - see `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 `_. + * - 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. + * - FORCE_POSIX_PATHS + - Not defined + - 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 `_. - * - 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 - - ``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. + * - MULTILINE_PROMPT + - ``'.'`` + - Prompt text for 2nd+ lines of input, may be str or function which returns + a str. + * - PROMPT + - xonsh.environ.DEFAULT_PROMPT + - The prompt text. May contain keyword arguments which are auto-formatted, + see `Customizing the Prompt `_. * - SHELL_TYPE - ``'readline'`` - Which shell is used. Currently two shell types are supported: ``'readline'`` that @@ -74,27 +51,53 @@ applicable. `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. + * - 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. + * - 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 `_. + * - 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 - 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. - * - XONSH_CONFIG_DIR - - ``$XDG_CONFIG_HOME/xonsh`` - - This is location where xonsh configuration information is stored. - + * - XONSH_STORE_STDOUT + - ``False`` + - Whether or not to store the stdout and stderr streams in the history files. From faea423d3ba219cdfc1272b8d3a32f18b70a0aa9 Mon Sep 17 00:00:00 2001 From: Anthony Scopatz Date: Sat, 10 Oct 2015 15:53:38 -0400 Subject: [PATCH 08/21] some updates --- docs/envvars.rst | 22 ++++++++++++++++++---- xonsh/environ.py | 38 ++++++++++++++++++++++---------------- xonsh/tools.py | 17 ++++++----------- 3 files changed, 46 insertions(+), 31 deletions(-) diff --git a/docs/envvars.rst b/docs/envvars.rst index e7fa7d8ec..221d1272b 100644 --- a/docs/envvars.rst +++ b/docs/envvars.rst @@ -27,17 +27,23 @@ applicable. - A list of paths to be used as roots for a ``cd``, breaking compatibility with bash, xonsh always prefer an existing relative path. * - FORCE_POSIX_PATHS - - Not defined + - ``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 `_. + * - INDENT + - ``' '`` + - Indentation string for multiline input * - MULTILINE_PROMPT - ``'.'`` - Prompt text for 2nd+ lines of input, may be str or function which returns a str. + * - PATH + - ``()`` + - List of strings representing where to look for executables. * - PROMPT - xonsh.environ.DEFAULT_PROMPT - The prompt text. May contain keyword arguments which are auto-formatted, @@ -69,6 +75,14 @@ applicable. - The title text for the window in which xonsh is running. Formatted in the same manner as PROMPT, see `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. * - XONSHRC - ``'~/.xonshrc'`` - Location of run control file. @@ -93,9 +107,9 @@ applicable. - - ``True`` if xonsh is running interactively, and ``False`` otherwise. * - 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 + - ``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`` diff --git a/xonsh/environ.py b/xonsh/environ.py index 21b9b7d74..5ef01da7b 100644 --- a/xonsh/environ.py +++ b/xonsh/environ.py @@ -96,31 +96,37 @@ def xonsh_config_dir(env): # 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 = { - 'INDENT': ' ', - '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, - 'SHELL_TYPE': 'readline', + '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, + '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), - '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'), - 'FORCE_POSIX_PATHS': False, - 'XDG_DATA_HOME': os.path.expanduser(os.path.join('~', '.local', 'share')), - 'XONSH_DATA_DIR': xonsh_data_dir, + 'MULTILINE_PROMPT': '.', + 'PATH': (), + 'PROMPT': DEFAULT_PROMPT, + 'SHELL_TYPE': 'readline', + 'SUGGEST_COMMANDS': True, + 'SUGGEST_MAX_NUM': 5, + 'SUGGEST_THRESHOLD': 3, + 'TITLE': DEFAULT_TITLE, 'XDG_CONFIG_HOME': os.path.expanduser(os.path.join('~', '.config')), + 'XDG_DATA_HOME': os.path.expanduser(os.path.join('~', '.local', 'share')), + '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): diff --git a/xonsh/tools.py b/xonsh/tools.py index 8be3e5913..dd9caa5ec 100644 --- a/xonsh/tools.py +++ b/xonsh/tools.py @@ -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 From ad0b084f2b2fc9f44ef646c0951bcaa3fdf12e03 Mon Sep 17 00:00:00 2001 From: Anthony Scopatz Date: Sat, 10 Oct 2015 15:59:51 -0400 Subject: [PATCH 09/21] base shell updates --- docs/envvars.rst | 8 ++++++++ xonsh/base_shell.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/envvars.rst b/docs/envvars.rst index 221d1272b..624f3f72a 100644 --- a/docs/envvars.rst +++ b/docs/envvars.rst @@ -11,6 +11,9 @@ applicable. * - variable - default - description + * - ANSICON + - No default set + - This is used on Windows to set the title, if available. * - BASH_COMPLETIONS - Normally this is ``('/etc/bash_completion', '/usr/share/bash-completion/completions/git')`` @@ -70,6 +73,11 @@ applicable. - 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. + * - 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 diff --git a/xonsh/base_shell.py b/xonsh/base_shell.py index 718c41bcb..ef4c281aa 100644 --- a/xonsh/base_shell.py +++ b/xonsh/base_shell.py @@ -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() From f5f7dba02c31fed31faa4732309751cc120916cf Mon Sep 17 00:00:00 2001 From: Anthony Scopatz Date: Sat, 10 Oct 2015 16:45:02 -0400 Subject: [PATCH 10/21] many updates --- docs/envvars.rst | 22 ++++++++++++++++++++++ xonsh/built_ins.py | 7 ++----- xonsh/completer.py | 30 ++++++++++++++---------------- xonsh/dirstack.py | 25 ++++++++++--------------- xonsh/environ.py | 7 +++++++ xonsh/history.py | 2 +- xonsh/prompt_toolkit_shell.py | 13 ++++++------- xonsh/readline_shell.py | 6 +++--- 8 files changed, 65 insertions(+), 47 deletions(-) diff --git a/docs/envvars.rst b/docs/envvars.rst index 624f3f72a..9d20fb153 100644 --- a/docs/envvars.rst +++ b/docs/envvars.rst @@ -14,6 +14,9 @@ applicable. * - 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')`` @@ -29,6 +32,9 @@ applicable. - ``[]`` - 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 @@ -44,13 +50,29 @@ applicable. - ``'.'`` - 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 `_. + * - 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`` + - 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 diff --git a/xonsh/built_ins.py b/xonsh/built_ins.py index a134353cf..745594785 100644 --- a/xonsh/built_ins.py +++ b/xonsh/built_ins.py @@ -268,20 +268,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 diff --git a/xonsh/completer.py b/xonsh/completer.py index 6b07bbd87..62731d0c2 100644 --- a/xonsh/completer.py +++ b/xonsh/completer.py @@ -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: diff --git a/xonsh/dirstack.py b/xonsh/dirstack.py index f790021f7..6e65a37e7 100644 --- a/xonsh/dirstack.py +++ b/xonsh/dirstack.py @@ -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: diff --git a/xonsh/environ.py b/xonsh/environ.py index 5ef01da7b..aa299e006 100644 --- a/xonsh/environ.py +++ b/xonsh/environ.py @@ -98,11 +98,14 @@ def xonsh_config_dir(env): # 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), @@ -112,7 +115,11 @@ DEFAULT_VALUES = { '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, diff --git a/xonsh/history.py b/xonsh/history.py index e1518b826..00d44ebed 100644 --- a/xonsh/history.py +++ b/xonsh/history.py @@ -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() diff --git a/xonsh/prompt_toolkit_shell.py b/xonsh/prompt_toolkit_shell.py index 1f9432340..b3216cfe1 100644 --- a/xonsh/prompt_toolkit_shell.py +++ b/xonsh/prompt_toolkit_shell.py @@ -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 diff --git a/xonsh/readline_shell.py b/xonsh/readline_shell.py index d224d5696..975570727 100644 --- a/xonsh/readline_shell.py +++ b/xonsh/readline_shell.py @@ -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: From 62624e43a36860779faa5e31c237424660fd4e3e Mon Sep 17 00:00:00 2001 From: Anthony Scopatz Date: Sat, 10 Oct 2015 19:04:43 -0400 Subject: [PATCH 11/21] initial foreign shell trial --- xonsh/foreign_shells.py | 116 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 xonsh/foreign_shells.py diff --git a/xonsh/foreign_shells.py b/xonsh/foreign_shells.py new file mode 100644 index 000000000..5ea41d94a --- /dev/null +++ b/xonsh/foreign_shells.py @@ -0,0 +1,116 @@ +"""Tools to help interface with foreign shells, such as Bash.""" +import re +import shlex +import builtins +import subprocess +from warnings import warn +from functools import lru_cache + + +COMMAND = """ +echo __XONSH_ENV_BEG__ +{envcmd} +echo __XONSH_ENV_END__ +echo __XONSH_ALIAS_BEG__ +{aliascmd} +echo __XONSH_ALIAS_END__ +""".strip() + +FAILED_COMMAND_STDOUT = """ +__XONSH_ENV_BEG__ +__XONSH_ENV_END__ +__XONSH_ALIAS_BEG__ +__XONSH_ALIAS_END__ +""".strip() + +@lru_cache() +def foreign_shell_data(shell, interactive=True, login=False, envcmd='env', + aliascmd='alias', extra_args=(), currenv=None): + """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'. + login : 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 : list of str, optional + Addtional command line options to pass into the shell. + currenv : dict or None, optional + Manual override for the current environment. + + Returns + ------- + env : dict + Dictionary of shell's environment + aliases : dict + Dictionary of shell's alaiases. + """ + cmd = [shell] + if interactive: + cmd.append('-i') + if login: + cmd.append('-l') + cmd.extend(extra_args) + 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() + try: + s = subprocess.check_output(cmd,stderr=subprocess.PIPE, env=currenv, + universal_newlines=True) + except (subprocess.CalledProcessError, FileNotFoundError): + s = FAILED_COMMAND_STDOUT + 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 + From 06200c96f3dff28acd3582453a339080c655c8e9 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 11 Oct 2015 10:47:40 +1100 Subject: [PATCH 12/21] aliases: fix linter warnings There are a couple more PEP8 violations around here, but I'll leave those for another day. --- xonsh/aliases.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/xonsh/aliases.py b/xonsh/aliases.py index ca9b9e3c6..a7e3eff88 100644 --- a/xonsh/aliases.py +++ b/xonsh/aliases.py @@ -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 @@ -109,7 +108,8 @@ def bash_aliases(): universal_newlines=True) except (subprocess.CalledProcessError, FileNotFoundError): s = '' - items = [line.split('=', 1) for line in s.splitlines() if line.startswith('alias ') and '=' in line] + items = [line.split('=', 1) for line in s.splitlines() + if line.startswith('alias ') and '=' in line] aliases = {} for key, value in items: try: @@ -185,5 +185,3 @@ elif ON_MAC: else: DEFAULT_ALIASES['grep'] = ['grep', '--color=auto'] DEFAULT_ALIASES['ls'] = ['ls', '--color=auto', '-v'] - - From 1e24bbb2ede0275eea400b7aa4b061f83b4eac0d Mon Sep 17 00:00:00 2001 From: Anthony Scopatz Date: Sat, 10 Oct 2015 19:52:53 -0400 Subject: [PATCH 13/21] added tests for foreign shells --- docs/api/foreign_shells.rst | 10 +++++++ docs/api/index.rst | 1 + tests/bashrc.sh | 5 ++++ tests/test_foreign_shells.py | 56 ++++++++++++++++++++++++++++++++++++ xonsh/foreign_shells.py | 15 +++++++--- 5 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 docs/api/foreign_shells.rst create mode 100644 tests/bashrc.sh create mode 100644 tests/test_foreign_shells.py diff --git a/docs/api/foreign_shells.rst b/docs/api/foreign_shells.rst new file mode 100644 index 000000000..d11488410 --- /dev/null +++ b/docs/api/foreign_shells.rst @@ -0,0 +1,10 @@ +.. _xonsh_foreign_shells: + +****************************************************** +Foreign Shell Tools (``xonsh.foreign_shells``) +****************************************************** + +.. automodule:: xonsh.foreign_shells + :members: + :undoc-members: + :inherited-members: diff --git a/docs/api/index.rst b/docs/api/index.rst index 367dbff38..61d6485d9 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -51,5 +51,6 @@ For those of you who want the gritty details. lazyjson teepty openpy + foriegn_shells main pyghooks diff --git a/tests/bashrc.sh b/tests/bashrc.sh new file mode 100644 index 000000000..a150d22da --- /dev/null +++ b/tests/bashrc.sh @@ -0,0 +1,5 @@ +export EMERALD="SWORD" +alias ll='ls -a -lF' +alias la='ls -A' +export MIGHTY=WARRIOR +alias l='ls -CF' diff --git a/tests/test_foreign_shells.py b/tests/test_foreign_shells.py new file mode 100644 index 000000000..adc9d4657 --- /dev/null +++ b/tests/test_foreign_shells.py @@ -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() diff --git a/xonsh/foreign_shells.py b/xonsh/foreign_shells.py index 5ea41d94a..07826576a 100644 --- a/xonsh/foreign_shells.py +++ b/xonsh/foreign_shells.py @@ -25,7 +25,8 @@ __XONSH_ALIAS_END__ @lru_cache() def foreign_shell_data(shell, interactive=True, login=False, envcmd='env', - aliascmd='alias', extra_args=(), currenv=None): + 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. @@ -41,10 +42,12 @@ def foreign_shell_data(shell, interactive=True, login=False, envcmd='env', The command to generate environment output with. aliascmd : str, optional The command to generate alais output with. - extra_args : list of str, optional + extra_args : tuple of str, optional Addtional command line options to pass into the shell. - currenv : dict or None, optional + 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 ------- @@ -54,19 +57,23 @@ def foreign_shell_data(shell, interactive=True, login=False, envcmd='env', 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.extend(extra_args) 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 s = FAILED_COMMAND_STDOUT env = parse_env(s) aliases = parse_aliases(s) From a13773d237062ae6ab255881fab420fa53807a48 Mon Sep 17 00:00:00 2001 From: Anthony Scopatz Date: Sat, 10 Oct 2015 22:34:30 -0400 Subject: [PATCH 14/21] have environment hookups --- docs/envvars.rst | 6 ++- xonsh/environ.py | 53 +++++++++++------- xonsh/foreign_shells.py | 117 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 155 insertions(+), 21 deletions(-) diff --git a/docs/envvars.rst b/docs/envvars.rst index 9d20fb153..61a617bb8 100644 --- a/docs/envvars.rst +++ b/docs/envvars.rst @@ -113,8 +113,12 @@ applicable. - ``~/.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'`` + - ``~/.xonshrc`` - Location of run control file. * - XONSH_CONFIG_DIR - ``$XDG_CONFIG_HOME/xonsh`` diff --git a/xonsh/environ.py b/xonsh/environ.py index aa299e006..24e7f9e12 100644 --- a/xonsh/environ.py +++ b/xonsh/environ.py @@ -1,6 +1,7 @@ """Environment for the xonsh shell.""" import os import re +import json import socket import string import locale @@ -16,6 +17,7 @@ from xonsh.tools import TERM_COLORS, ON_WINDOWS, ON_MAC, ON_LINUX, string_types, env_path_to_str, is_bool, to_bool, bool_to_str, is_history_tuple, to_history_tuple, \ history_tuple_to_str from xonsh.dirstack import _get_cwd +from xonsh.foreign_shells import DEFAULT_SHELLS, load_foreign_envs LOCALE_CATS = { 'LC_CTYPE': locale.LC_CTYPE, @@ -89,11 +91,19 @@ def xonsh_data_dir(env): @default_value def xonsh_config_dir(env): """Ensures and returns the $XONSH_CONFIG_DIR""" - xcd = os.path.join(xdgch, 'xonsh') + 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. @@ -127,6 +137,7 @@ DEFAULT_VALUES = { '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, @@ -574,23 +585,24 @@ try: except AttributeError: pass -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): @@ -613,6 +625,7 @@ def xonshrc_context(rcfile=None, execer=None): execer.filename = fname return env + def windows_env_fixes(ctx): """Environment fixes for Windows. Operates in-place.""" # Windows default prompt doesn't work. @@ -637,7 +650,9 @@ 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)) if ON_WINDOWS: windows_env_fixes(ctx) # finalize env diff --git a/xonsh/foreign_shells.py b/xonsh/foreign_shells.py index 07826576a..e5de3ffd5 100644 --- a/xonsh/foreign_shells.py +++ b/xonsh/foreign_shells.py @@ -1,10 +1,15 @@ """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 = """ @@ -34,7 +39,7 @@ def foreign_shell_data(shell, interactive=True, login=False, envcmd='env', ---------- shell : str The name of the shell, such as 'bash' or '/bin/sh'. - login : bool, optional + interactive : bool, optional Whether the shell should be run in interactive mode. login : bool, optional Whether the shell should be a login shell. @@ -121,3 +126,113 @@ def parse_aliases(s): 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): + 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: + 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): + """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. + + Returns + ------- + env : dict + A dictionary of the merged environments. + """ + shells = _get_shells(shells=shells, config=config) + 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): + """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. + + Returns + ------- + env : dict + A dictionary of the merged environments. + """ + shells = _get_shells(shells=shells, config=config) + aliases = {} + for shell in shells: + shell = ensure_shell(shell) + _, shaliases = foreign_shell_data(**shell) + aliases.update(shaliases) + return aliases From bc1a54c1e69d81317a76f9bcc8909e41b5f9bfba Mon Sep 17 00:00:00 2001 From: Anthony Scopatz Date: Sat, 10 Oct 2015 22:41:51 -0400 Subject: [PATCH 15/21] hooked up aliases --- xonsh/aliases.py | 31 ------------------------------- xonsh/built_ins.py | 3 ++- xonsh/foreign_shells.py | 19 ++++++++++++------- 3 files changed, 14 insertions(+), 39 deletions(-) diff --git a/xonsh/aliases.py b/xonsh/aliases.py index a7e3eff88..163682edd 100644 --- a/xonsh/aliases.py +++ b/xonsh/aliases.py @@ -100,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', '-c', 'alias'], - stderr=subprocess.PIPE, - universal_newlines=True) - except (subprocess.CalledProcessError, FileNotFoundError): - s = '' - items = [line.split('=', 1) for line in s.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 Bash alias "{0}": {1!r}'.format(key, exc), - RuntimeWarning) - continue - aliases[key] = value - return aliases - - DEFAULT_ALIASES = { 'cd': cd, 'pushd': pushd, diff --git a/xonsh/built_ins.py b/xonsh/built_ins.py index 745594785..29aa92636 100644 --- a/xonsh/built_ins.py +++ b/xonsh/built_ins.py @@ -26,6 +26,7 @@ from xonsh.aliases import DEFAULT_ALIASES, bash_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 @@ -663,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, diff --git a/xonsh/foreign_shells.py b/xonsh/foreign_shells.py index e5de3ffd5..32f8f8446 100644 --- a/xonsh/foreign_shells.py +++ b/xonsh/foreign_shells.py @@ -165,7 +165,7 @@ def ensure_shell(shell): DEFAULT_SHELLS = ({'shell': 'bash'},) -def _get_shells(shells=None, config=None): +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: @@ -178,13 +178,14 @@ def _get_shells(shells=None, config=None): conf = json.load(f) shells = conf.get('foreign_shells', DEFAULT_SHELLS) else: - msg = 'could not find xonsh config file ($XONSHCONFIG) at {0!r}' - warn(msg.format(config), RuntimeWarning) + 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): +def load_foreign_envs(shells=None, config=None, issue_warning=True): """Loads environments from foreign shells. Parameters @@ -196,13 +197,15 @@ def load_foreign_envs(shells=None, config=None): 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) + shells = _get_shells(shells=shells, config=config, issue_warning=issue_warning) env = {} for shell in shells: shell = ensure_shell(shell) @@ -211,7 +214,7 @@ def load_foreign_envs(shells=None, config=None): return env -def load_foreign_aliases(shells=None, config=None): +def load_foreign_aliases(shells=None, config=None, issue_warning=True): """Loads aliases from foreign shells. Parameters @@ -223,13 +226,15 @@ def load_foreign_aliases(shells=None, config=None): 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) + shells = _get_shells(shells=shells, config=config, issue_warning=issue_warning) aliases = {} for shell in shells: shell = ensure_shell(shell) From 4b4d1919367ee2150f00749059a0c6ae963fd51e Mon Sep 17 00:00:00 2001 From: Anthony Scopatz Date: Sat, 10 Oct 2015 23:22:36 -0400 Subject: [PATCH 16/21] more changes --- docs/index.rst | 1 + docs/xonshconfig.json | 12 +++++++ docs/xonshconfig.rst | 84 +++++++++++++++++++++++++++++++++++++++++++ xonsh/built_ins.py | 2 +- xonsh/environ.py | 3 +- 5 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 docs/xonshconfig.json create mode 100644 docs/xonshconfig.rst diff --git a/docs/index.rst b/docs/index.rst index 3dd763b8d..6810ac42b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -67,6 +67,7 @@ Contents tutorial tutorial_hist xonshrc + xonshconfig envvars aliases windows diff --git a/docs/xonshconfig.json b/docs/xonshconfig.json new file mode 100644 index 000000000..3123a4da9 --- /dev/null +++ b/docs/xonshconfig.json @@ -0,0 +1,12 @@ +{"env": { + "EDITOR": "xo", + "PAGER": "more" + }, + "foreign_shells": [ + {"shell": "bash", + "login": true, + "extra_args": ["--rcfile", "/path/to/rcfile"] + }, + {"shell": "zsh"} + ] +} \ No newline at end of file diff --git a/docs/xonshconfig.rst b/docs/xonshconfig.rst new file mode 100644 index 000000000..d18d9fe1f --- /dev/null +++ b/docs/xonshconfig.rst @@ -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 ` + +.. include:: xonshconfig.json + :code: json + diff --git a/xonsh/built_ins.py b/xonsh/built_ins.py index 29aa92636..ba5e8d631 100644 --- a/xonsh/built_ins.py +++ b/xonsh/built_ins.py @@ -22,7 +22,7 @@ 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 diff --git a/xonsh/environ.py b/xonsh/environ.py index 24e7f9e12..2051ed1fc 100644 --- a/xonsh/environ.py +++ b/xonsh/environ.py @@ -652,7 +652,8 @@ def default_env(env=None): ctx.update(os.environ) conf = load_static_config(ctx) ctx.update(conf.get('env', ())) - ctx.update(load_foreign_envs(shells=conf.get('foreign_shells', DEFAULT_SHELLS)) + ctx.update(load_foreign_envs(shells=conf.get('foreign_shells', DEFAULT_SHELLS), + issue_warning=False)) if ON_WINDOWS: windows_env_fixes(ctx) # finalize env From 502bce87ffa045849e240e5e3484daef06ca9d45 Mon Sep 17 00:00:00 2001 From: Anthony Scopatz Date: Sun, 11 Oct 2015 10:51:26 -0400 Subject: [PATCH 17/21] minor fixes from review --- docs/api/index.rst | 2 +- xonsh/environ.py | 2 +- xonsh/foreign_shells.py | 13 +++---------- xonsh/shell.py | 2 +- 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/docs/api/index.rst b/docs/api/index.rst index 61d6485d9..2d0706115 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -51,6 +51,6 @@ For those of you who want the gritty details. lazyjson teepty openpy - foriegn_shells + foreign_shells main pyghooks diff --git a/xonsh/environ.py b/xonsh/environ.py index 2051ed1fc..22bfbbb46 100644 --- a/xonsh/environ.py +++ b/xonsh/environ.py @@ -168,7 +168,7 @@ class Env(MutableMapping): locale via locale.getlocale() and locale.setlocale() functions. An Env instance may be converted to an untyped version suitable for - use in a subprocess. + use in a subprocess. """ _arg_regex = re.compile(r'ARG(\d+)') diff --git a/xonsh/foreign_shells.py b/xonsh/foreign_shells.py index 32f8f8446..f72635371 100644 --- a/xonsh/foreign_shells.py +++ b/xonsh/foreign_shells.py @@ -21,13 +21,6 @@ echo __XONSH_ALIAS_BEG__ echo __XONSH_ALIAS_END__ """.strip() -FAILED_COMMAND_STDOUT = """ -__XONSH_ENV_BEG__ -__XONSH_ENV_END__ -__XONSH_ALIAS_BEG__ -__XONSH_ALIAS_END__ -""".strip() - @lru_cache() def foreign_shell_data(shell, interactive=True, login=False, envcmd='env', aliascmd='alias', extra_args=(), currenv=None, @@ -79,7 +72,7 @@ def foreign_shell_data(shell, interactive=True, login=False, envcmd='env', except (subprocess.CalledProcessError, FileNotFoundError): if not safe: raise - s = FAILED_COMMAND_STDOUT + return {}, {} env = parse_env(s) aliases = parse_aliases(s) return env, aliases @@ -231,8 +224,8 @@ def load_foreign_aliases(shells=None, config=None, issue_warning=True): Returns ------- - env : dict - A dictionary of the merged environments. + aliases : dict + A dictionary of the merged alaiases. """ shells = _get_shells(shells=shells, config=config, issue_warning=issue_warning) aliases = {} diff --git a/xonsh/shell.py b/xonsh/shell.py index d54c9cc9d..152d3364d 100644 --- a/xonsh/shell.py +++ b/xonsh/shell.py @@ -26,7 +26,7 @@ class Shell(object): def __init__(self, ctx=None, shell_type=None, **kwargs): self._init_environ(ctx) env = builtins.__xonsh_env__ - # piclk a valid shell + # pick a valid shell if shell_type is not None: env['SHELL_TYPE'] = shell_type shell_type = env.get('SHELL_TYPE') From e78564b1689338a783e137ae6085934d21d343d2 Mon Sep 17 00:00:00 2001 From: Anthony Scopatz Date: Sun, 11 Oct 2015 11:02:00 -0400 Subject: [PATCH 18/21] minor bug fix when not reading in xonshrc --- xonsh/shell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xonsh/shell.py b/xonsh/shell.py index 152d3364d..71a4f3e3e 100644 --- a/xonsh/shell.py +++ b/xonsh/shell.py @@ -59,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__' From 84fb672c619a57475f25fa868d403892982f4203 Mon Sep 17 00:00:00 2001 From: Anthony Scopatz Date: Sun, 11 Oct 2015 14:00:38 -0400 Subject: [PATCH 19/21] added most of teepty delay --- tests/test_tools.py | 6 +++++- xonsh/environ.py | 3 ++- xonsh/proc.py | 5 ++++- xonsh/teepty.py | 23 ++++++++++++++++++++--- xonsh/tools.py | 5 +++++ 5 files changed, 36 insertions(+), 6 deletions(-) diff --git a/tests/test_tools.py b/tests/test_tools.py index 77bd7963e..1e8805b55 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -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') diff --git a/xonsh/environ.py b/xonsh/environ.py index 22bfbbb46..d4892fbc6 100644 --- a/xonsh/environ.py +++ b/xonsh/environ.py @@ -15,7 +15,7 @@ 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 @@ -61,6 +61,7 @@ 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), } # diff --git a/xonsh/proc.py b/xonsh/proc.py index 106c3e027..e33dfb85b 100644 --- a/xonsh/proc.py +++ b/xonsh/proc.py @@ -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): diff --git a/xonsh/teepty.py b/xonsh/teepty.py index 359eb5897..7b18666a4 100644 --- a/xonsh/teepty.py +++ b/xonsh/teepty.py @@ -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: @@ -255,8 +266,14 @@ class TeePTY(object): tsi.flush() else: raise ValueError('stdin not understood {0!r}'.format(stdin)) - + def _delay_for_pipe(self, env=None, delay=None): + 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__': tpty = TeePTY() @@ -266,4 +283,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)) diff --git a/xonsh/tools.py b/xonsh/tools.py index dd9caa5ec..3f7b6de43 100644 --- a/xonsh/tools.py +++ b/xonsh/tools.py @@ -469,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 From 6d2fecb3a1735f836e4dbcbf9a3384542ce1832b Mon Sep 17 00:00:00 2001 From: Anthony Scopatz Date: Sun, 11 Oct 2015 14:23:30 -0400 Subject: [PATCH 20/21] defaults and docs --- docs/envvars.rst | 8 ++++++++ xonsh/environ.py | 1 + xonsh/teepty.py | 21 +++++++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/docs/envvars.rst b/docs/envvars.rst index 61a617bb8..e4265b6a7 100644 --- a/docs/envvars.rst +++ b/docs/envvars.rst @@ -95,6 +95,14 @@ applicable. - 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) diff --git a/xonsh/environ.py b/xonsh/environ.py index d4892fbc6..1d0528efe 100644 --- a/xonsh/environ.py +++ b/xonsh/environ.py @@ -135,6 +135,7 @@ DEFAULT_VALUES = { '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')), diff --git a/xonsh/teepty.py b/xonsh/teepty.py index 7b18666a4..9ac517656 100644 --- a/xonsh/teepty.py +++ b/xonsh/teepty.py @@ -268,6 +268,27 @@ class TeePTY(object): 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) From ca7027414cc3a084971d6ccc691d317dd9e476dd Mon Sep 17 00:00:00 2001 From: Anthony Scopatz Date: Mon, 12 Oct 2015 00:22:28 -0400 Subject: [PATCH 21/21] fixed typo --- xonsh/foreign_shells.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xonsh/foreign_shells.py b/xonsh/foreign_shells.py index f72635371..1f509a899 100644 --- a/xonsh/foreign_shells.py +++ b/xonsh/foreign_shells.py @@ -225,7 +225,7 @@ def load_foreign_aliases(shells=None, config=None, issue_warning=True): Returns ------- aliases : dict - A dictionary of the merged alaiases. + A dictionary of the merged aliases. """ shells = _get_shells(shells=shells, config=config, issue_warning=issue_warning) aliases = {}