mirror of
https://github.com/xonsh/xonsh.git
synced 2025-03-04 16:34:47 +01:00
commit
57e7c1f438
20 changed files with 1526 additions and 300 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -16,6 +16,7 @@ build/
|
|||
dist/
|
||||
xonsh.egg-info/
|
||||
docs/_build/
|
||||
docs/envvarsbody
|
||||
|
||||
# temporary files from vim and emacs
|
||||
*~
|
||||
|
|
|
@ -6,6 +6,10 @@ Current Developments
|
|||
====================
|
||||
**Added:**
|
||||
|
||||
* New configuration utility 'xonfig' which reports current system
|
||||
setup information and creates config files through an interactive
|
||||
wizard.
|
||||
* Toolkit for creating wizards now available
|
||||
* timeit and which aliases will now complete their arguments.
|
||||
* $COMPLETIONS_MENU_ROWS environment variable controls the size of the
|
||||
tab-completion menu in prompt-toolkit.
|
||||
|
@ -14,6 +18,10 @@ Current Developments
|
|||
|
||||
**Changed:**
|
||||
|
||||
* The xonfig wizard will run on interactive startup if no configuration
|
||||
file is found.
|
||||
* BaseShell now has a singleline() method for prompting a single input.
|
||||
* Environment variable docs are now auto-generated.
|
||||
* Prompt-toolkit shell will now dynamically allocate space for the
|
||||
tab-completion menu.
|
||||
* Looking up nonexistent environment variables now generates an error
|
||||
|
|
|
@ -56,3 +56,5 @@ For those of you who want the gritty details.
|
|||
main
|
||||
pyghooks
|
||||
jupyter_kernel
|
||||
wizard
|
||||
xonfig
|
||||
|
|
11
docs/api/wizard.rst
Normal file
11
docs/api/wizard.rst
Normal file
|
@ -0,0 +1,11 @@
|
|||
.. _xonsh_wizard:
|
||||
|
||||
******************************************
|
||||
Wizard Making Tools (``xonsh.wizard``)
|
||||
******************************************
|
||||
|
||||
.. automodule:: xonsh.wizard
|
||||
:members:
|
||||
:undoc-members:
|
||||
:inherited-members:
|
||||
|
11
docs/api/xonfig.rst
Normal file
11
docs/api/xonfig.rst
Normal file
|
@ -0,0 +1,11 @@
|
|||
.. _xonsh_xonfig:
|
||||
|
||||
***********************************************
|
||||
Xonsh Configuration Utility (``xonsh.xonfig``)
|
||||
***********************************************
|
||||
|
||||
.. automodule:: xonsh.xonfig
|
||||
:members:
|
||||
:undoc-members:
|
||||
:inherited-members:
|
||||
|
41
docs/conf.py
41
docs/conf.py
|
@ -10,6 +10,7 @@
|
|||
|
||||
import sys, os
|
||||
from xonsh import __version__ as XONSH_VERSION
|
||||
from xonsh.environ import DEFAULT_DOCS, Env
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
|
@ -230,3 +231,43 @@ autosummary_generate = []
|
|||
# Prevent numpy from making silly tables
|
||||
numpydoc_show_class_members = False
|
||||
|
||||
|
||||
#
|
||||
# Auto-generate some docs
|
||||
#
|
||||
|
||||
def make_envvars():
|
||||
env = Env()
|
||||
vars = sorted(DEFAULT_DOCS.keys())
|
||||
s = ('.. list-table::\n'
|
||||
' :header-rows: 0\n\n')
|
||||
table = []
|
||||
ncol = 3
|
||||
row = ' {0} - :ref:`${1} <{2}>`'
|
||||
for i, var in enumerate(vars):
|
||||
star = '*' if i%ncol == 0 else ' '
|
||||
table.append(row.format(star, var, var.lower()))
|
||||
table.extend([' -']*((ncol - len(vars)%ncol)%ncol))
|
||||
s += '\n'.join(table) + '\n\n'
|
||||
s += ('Listing\n'
|
||||
'-------\n\n')
|
||||
sec = ('.. _{low}:\n\n'
|
||||
'{title}\n'
|
||||
'{under}\n'
|
||||
'{docstr}\n\n'
|
||||
'**configurable:** {configurable}\n\n'
|
||||
'**default:** {default}\n\n'
|
||||
'-------\n\n')
|
||||
for var in vars:
|
||||
title = '$' + var
|
||||
under = '.' * len(title)
|
||||
vd = env.get_docs(var)
|
||||
s += sec.format(low=var.lower(), title=title, under=under,
|
||||
docstr=vd.docstr, configurable=vd.configurable,
|
||||
default=vd.default)
|
||||
s = s[:-9]
|
||||
fname = os.path.join(os.path.dirname(__file__), 'envvarsbody')
|
||||
with open(fname, 'w') as f:
|
||||
f.write(s)
|
||||
|
||||
make_envvars()
|
||||
|
|
239
docs/envvars.rst
239
docs/envvars.rst
|
@ -4,241 +4,4 @@ The following table displays information about the environment variables that
|
|||
effect XONSH performance in some way. It also lists their default values, if
|
||||
applicable.
|
||||
|
||||
.. Please keep the following in alphabetic order - scopatz
|
||||
|
||||
.. list-table::
|
||||
:widths: 1 1 3
|
||||
:header-rows: 1
|
||||
|
||||
* - variable
|
||||
- default
|
||||
- description
|
||||
* - ANSICON
|
||||
- No default set
|
||||
- This is used on Windows to set the title, if available.
|
||||
* - AUTO_CD
|
||||
- ``False``
|
||||
- Flag to enable changing to a directory by entering the dirname or full path only (without the `cd` command)
|
||||
* - AUTO_PUSHD
|
||||
- ``False``
|
||||
- Flag for automatically pushing directories onto the directory stack.
|
||||
* - AUTO_SUGGEST
|
||||
- ``True``
|
||||
- Enable automatic command suggestions based on history (like in fish shell).
|
||||
|
||||
Pressing the right arrow key inserts the currently displayed suggestion.
|
||||
|
||||
(Only usable with SHELL_TYPE=prompt_toolkit)
|
||||
* - 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')``
|
||||
and on Arch Linux is ``('/usr/share/bash-completion/bash_completion',
|
||||
'/usr/share/bash-completion/completions/git')``.
|
||||
- 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.
|
||||
* - COMPLETIONS_DISPLAY
|
||||
- ``'multi'``
|
||||
- Configure if and how Python completions are displayed by the prompt_toolkit shell.
|
||||
|
||||
This option does not affect bash completions, auto-suggestions etc.
|
||||
|
||||
Changing it at runtime will take immediate effect, so you can quickly
|
||||
disable and enable completions during shell sessions.
|
||||
|
||||
- If COMPLETIONS_DISPLAY is ``'none'`` or ``'false'``, do not display those completions.
|
||||
|
||||
- If COMPLETIONS_DISPLAY is ``'single'``, display completions in a single column while typing.
|
||||
|
||||
- If COMPLETIONS_DISPLAY is ``'multi'`` or ``'true'``, display completions in multiple columns while typing.
|
||||
|
||||
These option values are not case- or type-sensitive, so e.g.
|
||||
writing ``$COMPLETIONS_DISPLAY = None`` and ``$COMPLETIONS_DISPLAY = 'none'`` is equivalent.
|
||||
|
||||
(Only usable with SHELL_TYPE=prompt_toolkit)
|
||||
* - COMPLETIONS_MENU_ROWS
|
||||
- ``5``
|
||||
- Number of rows to reserve for tab-completions menu if
|
||||
``$COMPLETIONS_DISPLAY`` is ``'single'`` or ``'multi'``. This only
|
||||
effects the prompt-toolkit shell.
|
||||
* - DIRSTACK_SIZE
|
||||
- ``20``
|
||||
- Maximum size of the directory stack.
|
||||
* - EXPAND_ENV_VARS
|
||||
- ``True``
|
||||
- Toggles whether environment variables are expanded inside of strings in subprocess mode.
|
||||
* - FORCE_POSIX_PATHS
|
||||
- ``False``
|
||||
- Forces forward slashes (``/``) on Windows systems when using auto completion if
|
||||
set to anything truthy.
|
||||
* - FORMATTER_DICT
|
||||
- xonsh.environ.FORMATTER_DICT
|
||||
- Dictionary containing variables to be used when formatting PROMPT and TITLE
|
||||
see `Customizing the Prompt <tutorial.html#customizing-the-prompt>`_.
|
||||
* - HISTCONTROL
|
||||
- ``set([])``
|
||||
- A set of strings (comma-separated list in string form) of options that
|
||||
determine what commands are saved to the history list. By default all
|
||||
commands are saved. The option ``ignoredups`` will not save the command
|
||||
if it matches the previous command. The option ``ignoreerr`` will cause
|
||||
any commands that fail (i.e. return non-zero exit status) to not be
|
||||
added to the history list.
|
||||
* - IGNOREEOF
|
||||
- ``False``
|
||||
- Prevents Ctrl-D from exiting the shell.
|
||||
* - INDENT
|
||||
- ``' '``
|
||||
- Indentation string for multiline input
|
||||
* - MOUSE_SUPPORT
|
||||
- ``False``
|
||||
- Enable mouse support in the prompt_toolkit shell.
|
||||
|
||||
This allows clicking for positioning the cursor or selecting a completion. In some terminals
|
||||
however, this disables the ability to scroll back through the history of the terminal.
|
||||
|
||||
(Only usable with SHELL_TYPE=prompt_toolkit)
|
||||
* - MULTILINE_PROMPT
|
||||
- ``'.'``
|
||||
- Prompt text for 2nd+ lines of input, may be str or function which returns
|
||||
a str.
|
||||
* - OLDPWD
|
||||
- No default
|
||||
- Used to represent a previous present working directory.
|
||||
* - PATH
|
||||
- ``()``
|
||||
- List of strings representing where to look for executables.
|
||||
* - PATHEXT
|
||||
- ``()``
|
||||
- List of strings for filtering valid exeutables by.
|
||||
* - PROMPT
|
||||
- xonsh.environ.DEFAULT_PROMPT
|
||||
- The prompt text. May contain keyword arguments which are auto-formatted,
|
||||
see `Customizing the Prompt <tutorial.html#customizing-the-prompt>`_.
|
||||
* - PROMPT_TOOLKIT_COLORS
|
||||
- ``{}``
|
||||
- This is a mapping of from color names to HTML color codes. Whenever
|
||||
prompt-toolkit would color a word a particular color (in the prompt, or
|
||||
in syntax highlighting), it will use the value specified here to
|
||||
represent that color, instead of its default. If a color is not
|
||||
specified here, prompt-toolkit uses the colors from
|
||||
``xonsh.tools._PT_COLORS``.
|
||||
* - 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
|
||||
- ``'prompt_toolkit'`` if on Windows, otherwise ``'readline'``
|
||||
- Which shell is used. Currently two base shell types are supported:
|
||||
``'readline'`` that is backed by Python's readline module, and
|
||||
``'prompt_toolkit'`` that uses external library of the same name.
|
||||
To use the prompt_toolkit shell you need to have
|
||||
`prompt_toolkit <https://github.com/jonathanslenders/python-prompt-toolkit>`_
|
||||
library installed. To specify which shell should be used, do so in the run
|
||||
control file. Additionally, you may also set this value to ``'random'``
|
||||
to get a random choice of shell type on startup.
|
||||
* - SUGGEST_COMMANDS
|
||||
- ``True``
|
||||
- When a user types an invalid command, xonsh will try to offer suggestions of
|
||||
similar valid commands if this is ``True``.
|
||||
* - SUGGEST_MAX_NUM
|
||||
- ``5``
|
||||
- xonsh will show at most this many suggestions in response to an invalid command.
|
||||
If negative, there is no limit to how many suggestions are shown.
|
||||
* - SUGGEST_THRESHOLD
|
||||
- ``3``
|
||||
- An error threshold. If the Levenshtein distance between the entered command and
|
||||
a valid command is less than this value, the valid command will be offered as a
|
||||
suggestion.
|
||||
* - TEEPTY_PIPE_DELAY
|
||||
- ``0.01``
|
||||
- The number of [seconds] to delay a spawned process if it has information
|
||||
being piped in via stdin. This value must be a float. If a value less than
|
||||
or equal to zero is passed in, no delay is used. This can be used to fix
|
||||
situations where a spawned process, such as piping into ``grep``, exits
|
||||
too quickly for the piping operation itself. TeePTY (and thus this variable)
|
||||
are currently only used when ``$XONSH_STORE_STDOUT`` is ``True``.
|
||||
* - TERM
|
||||
- No default
|
||||
- TERM is sometimes set by the terminal emulator. This is used (when valid)
|
||||
to determine whether or not to set the title. Users shouldn't need to
|
||||
set this themselves.
|
||||
* - TITLE
|
||||
- xonsh.environ.DEFAULT_TITLE
|
||||
- The title text for the window in which xonsh is running. Formatted in the same
|
||||
manner as PROMPT,
|
||||
see `Customizing the Prompt <tutorial.html#customizing-the-prompt>`_.
|
||||
* - VI_MODE
|
||||
- ``False``
|
||||
- Flag to enable ``vi_mode`` in the ``prompt_toolkit`` shell.
|
||||
* - 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
|
||||
- ``('/etc/xonshrc', '~/.xonshrc')`` (Linux and OSX)
|
||||
``('%ALLUSERSPROFILE%\xonsh\xonshrc', '~/.xonshrc')`` (Windows)
|
||||
- A tuple of the locations of run control files, if they exist. User defined
|
||||
run control file will supercede values set in system-wide control file if there
|
||||
is a naming collision.
|
||||
* - 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_ENCODING
|
||||
- ``sys.getdefaultencoding()``
|
||||
- This is the that xonsh should use for subrpocess operations.
|
||||
* - XONSH_ENCODING_ERRORS
|
||||
- ``'surrogateescape'``
|
||||
- The flag for how to handle encoding errors should they happen.
|
||||
Any string flag that has been previously registered with Python
|
||||
is allowed. See the `Python codecs documentation <https://docs.python.org/3/library/codecs.html#error-handlers>`_
|
||||
for more information and available options.
|
||||
* - 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_LOGIN
|
||||
- ``True`` if xonsh is running as a login shell, and ``False`` otherwise.
|
||||
- Whether or not xonsh is a login shell.
|
||||
* - 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.
|
||||
|
||||
.. include:: envvarsbody
|
||||
|
|
53
tests/test_wizard.py
Normal file
53
tests/test_wizard.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Tests the xonsh lexer."""
|
||||
from __future__ import unicode_literals, print_function
|
||||
import os
|
||||
|
||||
import nose
|
||||
from nose.tools import assert_equal, assert_true, assert_false
|
||||
|
||||
from xonsh.wizard import (Node, Wizard, Pass, PrettyFormatter,
|
||||
Message, Question, StateVisitor)
|
||||
|
||||
|
||||
TREE0 = Wizard(children=[Pass(), Message(message='yo')])
|
||||
TREE1 = Question('wakka?', {'jawaka': Pass()})
|
||||
|
||||
def test_pretty_format_tree0():
|
||||
exp = ('Wizard(children=[\n'
|
||||
' Pass(),\n'
|
||||
" Message('yo')\n"
|
||||
'])')
|
||||
obs = PrettyFormatter(TREE0).visit()
|
||||
yield assert_equal, exp, obs
|
||||
yield assert_equal, exp, str(TREE0)
|
||||
yield assert_equal, exp.replace('\n', ''), repr(TREE0)
|
||||
|
||||
|
||||
def test_pretty_format_tree1():
|
||||
exp = ('Question(\n'
|
||||
" question='wakka?',\n"
|
||||
' responses={\n'
|
||||
" 'jawaka': Pass()\n"
|
||||
' }\n'
|
||||
')')
|
||||
obs = PrettyFormatter(TREE1).visit()
|
||||
yield assert_equal, exp, obs
|
||||
yield assert_equal, exp, str(TREE1)
|
||||
yield assert_equal, exp.replace('\n', ''), repr(TREE1)
|
||||
|
||||
|
||||
def test_state_visitor_store():
|
||||
exp = {'rick': [{}, {}, {'and': 'morty'}]}
|
||||
sv = StateVisitor()
|
||||
sv.store('/rick/2/and', 'morty')
|
||||
obs = sv.state
|
||||
yield assert_equal, exp, obs
|
||||
|
||||
exp['rick'][1]['mr'] = 'meeseeks'
|
||||
sv.store('/rick/-2/mr', 'meeseeks')
|
||||
yield assert_equal, exp, obs
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
nose.runmodule()
|
|
@ -162,6 +162,12 @@ def bang_bang(args, stdin=None):
|
|||
return bang_n(['-1'])
|
||||
|
||||
|
||||
def xonfig(args, stdin=None):
|
||||
"""Runs the xonsh configuration utility."""
|
||||
from xonsh.xonfig import main # lazy import
|
||||
return main(args)
|
||||
|
||||
|
||||
DEFAULT_ALIASES = {
|
||||
'cd': cd,
|
||||
'pushd': pushd,
|
||||
|
@ -182,6 +188,7 @@ DEFAULT_ALIASES = {
|
|||
'!!': bang_bang,
|
||||
'!n': bang_n,
|
||||
'timeit': timeit_alias,
|
||||
'xonfig': xonfig,
|
||||
'scp-resume': ['rsync', '--partial', '-h', '--progress', '--rsh=ssh'],
|
||||
'ipynb': ['ipython', 'notebook', '--no-browser'],
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ class _TeeOut(object):
|
|||
|
||||
def write(self, data):
|
||||
"""Writes data to the original stdout and the buffer."""
|
||||
data = data.replace('\001', '').replace('\002', '')
|
||||
self.stdout.write(data)
|
||||
self.buffer.write(data)
|
||||
|
||||
|
@ -62,6 +63,7 @@ class _TeeErr(object):
|
|||
|
||||
def write(self, data):
|
||||
"""Writes data to the original stderr and the buffer."""
|
||||
data = data.replace('\001', '').replace('\002', '')
|
||||
self.stderr.write(data)
|
||||
self.buffer.write(data)
|
||||
|
||||
|
@ -117,6 +119,11 @@ class BaseShell(object):
|
|||
self.need_more_lines = False
|
||||
self.default('')
|
||||
|
||||
def singleline(self, **kwargs):
|
||||
"""Reads a single line of input from the shell."""
|
||||
msg = '{0} has not implemented singleline().'
|
||||
raise RuntimeError(msg.format(self.__class__.__name__))
|
||||
|
||||
def precmd(self, line):
|
||||
"""Called just before execution of line."""
|
||||
return line if self.need_more_lines else line.lstrip()
|
||||
|
|
|
@ -664,13 +664,13 @@ def ensure_list_of_strs(x):
|
|||
return rtn
|
||||
|
||||
|
||||
def load_builtins(execer=None):
|
||||
def load_builtins(execer=None, config=None):
|
||||
"""Loads the xonsh builtins into the Python builtins. Sets the
|
||||
BUILTINS_LOADED variable to True.
|
||||
"""
|
||||
global BUILTINS_LOADED, ENV
|
||||
# private built-ins
|
||||
builtins.__xonsh_env__ = ENV = Env(default_env())
|
||||
builtins.__xonsh_env__ = ENV = Env(default_env(config=config))
|
||||
builtins.__xonsh_ctx__ = {}
|
||||
builtins.__xonsh_help__ = helper
|
||||
builtins.__xonsh_superhelp__ = superhelper
|
||||
|
|
320
xonsh/environ.py
320
xonsh/environ.py
|
@ -9,7 +9,9 @@ import locale
|
|||
import builtins
|
||||
import subprocess
|
||||
from warnings import warn
|
||||
from pprint import pformat
|
||||
from functools import wraps
|
||||
from contextlib import contextmanager
|
||||
from collections import MutableMapping, MutableSequence, MutableSet, namedtuple
|
||||
|
||||
from xonsh import __version__ as XONSH_VERSION
|
||||
|
@ -19,7 +21,8 @@ from xonsh.tools import (
|
|||
env_path_to_str, is_bool, to_bool, bool_to_str, is_history_tuple, to_history_tuple,
|
||||
history_tuple_to_str, is_float, string_types, is_string, DEFAULT_ENCODING,
|
||||
is_completions_display_value, to_completions_display_value, is_string_set,
|
||||
csv_to_set, set_to_csv, get_sep, is_int
|
||||
csv_to_set, set_to_csv, get_sep, is_int, is_bool_seq, csv_to_bool_seq,
|
||||
bool_seq_to_csv
|
||||
)
|
||||
from xonsh.dirstack import _get_cwd
|
||||
from xonsh.foreign_shells import DEFAULT_SHELLS, load_foreign_envs
|
||||
|
@ -53,6 +56,7 @@ represent environment variable validation, conversion, detyping.
|
|||
|
||||
DEFAULT_ENSURERS = {
|
||||
'AUTO_CD': (is_bool, to_bool, bool_to_str),
|
||||
'AUTO_PUSHD': (is_bool, to_bool, bool_to_str),
|
||||
'AUTO_SUGGEST': (is_bool, to_bool, bool_to_str),
|
||||
'BASH_COMPLETIONS': (is_env_path, str_to_env_path, env_path_to_str),
|
||||
'CASE_SENSITIVE_COMPLETIONS': (is_bool, to_bool, bool_to_str),
|
||||
|
@ -67,7 +71,9 @@ DEFAULT_ENSURERS = {
|
|||
'LC_MESSAGES': (always_false, locale_convert('LC_MESSAGES'), ensure_string),
|
||||
'LC_MONETARY': (always_false, locale_convert('LC_MONETARY'), ensure_string),
|
||||
'LC_NUMERIC': (always_false, locale_convert('LC_NUMERIC'), ensure_string),
|
||||
'LC_TIME': (always_false, locale_convert('LC_TIME'), ensure_string),
|
||||
'LC_TIME': (always_false, locale_convert('LC_TIME'), ensure_string),
|
||||
'LOADED_CONFIG': (is_bool, to_bool, bool_to_str),
|
||||
'LOADED_RC_FILES': (is_bool_seq, csv_to_bool_seq, bool_seq_to_csv),
|
||||
'MOUSE_SUPPORT': (is_bool, to_bool, bool_to_str),
|
||||
re.compile('\w*PATH$'): (is_env_path, str_to_env_path, env_path_to_str),
|
||||
'PATHEXT': (is_env_path, str_to_env_path, env_path_to_str),
|
||||
|
@ -157,6 +163,8 @@ DEFAULT_VALUES = {
|
|||
'LC_TIME': locale.setlocale(locale.LC_TIME),
|
||||
'LC_MONETARY': locale.setlocale(locale.LC_MONETARY),
|
||||
'LC_NUMERIC': locale.setlocale(locale.LC_NUMERIC),
|
||||
'LOADED_CONFIG': False,
|
||||
'LOADED_RC_FILES': (),
|
||||
'MOUSE_SUPPORT': False,
|
||||
'MULTILINE_PROMPT': '.',
|
||||
'PATH': (),
|
||||
|
@ -199,6 +207,240 @@ class DefaultNotGivenType(object):
|
|||
|
||||
DefaultNotGiven = DefaultNotGivenType()
|
||||
|
||||
VarDocs = namedtuple('VarDocs', ['docstr', 'configurable', 'default'])
|
||||
VarDocs.__doc__ = """Named tuple for environment variable documentation
|
||||
|
||||
Parameters
|
||||
----------
|
||||
docstr : str
|
||||
The environment variable docstring.
|
||||
configurable : bool, optional
|
||||
Flag for whether the environment variable is configurable or not.
|
||||
default : str, optional
|
||||
Custom docstring for the default value for complex defaults.
|
||||
Is this is DefaultNotGiven, then the default will be looked up
|
||||
from DEFAULT_VALUES and converted to a str.
|
||||
"""
|
||||
VarDocs.__new__.__defaults__ = (True, DefaultNotGiven) # iterates from back
|
||||
|
||||
# Please keep the following in alphabetic order - scopatz
|
||||
DEFAULT_DOCS = {
|
||||
'ANSICON': VarDocs('This is used on Windows to set the title, '
|
||||
'if available.', configurable=ON_WINDOWS),
|
||||
'AUTO_CD': VarDocs(
|
||||
'Flag to enable changing to a directory by entering the dirname or '
|
||||
'full path only (without the cd command).'),
|
||||
'AUTO_PUSHD': VarDocs(
|
||||
'Flag for automatically pushing directories onto the directory stack.'
|
||||
),
|
||||
'AUTO_SUGGEST': VarDocs(
|
||||
'Enable automatic command suggestions based on history, like in fish '
|
||||
'shell.\n\nPressing the right arrow key inserts the currently '
|
||||
'displayed suggestion. Only usable with $SHELL_TYPE=prompt_toolkit.'),
|
||||
'BASH_COMPLETIONS': VarDocs(
|
||||
'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.', default=(
|
||||
"Normally this is:\n\n"
|
||||
" ('/etc/bash_completion',\n"
|
||||
" '/usr/share/bash-completion/completions/git')\n\n"
|
||||
"But, on Mac it is:\n\n"
|
||||
" ('/usr/local/etc/bash_completion',\n"
|
||||
" '/opt/local/etc/profile.d/bash_completion.sh')\n\n"
|
||||
"And on Arch Linux it is:\n\n"
|
||||
" ('/usr/share/bash-completion/bash_completion',\n"
|
||||
" '/usr/share/bash-completion/completions/git')\n\n"
|
||||
"Other OS-specific defaults may be added in the future.")),
|
||||
'CASE_SENSITIVE_COMPLETIONS': VarDocs(
|
||||
'Sets whether completions should be case sensitive or case '
|
||||
'insensitive.', default='True on Linux, False otherwise.'),
|
||||
'CDPATH': VarDocs(
|
||||
'A list of paths to be used as roots for a cd, breaking compatibility '
|
||||
'with Bash, xonsh always prefer an existing relative path.'),
|
||||
'COMPLETIONS_DISPLAY': VarDocs(
|
||||
'Configure if and how Python completions are displayed by the '
|
||||
'prompt_toolkit shell.\n\nThis option does not affect Bash '
|
||||
'completions, auto-suggestions, etc.\n\nChanging it at runtime will '
|
||||
'take immediate effect, so you can quickly disable and enable '
|
||||
'completions during shell sessions.\n\n'
|
||||
"- If $COMPLETIONS_DISPLAY is 'none' or 'false', do not display\n"
|
||||
" those completions.\n"
|
||||
"- If $COMPLETIONS_DISPLAY is 'single', display completions in a\n"
|
||||
' single column while typing.\n'
|
||||
"- If $COMPLETIONS_DISPLAY is 'multi' or 'true', display completions\n"
|
||||
" in multiple columns while typing.\n\n"
|
||||
'These option values are not case- or type-sensitive, so e.g.'
|
||||
"writing \"$COMPLETIONS_DISPLAY = None\" and \"$COMPLETIONS_DISPLAY "
|
||||
"= 'none'\" are equivalent. Only usable with "
|
||||
"$SHELL_TYPE=prompt_toolkit"),
|
||||
'COMPLETIONS_MENU_ROWS': VarDocs(
|
||||
'Number of rows to reserve for tab-completions menu if '
|
||||
"$COMPLETIONS_DISPLAY is 'single' or 'multi'. This only effects the "
|
||||
'prompt-toolkit shell.'),
|
||||
'DIRSTACK_SIZE': VarDocs('Maximum size of the directory stack.'),
|
||||
'EXPAND_ENV_VARS': VarDocs(
|
||||
'Toggles whether environment variables are expanded inside of strings '
|
||||
'in subprocess mode.'),
|
||||
'FORCE_POSIX_PATHS': VarDocs(
|
||||
"Forces forward slashes ('/') on Windows systems when using auto "
|
||||
'completion if set to anything truthy.', configurable=ON_WINDOWS),
|
||||
'FORMATTER_DICT': VarDocs(
|
||||
'Dictionary containing variables to be used when formatting $PROMPT '
|
||||
"and $TITLE. See 'Customizing the Prompt' "
|
||||
'http://xonsh.org/tutorial.html#customizing-the-prompt',
|
||||
configurable=False, default='xonsh.environ.FORMATTER_DICT'),
|
||||
'HISTCONTROL': VarDocs(
|
||||
'A set of strings (comma-separated list in string form) of options '
|
||||
'that determine what commands are saved to the history list. By '
|
||||
"default all commands are saved. The option 'ignoredups' will not "
|
||||
"save the command if it matches the previous command. The option "
|
||||
"'ignoreerr' will cause any commands that fail (i.e. return non-zero "
|
||||
"exit status) to not be added to the history list."),
|
||||
'IGNOREEOF': VarDocs('Prevents Ctrl-D from exiting the shell.'),
|
||||
'INDENT': VarDocs('Indentation string for multiline input'),
|
||||
'LOADED_CONFIG': VarDocs('Whether or not the xonsh config file was loaded',
|
||||
configurable=False),
|
||||
'LOADED_RC_FILES': VarDocs(
|
||||
'Whether or not any of the xonsh run control files were loaded at '
|
||||
'startup. This is a sequence of bools in Python that is converted '
|
||||
"to a CSV list in string form, ie [True, False] becomes 'True,False'.",
|
||||
configurable=False),
|
||||
'MOUSE_SUPPORT': VarDocs(
|
||||
'Enable mouse support in the prompt_toolkit shell. This allows '
|
||||
'clicking for positioning the cursor or selecting a completion. In '
|
||||
'some terminals however, this disables the ability to scroll back '
|
||||
'through the history of the terminal. Only usable with '
|
||||
'$SHELL_TYPE=prompt_toolkit'),
|
||||
'MULTILINE_PROMPT': VarDocs(
|
||||
'Prompt text for 2nd+ lines of input, may be str or function which '
|
||||
'returns a str.'),
|
||||
'OLDPWD': VarDocs('Used to represent a previous present working directory.',
|
||||
configurable=False),
|
||||
'PATH': VarDocs(
|
||||
'List of strings representing where to look for executables.'),
|
||||
'PATHEXT': VarDocs('List of strings for filtering valid exeutables by.'),
|
||||
'PROMPT': VarDocs(
|
||||
'The prompt text. May contain keyword arguments which are '
|
||||
"auto-formatted, see 'Customizing the Prompt' at "
|
||||
'http://xonsh.org/tutorial.html#customizing-the-prompt.',
|
||||
default='xonsh.environ.DEFAULT_PROMPT'),
|
||||
'PROMPT_TOOLKIT_COLORS': VarDocs(
|
||||
'This is a mapping of from color names to HTML color codes. Whenever '
|
||||
'prompt-toolkit would color a word a particular color (in the prompt, '
|
||||
'or in syntax highlighting), it will use the value specified here to '
|
||||
'represent that color, instead of its default. If a color is not '
|
||||
'specified here, prompt-toolkit uses the colors from '
|
||||
"'xonsh.tools._PT_COLORS'.", configurable=False),
|
||||
'PROMPT_TOOLKIT_STYLES': VarDocs(
|
||||
'This is a mapping of user-specified styles for prompt-toolkit. See '
|
||||
'the prompt-toolkit documentation for more details. If None, this is '
|
||||
'skipped.', configurable=False),
|
||||
'PUSHD_MINUS': VarDocs(
|
||||
'Flag for directory pushing functionality. False is the normal '
|
||||
'behaviour.'),
|
||||
'PUSHD_SILENT': VarDocs(
|
||||
'Whether or not to suppress directory stack manipulation output.'),
|
||||
'SHELL_TYPE': VarDocs(
|
||||
'Which shell is used. Currently two base shell types are supported:\n\n'
|
||||
" - 'readline' that is backed by Python's readline module\n"
|
||||
" - 'prompt_toolkit' that uses external library of the same name\n"
|
||||
" - 'random' selects a random shell from the above on startup\n\n"
|
||||
'To use the prompt_toolkit shell you need to have prompt_toolkit '
|
||||
'(https://github.com/jonathanslenders/python-prompt-toolkit)'
|
||||
'library installed. To specify which shell should be used, do so in '
|
||||
'the run control file.', default=("'prompt_toolkit' if on Windows, "
|
||||
"and 'readline' otherwise.")),
|
||||
'SUGGEST_COMMANDS': VarDocs(
|
||||
'When a user types an invalid command, xonsh will try to offer '
|
||||
'suggestions of similar valid commands if this is True.'),
|
||||
'SUGGEST_MAX_NUM': VarDocs(
|
||||
'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': VarDocs(
|
||||
'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': VarDocs(
|
||||
'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.', configurable=ON_LINUX),
|
||||
'TERM': VarDocs(
|
||||
'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.", configurable=False),
|
||||
'TITLE': VarDocs(
|
||||
'The title text for the window in which xonsh is running. Formatted '
|
||||
"in the same manner as $PROMPT, see 'Customizing the Prompt' "
|
||||
'http://xonsh.org/tutorial.html#customizing-the-prompt.',
|
||||
default='xonsh.environ.DEFAULT_TITLE'),
|
||||
'VI_MODE': VarDocs(
|
||||
"Flag to enable 'vi_mode' in the 'prompt_toolkit' shell."),
|
||||
'XDG_CONFIG_HOME': VarDocs(
|
||||
'Open desktop standard configuration home dir. This is the same '
|
||||
'default as used in the standard.', configurable=False,
|
||||
default="'~/.config'"),
|
||||
'XDG_DATA_HOME': VarDocs(
|
||||
'Open desktop standard data home dir. This is the same default as '
|
||||
'used in the standard.', default="'~/.local/share'"),
|
||||
'XONSHCONFIG': VarDocs(
|
||||
'The location of the static xonsh configuration file, if it exists. '
|
||||
'This is in JSON format.', configurable=False,
|
||||
default="'$XONSH_CONFIG_DIR/config.json'"),
|
||||
'XONSHRC': VarDocs(
|
||||
'A tuple of the locations of run control files, if they exist. User '
|
||||
'defined run control file will supercede values set in system-wide '
|
||||
'control file if there is a naming collision.', default=(
|
||||
"On Linux & Mac OSX: ('/etc/xonshrc', '~/.xonshrc')\n"
|
||||
"On Windows: ('%ALLUSERSPROFILE%\\xonsh\\xonshrc', '~/.xonshrc')")),
|
||||
'XONSH_CONFIG_DIR': VarDocs(
|
||||
'This is location where xonsh configuration information is stored.',
|
||||
configurable=False, default="'$XDG_CONFIG_HOME/xonsh'"),
|
||||
'XONSH_DATA_DIR': VarDocs(
|
||||
'This is the location where xonsh data files are stored, such as '
|
||||
'history.', default="'$XDG_DATA_HOME/xonsh'"),
|
||||
'XONSH_ENCODING': VarDocs(
|
||||
'This is the that xonsh should use for subrpocess operations.',
|
||||
default='sys.getdefaultencoding()'),
|
||||
'XONSH_ENCODING_ERRORS': VarDocs(
|
||||
'The flag for how to handle encoding errors should they happen. '
|
||||
'Any string flag that has been previously registered with Python '
|
||||
"is allowed. See the 'Python codecs documentation' "
|
||||
"(https://docs.python.org/3/library/codecs.html#error-handlers) "
|
||||
'for more information and available options.',
|
||||
default="'surrogateescape'"),
|
||||
'XONSH_HISTORY_FILE': VarDocs('Location of history file (deprecated).',
|
||||
configurable=False, default="'~/.xonsh_history'"),
|
||||
'XONSH_HISTORY_SIZE': VarDocs(
|
||||
'Value and units tuple that sets the size of history after garbage '
|
||||
'collection. Canonical units are:\n\n'
|
||||
"- 'commands' for the number of past commands executed,\n"
|
||||
"- 'files' for the number of history files to keep,\n"
|
||||
"- 's' for the number of seconds in the past that are allowed, and\n"
|
||||
"- 'b' for the number of bytes that history may consume.\n\n"
|
||||
"Common abbreviations, such as '6 months' or '1 GB' are also allowed.",
|
||||
default="(8128, 'commands') or '8128 commands'"),
|
||||
'XONSH_INTERACTIVE': VarDocs(
|
||||
'True if xonsh is running interactively, and False otherwise.',
|
||||
configurable=False),
|
||||
'XONSH_LOGIN': VarDocs(
|
||||
'True if xonsh is running as a login shell, and False otherwise.',
|
||||
configurable=False),
|
||||
'XONSH_SHOW_TRACEBACK': VarDocs(
|
||||
'Controls if a traceback is shown exceptions occur in the shell. '
|
||||
'Set to 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': VarDocs(
|
||||
'Whether or not to store the stdout and stderr streams in the '
|
||||
'history files.', configurable=False),
|
||||
}
|
||||
|
||||
#
|
||||
# actual environment
|
||||
#
|
||||
|
@ -224,6 +466,7 @@ class Env(MutableMapping):
|
|||
self._d = {}
|
||||
self.ensurers = {k: Ensurer(*v) for k, v in DEFAULT_ENSURERS.items()}
|
||||
self.defaults = DEFAULT_VALUES
|
||||
self.docs = DEFAULT_DOCS
|
||||
if len(args) == 0 and len(kwargs) == 0:
|
||||
args = (os.environ, )
|
||||
for key, val in dict(*args, **kwargs).items():
|
||||
|
@ -281,6 +524,35 @@ class Env(MutableMapping):
|
|||
self.ensurers[key] = ens
|
||||
return ens
|
||||
|
||||
def get_docs(self, key, default=VarDocs('<no documentation>')):
|
||||
"""Gets the documentation for the environment variable."""
|
||||
vd = self.docs.get(key, None)
|
||||
if vd is None:
|
||||
return default
|
||||
if vd.default is DefaultNotGiven:
|
||||
dval = pformat(self.defaults.get(key, '<default not set>'))
|
||||
vd = vd._replace(default=dval)
|
||||
self.docs[key] = vd
|
||||
return vd
|
||||
|
||||
@contextmanager
|
||||
def swap(self, other):
|
||||
"""Provides a context manager for temporarily swapping out certain
|
||||
environment variables with other values. On exit from the context
|
||||
manager, the original values are restored.
|
||||
"""
|
||||
old = {}
|
||||
for k, v in other.items():
|
||||
old[k] = self.get(k, NotImplemented)
|
||||
self[k] = v
|
||||
yield self
|
||||
for k, v in old.items():
|
||||
if v is NotImplemented:
|
||||
del self[k]
|
||||
else:
|
||||
self[k] = v
|
||||
|
||||
|
||||
#
|
||||
# Mutable mapping interface
|
||||
#
|
||||
|
@ -604,6 +876,23 @@ DEFAULT_VALUES['FORMATTER_DICT'] = dict(FORMATTER_DICT)
|
|||
|
||||
_FORMATTER = string.Formatter()
|
||||
|
||||
|
||||
def is_template_string(template, formatter_dict=None):
|
||||
"""Returns whether or not the string is a valid template."""
|
||||
template = template() if callable(template) else template
|
||||
try:
|
||||
included_names = set(i[1] for i in _FORMATTER.parse(template))
|
||||
except ValueError:
|
||||
return False
|
||||
included_names.discard(None)
|
||||
if formatter_dict is None:
|
||||
fmtter = builtins.__xonsh_env__.get('FORMATTER_DICT', FORMATTER_DICT)
|
||||
else:
|
||||
fmtter = formatter_dict
|
||||
known_names = set(fmtter.keys())
|
||||
return included_names <= known_names
|
||||
|
||||
|
||||
def format_prompt(template=DEFAULT_PROMPT, formatter_dict=None):
|
||||
"""Formats a xonsh prompt template string."""
|
||||
template = template() if callable(template) else template
|
||||
|
@ -653,34 +942,41 @@ BASE_ENV = {
|
|||
'XONSH_VERSION': XONSH_VERSION,
|
||||
}
|
||||
|
||||
def load_static_config(ctx):
|
||||
def load_static_config(ctx, config=None):
|
||||
"""Loads a static configuration file from a given context, rather than the
|
||||
current environment.
|
||||
current environment. Optionally may pass in configuration file name.
|
||||
"""
|
||||
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 config is not None:
|
||||
env['XONSHCONFIG'] = ctx['XONSHCONFIG'] = config
|
||||
elif 'XONSHCONFIG' in ctx:
|
||||
config = env['XONSHCONFIG'] = ctx['XONSHCONFIG']
|
||||
else:
|
||||
# don't set in ctx in order to maintain default
|
||||
config = env['XONSHCONFIG'] = xonshconfig(env)
|
||||
if os.path.isfile(config):
|
||||
with open(config, 'r') as f:
|
||||
conf = json.load(f)
|
||||
ctx['LOADED_CONFIG'] = True
|
||||
else:
|
||||
conf = {}
|
||||
ctx['LOADED_CONFIG'] = False
|
||||
return conf
|
||||
|
||||
|
||||
def xonshrc_context(rcfiles=None, execer=None):
|
||||
"""Attempts to read in xonshrc file, and return the contents."""
|
||||
if (rcfiles is None or execer is None
|
||||
or sum([os.path.isfile(rcfile) for rcfile in rcfiles]) == 0):
|
||||
loaded = builtins.__xonsh_env__['LOADED_RC_FILES'] = []
|
||||
if (rcfiles is None or execer is None):
|
||||
return {}
|
||||
env = {}
|
||||
for rcfile in rcfiles:
|
||||
if not os.path.isfile(rcfile):
|
||||
loaded.append(False)
|
||||
continue
|
||||
with open(rcfile, 'r') as f:
|
||||
rc = f.read()
|
||||
|
@ -690,7 +986,9 @@ def xonshrc_context(rcfiles=None, execer=None):
|
|||
try:
|
||||
execer.filename = rcfile
|
||||
execer.exec(rc, glbs=env)
|
||||
loaded.append(True)
|
||||
except SyntaxError as err:
|
||||
loaded.append(False)
|
||||
msg = 'syntax error in xonsh run control file {0!r}: {1!s}'
|
||||
warn(msg.format(rcfile, err), RuntimeWarning)
|
||||
finally:
|
||||
|
@ -717,12 +1015,12 @@ def windows_env_fixes(ctx):
|
|||
ctx['PWD'] = _get_cwd()
|
||||
|
||||
|
||||
def default_env(env=None):
|
||||
def default_env(env=None, config=None):
|
||||
"""Constructs a default xonsh environment."""
|
||||
# in order of increasing precedence
|
||||
ctx = dict(BASE_ENV)
|
||||
ctx.update(os.environ)
|
||||
conf = load_static_config(ctx)
|
||||
conf = load_static_config(ctx, config=config)
|
||||
ctx.update(conf.get('env', ()))
|
||||
ctx.update(load_foreign_envs(shells=conf.get('foreign_shells', DEFAULT_SHELLS),
|
||||
issue_warning=False))
|
||||
|
|
|
@ -16,11 +16,8 @@ from xonsh.built_ins import load_builtins, unload_builtins
|
|||
class Execer(object):
|
||||
"""Executes xonsh code in a context."""
|
||||
|
||||
def __init__(self,
|
||||
filename='<xonsh-code>',
|
||||
debug_level=0,
|
||||
parser_args=None,
|
||||
unload=True):
|
||||
def __init__(self, filename='<xonsh-code>', debug_level=0, parser_args=None,
|
||||
unload=True, config=None):
|
||||
"""Parameters
|
||||
----------
|
||||
filename : str, optional
|
||||
|
@ -31,6 +28,8 @@ class Execer(object):
|
|||
Arguments to pass down to the parser.
|
||||
unload : bool, optional
|
||||
Whether or not to unload xonsh builtins upon deletion.
|
||||
config : str, optional
|
||||
Path to configuration file.
|
||||
"""
|
||||
parser_args = parser_args or {}
|
||||
self.parser = Parser(**parser_args)
|
||||
|
@ -38,7 +37,7 @@ class Execer(object):
|
|||
self.debug_level = debug_level
|
||||
self.unload = unload
|
||||
self.ctxtransformer = ast.CtxAwareTransformer(self.parser)
|
||||
load_builtins(execer=self)
|
||||
load_builtins(execer=self, config=config)
|
||||
|
||||
def __del__(self):
|
||||
if self.unload:
|
||||
|
|
|
@ -52,9 +52,10 @@ parser.add_argument('-l',
|
|||
parser.add_argument('--config-path',
|
||||
help='specify a custom static configuration file',
|
||||
dest='config_path',
|
||||
default=None,
|
||||
type=path_argument)
|
||||
parser.add_argument('--no-rc',
|
||||
help="Do not load the .xonshrc file",
|
||||
help="Do not load the .xonshrc files",
|
||||
dest='norc',
|
||||
action='store_true',
|
||||
default=False)
|
||||
|
@ -136,10 +137,10 @@ def premain(argv=None):
|
|||
print(version)
|
||||
exit()
|
||||
shell_kwargs = {'shell_type': args.shell_type}
|
||||
if args.config_path is None:
|
||||
shell_kwargs['config'] = args.config_path
|
||||
if args.norc:
|
||||
shell_kwargs['ctx'] = {}
|
||||
if args.config_path:
|
||||
shell_kwargs['ctx']= {'XONSHCONFIG': args.config_path}
|
||||
shell_kwargs['rc'] = ()
|
||||
setattr(sys, 'displayhook', _pprint_displayhook)
|
||||
shell = builtins.__xonsh_shell__ = Shell(**shell_kwargs)
|
||||
from xonsh import imphooks
|
||||
|
@ -182,6 +183,10 @@ def main(argv=None):
|
|||
# otherwise, enter the shell
|
||||
env['XONSH_INTERACTIVE'] = True
|
||||
ignore_sigtstp()
|
||||
if not env['LOADED_CONFIG'] and not any(env['LOADED_RC_FILES']):
|
||||
print('Could not find xonsh configuration or run control files.')
|
||||
code = '$[xonfig wizard --confirm]'
|
||||
shell.execer.exec(code, mode='single', glbs=shell.ctx)
|
||||
shell.cmdloop()
|
||||
postmain(args)
|
||||
|
||||
|
|
|
@ -38,6 +38,40 @@ class PromptToolkitShell(BaseShell):
|
|||
enable_open_in_editor=True)
|
||||
load_xonsh_bindings(self.key_bindings_manager)
|
||||
|
||||
def singleline(self, store_in_history=True, auto_suggest=None,
|
||||
enable_history_search=True, multiline=True, **kwargs):
|
||||
"""Reads a single line of input from the shell. The store_in_history
|
||||
kwarg flags whether the input should be stored in PTK's in-memory
|
||||
history.
|
||||
"""
|
||||
token_func, style_cls = self._get_prompt_tokens_and_style()
|
||||
env = builtins.__xonsh_env__
|
||||
mouse_support = env.get('MOUSE_SUPPORT')
|
||||
if store_in_history:
|
||||
history = self.history
|
||||
else:
|
||||
history = None
|
||||
enable_history_search = False
|
||||
auto_suggest = auto_suggest if env.get('AUTO_SUGGEST') else None
|
||||
completions_display = env.get('COMPLETIONS_DISPLAY')
|
||||
multicolumn = (completions_display == 'multi')
|
||||
completer = None if completions_display == 'none' else self.pt_completer
|
||||
with self.prompter:
|
||||
line = self.prompter.prompt(
|
||||
mouse_support=mouse_support,
|
||||
auto_suggest=auto_suggest,
|
||||
get_prompt_tokens=token_func,
|
||||
style=style_cls,
|
||||
completer=completer,
|
||||
lexer=PygmentsLexer(XonshLexer),
|
||||
multiline=multiline,
|
||||
history=history,
|
||||
enable_history_search=enable_history_search,
|
||||
reserve_space_for_menu=0,
|
||||
key_bindings_registry=self.key_bindings_manager.registry,
|
||||
display_completions_in_columns=multicolumn)
|
||||
return line
|
||||
|
||||
def push(self, line):
|
||||
"""Pushes a line onto the buffer and compiles the code in a way that
|
||||
enables multiline input.
|
||||
|
@ -63,33 +97,10 @@ class PromptToolkitShell(BaseShell):
|
|||
"""Enters a loop that reads and execute input from user."""
|
||||
if intro:
|
||||
print(intro)
|
||||
_auto_suggest = AutoSuggestFromHistory()
|
||||
auto_suggest = AutoSuggestFromHistory()
|
||||
while not builtins.__xonsh_exit__:
|
||||
try:
|
||||
token_func, style_cls = self._get_prompt_tokens_and_style()
|
||||
env = builtins.__xonsh_env__
|
||||
mouse_support = env.get('MOUSE_SUPPORT')
|
||||
if env.get('AUTO_SUGGEST'):
|
||||
auto_suggest = _auto_suggest
|
||||
else:
|
||||
auto_suggest = None
|
||||
completions_display = env.get('COMPLETIONS_DISPLAY')
|
||||
multicolumn = (completions_display == 'multi')
|
||||
completer = None if completions_display == 'none' else self.pt_completer
|
||||
with self.prompter:
|
||||
line = self.prompter.prompt(
|
||||
mouse_support=mouse_support,
|
||||
auto_suggest=auto_suggest,
|
||||
get_prompt_tokens=token_func,
|
||||
style=style_cls,
|
||||
completer=completer,
|
||||
lexer=PygmentsLexer(XonshLexer),
|
||||
history=self.history,
|
||||
multiline=True,
|
||||
enable_history_search=True,
|
||||
reserve_space_for_menu=0,
|
||||
key_bindings_registry=self.key_bindings_manager.registry,
|
||||
display_completions_in_columns=multicolumn)
|
||||
line = self.singleline(auto_suggest=auto_suggest)
|
||||
if not line:
|
||||
self.emptyline()
|
||||
else:
|
||||
|
|
|
@ -20,7 +20,7 @@ RL_DONE = None
|
|||
|
||||
|
||||
def setup_readline():
|
||||
"""Sets up the readline module and completion supression, if available."""
|
||||
"""Sets up the readline module and completion suppression, if available."""
|
||||
global RL_COMPLETION_SUPPRESS_APPEND, RL_LIB, RL_CAN_RESIZE
|
||||
if RL_COMPLETION_SUPPRESS_APPEND is not None:
|
||||
return
|
||||
|
@ -101,6 +101,22 @@ class ReadlineShell(BaseShell, Cmd):
|
|||
def __del__(self):
|
||||
teardown_readline()
|
||||
|
||||
def singleline(self, store_in_history=True, **kwargs):
|
||||
"""Reads a single line of input. The store_in_history kwarg
|
||||
flags whether the input should be stored in readline's in-memory
|
||||
history.
|
||||
"""
|
||||
if not store_in_history: # store current position to remove it later
|
||||
try:
|
||||
import readline
|
||||
except ImportError:
|
||||
store_in_history = True
|
||||
pos = readline.get_current_history_length() - 1
|
||||
rtn = input(self.prompt)
|
||||
if not store_in_history:
|
||||
readline.remove_history_item(pos)
|
||||
return rtn
|
||||
|
||||
def parseline(self, line):
|
||||
"""Overridden to no-op."""
|
||||
return '', line, line
|
||||
|
@ -198,7 +214,7 @@ class ReadlineShell(BaseShell, Cmd):
|
|||
if inserter is not None:
|
||||
readline.set_pre_input_hook(inserter)
|
||||
try:
|
||||
line = input(self.prompt)
|
||||
line = self.singleline()
|
||||
except EOFError:
|
||||
if builtins.__xonsh_env__.get("IGNOREEOF"):
|
||||
self.stdout.write('Use "exit" to leave the shell.'
|
||||
|
|
|
@ -9,6 +9,15 @@ from xonsh.environ import xonshrc_context
|
|||
from xonsh.tools import XonshError
|
||||
|
||||
|
||||
def is_readline_available():
|
||||
"""Checks if readline is available to import."""
|
||||
try:
|
||||
import readline
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
|
||||
def is_prompt_toolkit_available():
|
||||
"""Checks if prompt_toolkit is available to import."""
|
||||
try:
|
||||
|
@ -18,6 +27,12 @@ def is_prompt_toolkit_available():
|
|||
return False
|
||||
|
||||
|
||||
def prompt_toolkit_version():
|
||||
"""Gets the prompt toolkit version."""
|
||||
import prompt_toolkit
|
||||
return getattr(prompt_toolkit, '__version__', '<0.57')
|
||||
|
||||
|
||||
class Shell(object):
|
||||
"""Main xonsh shell.
|
||||
|
||||
|
@ -25,8 +40,25 @@ class Shell(object):
|
|||
readline version of shell should be used.
|
||||
"""
|
||||
|
||||
def __init__(self, ctx=None, shell_type=None, **kwargs):
|
||||
self._init_environ(ctx)
|
||||
def __init__(self, ctx=None, shell_type=None, config=None, rc=None,
|
||||
**kwargs):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
ctx : Mapping, optional
|
||||
The execution context for the shell (e.g. the globals namespace).
|
||||
If none, this is computed by loading the rc files. If not None,
|
||||
this no additional context is computed and this is used
|
||||
directly.
|
||||
shell_type : str, optional
|
||||
The shell type to start, such as 'readline', 'prompt_toolkit',
|
||||
or 'random'.
|
||||
config : str, optional
|
||||
Path to configuration file.
|
||||
rc : list of str, optional
|
||||
Sequence of paths to run control files.
|
||||
"""
|
||||
self._init_environ(ctx, config, rc)
|
||||
env = builtins.__xonsh_env__
|
||||
# pick a valid shell
|
||||
if shell_type is not None:
|
||||
|
@ -57,13 +89,13 @@ class Shell(object):
|
|||
"""Delegates calls to appropriate shell instance."""
|
||||
return getattr(self.shell, attr)
|
||||
|
||||
def _init_environ(self, ctx):
|
||||
self.execer = Execer()
|
||||
def _init_environ(self, ctx, config, rc):
|
||||
self.execer = Execer(config=config)
|
||||
env = builtins.__xonsh_env__
|
||||
if ctx is not None:
|
||||
self.ctx = ctx
|
||||
else:
|
||||
rc = env.get('XONSHRC')
|
||||
if ctx is None:
|
||||
rc = env.get('XONSHRC') if rc is None else rc
|
||||
self.ctx = xonshrc_context(rcfiles=rc, execer=self.execer)
|
||||
else:
|
||||
self.ctx = ctx
|
||||
builtins.__xonsh_ctx__ = self.ctx
|
||||
self.ctx['__name__'] = '__main__'
|
||||
|
|
|
@ -27,7 +27,7 @@ import traceback
|
|||
import threading
|
||||
import subprocess
|
||||
from contextlib import contextmanager
|
||||
from collections import OrderedDict, Sequence
|
||||
from collections import OrderedDict, Sequence, Set
|
||||
from warnings import warn
|
||||
|
||||
if sys.version_info[0] >= 3:
|
||||
|
@ -553,6 +553,15 @@ def bool_to_str(x):
|
|||
return '1' if x else ''
|
||||
|
||||
|
||||
_BREAKS = frozenset(['b', 'break', 's', 'skip', 'q', 'quit'])
|
||||
|
||||
def to_bool_or_break(x):
|
||||
if isinstance(x, string_types) and x.lower() in _BREAKS:
|
||||
return 'break'
|
||||
else:
|
||||
return to_bool(x)
|
||||
|
||||
|
||||
def ensure_int_or_slice(x):
|
||||
"""Makes sure that x is list-indexable."""
|
||||
if x is None:
|
||||
|
@ -572,7 +581,7 @@ def is_string_set(x):
|
|||
if isinstance(x, string_types):
|
||||
return False
|
||||
else:
|
||||
return (isinstance(x, set) and
|
||||
return (isinstance(x, Set) and
|
||||
all([isinstance(a, string_types) for a in x]))
|
||||
|
||||
|
||||
|
@ -589,6 +598,23 @@ def set_to_csv(x):
|
|||
return ','.join(x)
|
||||
|
||||
|
||||
def is_bool_seq(x):
|
||||
"""Tests if an object is a sequence of bools."""
|
||||
return isinstance(x, Sequence) and all(map(isinstance, x, [bool]*len(x)))
|
||||
|
||||
|
||||
def csv_to_bool_seq(x):
|
||||
"""Takes a comma-separated string and converts it into a list of bools."""
|
||||
if len(x) == 0:
|
||||
return []
|
||||
return list(map(to_bool, x.split(',')))
|
||||
|
||||
|
||||
def bool_seq_to_csv(x):
|
||||
"""Converts a sequence of bools to a comma-separated string."""
|
||||
return ','.join(map(str, x))
|
||||
|
||||
|
||||
def is_completions_display_value(x):
|
||||
return x in {'none', 'single', 'multi'}
|
||||
|
||||
|
@ -807,12 +833,29 @@ def format_prompt_for_prompt_toolkit(prompt):
|
|||
return token_names, cstyles, strings
|
||||
|
||||
|
||||
def format_color(string):
|
||||
"""Formats strings that contain xonsh.tools.TERM_COLORS values."""
|
||||
s = string.format(**TERM_COLORS).replace('\001', '').replace('\002', '')
|
||||
return s
|
||||
|
||||
|
||||
def print_color(string, file=sys.stdout):
|
||||
"""Print strings that contain xonsh.tools.TERM_COLORS values. By default
|
||||
`sys.stdout` is used as the output stream but an alternate can be specified
|
||||
by the `file` keyword argument."""
|
||||
print(string.format(**TERM_COLORS).replace('\001', '').replace('\002', ''),
|
||||
file=file)
|
||||
print(format_color(string), file=file)
|
||||
|
||||
|
||||
def escape_color(string):
|
||||
"""Escapes color formatting, ie '{RED}' becomes '{{RED}}'."""
|
||||
s = string
|
||||
for color in TERM_COLORS.keys():
|
||||
if color in s:
|
||||
bc = '{' + color + '}' # braced color
|
||||
dbc = '{' + bc + '}' # double-braced color
|
||||
s = s.replace(bc, dbc)
|
||||
return s
|
||||
|
||||
|
||||
_RE_STRING_START = "[bBrRuU]*"
|
||||
_RE_STRING_TRIPLE_DOUBLE = '"""'
|
||||
|
@ -1021,3 +1064,18 @@ def expandvars(path):
|
|||
res += c
|
||||
index += 1
|
||||
return res
|
||||
|
||||
#
|
||||
# File handling tools
|
||||
#
|
||||
|
||||
def backup_file(fname):
|
||||
"""Moves an existing file to a new name that has the current time right
|
||||
before the extension.
|
||||
"""
|
||||
# lazy imports
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
base, ext = os.path.splitext(fname)
|
||||
newfname = base + '.' + datetime.now().isoformat() + ext
|
||||
shutil.move(fname, newfname)
|
||||
|
|
614
xonsh/wizard.py
Normal file
614
xonsh/wizard.py
Normal file
|
@ -0,0 +1,614 @@
|
|||
"""Tools for creating command-line and web-based wizards from a tree of nodes.
|
||||
"""
|
||||
import os
|
||||
import ast
|
||||
import json
|
||||
import builtins
|
||||
import textwrap
|
||||
from pprint import pformat
|
||||
from collections.abc import MutableSequence, Mapping, Sequence
|
||||
|
||||
from xonsh.tools import to_bool, to_bool_or_break, backup_file, print_color
|
||||
|
||||
#
|
||||
# Nodes themselves
|
||||
#
|
||||
class Node(object):
|
||||
"""Base type of all nodes."""
|
||||
|
||||
attrs = ()
|
||||
|
||||
def __str__(self):
|
||||
return PrettyFormatter(self).visit()
|
||||
|
||||
def __repr__(self):
|
||||
return str(self).replace('\n', '')
|
||||
|
||||
|
||||
class Wizard(Node):
|
||||
"""Top-level node in the tree."""
|
||||
|
||||
attrs = ('children', 'path')
|
||||
|
||||
def __init__(self, children, path=None):
|
||||
self.children = children
|
||||
self.path = path
|
||||
|
||||
|
||||
class Pass(Node):
|
||||
"""Simple do-nothing node"""
|
||||
|
||||
|
||||
class Message(Node):
|
||||
"""Contains a simple message to report to the user."""
|
||||
|
||||
attrs = ('message')
|
||||
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
|
||||
class Question(Node):
|
||||
"""Asks a question and then chooses the next node based on the response.
|
||||
"""
|
||||
|
||||
attrs = ('question', 'responses', 'converter', 'path')
|
||||
|
||||
def __init__(self, question, responses, converter=None, path=None):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
question : str
|
||||
The question itself.
|
||||
responses : dict with str keys and Node values
|
||||
Mapping from user-input responses to nodes.
|
||||
converter : callable, optional
|
||||
Converts the string the user typed into another object
|
||||
that serves as a key to the reponses dict.
|
||||
path : str or sequence of str, optional
|
||||
A path within the storage object.
|
||||
"""
|
||||
self.question = question
|
||||
self.responses = responses
|
||||
self.converter = converter
|
||||
self.path = path
|
||||
|
||||
|
||||
class Input(Node):
|
||||
"""Gets input from the user."""
|
||||
|
||||
attrs = ('prompt', 'converter', 'show_conversion', 'confirm', 'path')
|
||||
|
||||
def __init__(self, prompt='>>> ', converter=None, show_conversion=False,
|
||||
confirm=False, path=None):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
prompt : str, optional
|
||||
Prompt string prior to input
|
||||
converter : callable, optional
|
||||
Converts the string the user typed into another object
|
||||
prior to storage.
|
||||
show_conversion : bool, optional
|
||||
Flag for whether or not to show the results of the conversion
|
||||
function if the conversion function was meaningfully executed.
|
||||
Default False.
|
||||
confirm : bool, optional
|
||||
Whether the input should be confirmed until true or broken,
|
||||
default False.
|
||||
path : str or sequence of str, optional
|
||||
A path within the storage object.
|
||||
"""
|
||||
self.prompt = prompt
|
||||
self.converter = converter
|
||||
self.show_conversion = show_conversion
|
||||
self.confirm = confirm
|
||||
self.path = path
|
||||
|
||||
|
||||
class While(Node):
|
||||
"""Computes a body while a condition function evaluates to true.
|
||||
The condition function has the form cond(visitor=None, node=None) and
|
||||
should return an object that is convertable to a bool. The beg attribute
|
||||
specifies the number to start the loop iteration at.
|
||||
"""
|
||||
|
||||
attrs = ('cond', 'body', 'idxname', 'beg', 'path')
|
||||
|
||||
def __init__(self, cond, body, idxname='idx', beg=0, path=None):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
cond : callable
|
||||
Function that determines if the next loop iteration should
|
||||
be executed. The condition function has the form
|
||||
cond(visitor=None, node=None) and should return an object that
|
||||
is convertable to a bool.
|
||||
body : sequence of nodes
|
||||
A list of node to execute on each iteration.
|
||||
idxname : str, optional
|
||||
The variable name for the index.
|
||||
beg : int, optional
|
||||
The first index value when evaluating path format strings.
|
||||
path : str or sequence of str, optional
|
||||
A path within the storage object.
|
||||
"""
|
||||
self.cond = cond
|
||||
self.body = body
|
||||
self.idxname = idxname
|
||||
self.beg = beg
|
||||
self.path = path
|
||||
|
||||
|
||||
#
|
||||
# Helper nodes
|
||||
#
|
||||
|
||||
class YesNo(Question):
|
||||
"""Represents a simple yes/no question."""
|
||||
|
||||
def __init__(self, question, yes, no, path=None):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
question : str
|
||||
The question itself.
|
||||
yes : Node
|
||||
Node to execute if the response is True.
|
||||
no : Node
|
||||
Node to execute if the response is False.
|
||||
path : str or sequence of str, optional
|
||||
A path within the storage object.
|
||||
"""
|
||||
responses = {True: yes, False: no}
|
||||
super().__init__(question, responses, converter=to_bool,
|
||||
path=path)
|
||||
|
||||
class TrueFalse(Input):
|
||||
"""Input node the returns a True or False value."""
|
||||
|
||||
def __init__(self, prompt='yes or no [default: no]? ', path=None):
|
||||
super().__init__(prompt=prompt, converter=to_bool,
|
||||
show_conversion=False, confirm=False, path=path)
|
||||
|
||||
|
||||
class TrueFalseBreak(Input):
|
||||
"""Input node the returns a True, False, or 'break' value."""
|
||||
|
||||
def __init__(self, prompt='yes, no, or break [default: no]? ', path=None):
|
||||
super().__init__(prompt=prompt, converter=to_bool_or_break,
|
||||
show_conversion=False, confirm=False, path=path)
|
||||
|
||||
|
||||
class StoreNonEmpty(Input):
|
||||
"""Stores the user input only if the input was not an empty string.
|
||||
This works by wrapping the converter function.
|
||||
"""
|
||||
|
||||
def __init__(self, prompt='>>> ', converter=None, show_conversion=False,
|
||||
confirm=False, path=None):
|
||||
def nonempty_converter(x):
|
||||
"""Converts non-empty values and converts empty inputs to
|
||||
Unstorable.
|
||||
"""
|
||||
if len(x) == 0:
|
||||
x = Unstorable
|
||||
elif converter is None:
|
||||
pass
|
||||
else:
|
||||
x = converter(x)
|
||||
return x
|
||||
super().__init__(prompt=prompt, converter=nonempty_converter,
|
||||
show_conversion=show_conversion, confirm=confirm,
|
||||
path=path)
|
||||
|
||||
|
||||
class StateFile(Input):
|
||||
"""Node for repesenting the state as a JSON file under a default or user
|
||||
given file name. This node type is likely not useful on its own.
|
||||
"""
|
||||
|
||||
attrs = ('default_file', 'check')
|
||||
|
||||
def __init__(self, default_file=None, check=True):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
default_file : str, optional
|
||||
The default filename to save the file as.
|
||||
check : bool, optional
|
||||
Whether to print the current state and ask if it should be
|
||||
saved/loaded prior to asking for the file name and saving the
|
||||
file, default=True.
|
||||
"""
|
||||
self._df = None
|
||||
super().__init__(prompt='filename: ', converter=None,
|
||||
confirm=False, path=None)
|
||||
self.default_file = default_file
|
||||
self.check = check
|
||||
|
||||
@property
|
||||
def default_file(self):
|
||||
return self._df
|
||||
|
||||
@default_file.setter
|
||||
def default_file(self, val):
|
||||
self._df = val
|
||||
if val is None:
|
||||
self.prompt = 'filename: '
|
||||
else:
|
||||
self.prompt = 'filename [default={0!r}]: '.format(val)
|
||||
|
||||
|
||||
class Save(StateFile):
|
||||
"""Node for saving the state as a JSON file under a default or user
|
||||
given file name.
|
||||
"""
|
||||
|
||||
|
||||
class Load(StateFile):
|
||||
"""Node for loading the state as a JSON file under a default or user
|
||||
given file name.
|
||||
"""
|
||||
|
||||
|
||||
def create_truefalse_cond(prompt='yes or no [default: no]? ', path=None):
|
||||
"""This creates a basic condition function for use with nodes like While
|
||||
or other conditions. The condition function creates and visits a TrueFalse
|
||||
node and returns the result. This TrueFalse node takes the prompt and
|
||||
path that is passed in here.
|
||||
"""
|
||||
def truefalse_cond(visitor, node=None):
|
||||
"""Prompts the user for a true/false condition."""
|
||||
tf = TrueFalse(prompt=prompt, path=path)
|
||||
rtn = visitor.visit(tf)
|
||||
return rtn
|
||||
return truefalse_cond
|
||||
|
||||
|
||||
#
|
||||
# Tools for trees of nodes.
|
||||
#
|
||||
|
||||
_lowername = lambda cls: cls.__name__.lower()
|
||||
|
||||
class Visitor(object):
|
||||
"""Super-class for all classes that should walk over a tree of nodes.
|
||||
This implements the visit() method.
|
||||
"""
|
||||
|
||||
def __init__(self, tree=None):
|
||||
self.tree = tree
|
||||
|
||||
def visit(self, node=None):
|
||||
"""Walks over a node. If no node is provided, the tree is used."""
|
||||
if node is None:
|
||||
node = self.tree
|
||||
if node is None:
|
||||
raise RuntimeError('no node or tree given!')
|
||||
for clsname in map(_lowername, type.mro(node.__class__)):
|
||||
meth = getattr(self, 'visit_' + clsname, None)
|
||||
if callable(meth):
|
||||
rtn = meth(node)
|
||||
break
|
||||
else:
|
||||
msg = 'could not find valid visitor method for {0} on {1}'
|
||||
nodename = node.__class__.__name__
|
||||
selfname = self.__class__.__name__
|
||||
raise AttributeError(msg.format(nodename, selfname))
|
||||
return rtn
|
||||
|
||||
|
||||
class PrettyFormatter(Visitor):
|
||||
"""Formats a tree of nodes into a pretty string"""
|
||||
|
||||
def __init__(self, tree=None, indent=' '):
|
||||
super().__init__(tree=tree)
|
||||
self.level = 0
|
||||
self.indent = indent
|
||||
|
||||
def visit_node(self, node):
|
||||
s = node.__class__.__name__ + '('
|
||||
if len(node.attrs) == 0:
|
||||
return s + ')'
|
||||
s += '\n'
|
||||
self.level += 1
|
||||
t = []
|
||||
for aname in node.attrs:
|
||||
a = getattr(node, aname)
|
||||
t.append(self.visit(a) if isinstance(a, Node) else pformat(a))
|
||||
t = ['{0}={1}'.format(n, x) for n, x in zip(node.attrs, t)]
|
||||
s += textwrap.indent(',\n'.join(t), self.indent)
|
||||
self.level -= 1
|
||||
s += '\n)'
|
||||
return s
|
||||
|
||||
def visit_wizard(self, node):
|
||||
s = 'Wizard(children=['
|
||||
if len(node.children) == 0:
|
||||
if node.path is None:
|
||||
return s + '])'
|
||||
else:
|
||||
return s + '], path={0!r})'.format(node.path)
|
||||
s += '\n'
|
||||
self.level += 1
|
||||
s += textwrap.indent(',\n'.join(map(self.visit, node.children)),
|
||||
self.indent)
|
||||
self.level -= 1
|
||||
if node.path is None:
|
||||
s += '\n])'
|
||||
else:
|
||||
s += '{0}],\n{0}path={1!r}\n)'.format(self.indent, node.path)
|
||||
return s
|
||||
|
||||
def visit_message(self, node):
|
||||
return 'Message({0!r})'.format(node.message)
|
||||
|
||||
def visit_question(self, node):
|
||||
s = node.__class__.__name__ + '(\n'
|
||||
self.level += 1
|
||||
s += self.indent + 'question={0!r},\n'.format(node.question)
|
||||
s += self.indent + 'responses={'
|
||||
if len(node.responses) == 0:
|
||||
s += '}'
|
||||
else:
|
||||
s += '\n'
|
||||
t = sorted(node.responses.items())
|
||||
t = ['{0!r}: {1}'.format(k, self.visit(v)) for k, v in t]
|
||||
s += textwrap.indent(',\n'.join(t), 2*self.indent)
|
||||
s += '\n' + self.indent + '}'
|
||||
if node.converter is not None:
|
||||
s += ',\n' + self.indent + 'converter={0!r}'.format(node.converter)
|
||||
if node.path is not None:
|
||||
s += ',\n' + self.indent + 'path={0!r}'.format(node.path)
|
||||
self.level -= 1
|
||||
s += '\n)'
|
||||
return s
|
||||
|
||||
def visit_input(self, node):
|
||||
s = '{0}(prompt={1!r}'.format(node.__class__.__name__, node.prompt)
|
||||
if node.converter is None and node.path is None:
|
||||
return s + '\n)'
|
||||
if node.converter is not None:
|
||||
s += ',\n' + self.indent + 'converter={0!r}'.format(node.converter)
|
||||
s += ',\n' + self.indent + 'show_conversion={0!r}'.format(node.show_conversion)
|
||||
if node.path is not None:
|
||||
s += ',\n' + self.indent + 'path={0!r}'.format(node.path)
|
||||
s += '\n)'
|
||||
return s
|
||||
|
||||
def visit_statefile(self, node):
|
||||
s = '{0}(default_file={1!r}, check={2})'
|
||||
s = s.format(node.__class__.__name__, node.default_file, node.check)
|
||||
return s
|
||||
|
||||
def visit_while(self, node):
|
||||
s = '{0}(cond={1!r}'.format(node.__class__.__name__, node.cond)
|
||||
s += ',\n' + self.indent + 'body=['
|
||||
if len(node.body) > 0:
|
||||
s += '\n'
|
||||
self.level += 1
|
||||
s += textwrap.indent(',\n'.join(map(self.visit, node.body)),
|
||||
self.indent)
|
||||
self.level -= 1
|
||||
s += '\n' + self.indent
|
||||
s += ']'
|
||||
s += ',\n' + self.indent + 'idxname={0!r}'.format(node.idxname)
|
||||
s += ',\n' + self.indent + 'beg={0!r}'.format(node.beg)
|
||||
if node.path is not None:
|
||||
s += ',\n' + self.indent + 'path={0!r}'.format(node.path)
|
||||
s += '\n)'
|
||||
return s
|
||||
|
||||
|
||||
def ensure_str_or_int(x):
|
||||
"""Creates a string or int."""
|
||||
if isinstance(x, int):
|
||||
return x
|
||||
x = x if isinstance(x, str) else str(x)
|
||||
try:
|
||||
x = ast.literal_eval(x)
|
||||
except (ValueError, SyntaxError):
|
||||
pass
|
||||
if not isinstance(x, (int, str)):
|
||||
msg = '{0!r} could not be converted to int or str'.format(x)
|
||||
raise ValueError(msg)
|
||||
return x
|
||||
|
||||
|
||||
def canon_path(path, indices=None):
|
||||
"""Returns the canonical form of a path, which is a tuple of str or ints.
|
||||
Indices may be optionally passed in.
|
||||
"""
|
||||
if not isinstance(path, str):
|
||||
return tuple(map(ensure_str_or_int, path))
|
||||
if indices is not None:
|
||||
path = path.format(**indices)
|
||||
path = path[1:] if path.startswith('/') else path
|
||||
path = path[:-1] if path.endswith('/') else path
|
||||
if len(path) == 0:
|
||||
return ()
|
||||
return tuple(map(ensure_str_or_int, path.split('/')))
|
||||
|
||||
|
||||
class UnstorableType(object):
|
||||
"""Represents an unstorable return value for when no input was given
|
||||
or such input was skipped. Typically represented by the Unstorable
|
||||
singleton.
|
||||
"""
|
||||
|
||||
_inst = None
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls._inst is None:
|
||||
cls._inst = super(UnstorableType, cls).__new__(cls, *args,
|
||||
**kwargs)
|
||||
return cls._inst
|
||||
|
||||
|
||||
Unstorable = UnstorableType()
|
||||
|
||||
|
||||
class StateVisitor(Visitor):
|
||||
"""This class visits the nodes and stores the results in a top-level
|
||||
dict of data according to the state path of the node. The the node
|
||||
does not have a path or the path does not exist, the storage is skipped.
|
||||
This class can be optionally initialized with an existing state.
|
||||
"""
|
||||
|
||||
def __init__(self, tree=None, state=None, indices=None):
|
||||
super().__init__(tree=tree)
|
||||
self.state = {} if state is None else state
|
||||
self.indices = {} if indices is None else indices
|
||||
|
||||
def visit(self, node=None):
|
||||
if node is None:
|
||||
node = self.tree
|
||||
if node is None:
|
||||
raise RuntimeError('no node or tree given!')
|
||||
rtn = super().visit(node)
|
||||
path = getattr(node, 'path', None)
|
||||
if path is not None and rtn is not Unstorable:
|
||||
self.store(path, rtn, indices=self.indices)
|
||||
return rtn
|
||||
|
||||
def store(self, path, val, indices=None):
|
||||
"""Stores a value at the path location."""
|
||||
path = canon_path(path, indices=indices)
|
||||
loc = self.state
|
||||
for p, n in zip(path[:-1], path[1:]):
|
||||
if isinstance(p, str) and p not in loc:
|
||||
loc[p] = {} if isinstance(n, str) else []
|
||||
elif isinstance(p, int) and abs(p) + (p >= 0) > len(loc):
|
||||
i = abs(p) + (p >= 0) - len(loc)
|
||||
if isinstance(n, str):
|
||||
ex = [{} for _ in range(i)]
|
||||
else:
|
||||
ex = [[] for _ in range(i)]
|
||||
loc.extend(ex)
|
||||
loc = loc[p]
|
||||
p = path[-1]
|
||||
loc[p] = val
|
||||
|
||||
|
||||
YN = "{GREEN}yes{NO_COLOR} or {RED}no{NO_COLOR} [default: no]? "
|
||||
YNB = ('{GREEN}yes{NO_COLOR}, {RED}no{NO_COLOR}, or '
|
||||
'{YELLOW}break{NO_COLOR} [default: no]? ')
|
||||
|
||||
class PromptVisitor(StateVisitor):
|
||||
"""Visits the nodes in the tree via the a command-line prompt."""
|
||||
|
||||
def __init__(self, tree=None, state=None, **kwargs):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
tree : Node, optional
|
||||
Tree of nodes to start visitor with.
|
||||
state : dict, optional
|
||||
Initial state to begin with.
|
||||
kwargs : optional
|
||||
Options that are passed through to the prompt via the shell's
|
||||
singleline() method. See BaseShell for mor details.
|
||||
"""
|
||||
super().__init__(tree=tree, state=state)
|
||||
self.env = builtins.__xonsh_env__
|
||||
self.shell = builtins.__xonsh_shell__.shell
|
||||
self.shell_kwargs = kwargs
|
||||
|
||||
def visit_wizard(self, node):
|
||||
for child in node.children:
|
||||
self.visit(child)
|
||||
|
||||
def visit_pass(self, node):
|
||||
pass
|
||||
|
||||
def visit_message(self, node):
|
||||
print_color(node.message)
|
||||
|
||||
def visit_question(self, node):
|
||||
self.env['PROMPT'] = node.question
|
||||
r = self.shell.singleline(**self.shell_kwargs)
|
||||
if callable(node.converter):
|
||||
r = node.converter(r)
|
||||
self.visit(node.responses[r])
|
||||
return r
|
||||
|
||||
def visit_input(self, node):
|
||||
need_input = True
|
||||
while need_input:
|
||||
self.env['PROMPT'] = node.prompt
|
||||
x = self.shell.singleline(**self.shell_kwargs)
|
||||
if callable(node.converter):
|
||||
x, raw = node.converter(x), x
|
||||
if node.show_conversion and x is not Unstorable \
|
||||
and str(x) != raw:
|
||||
msg = '{{BOLD_PURPLE}}Converted{{NO_COLOR}} input {0!r} to {1!r}.'
|
||||
print_color(msg.format(raw, x))
|
||||
if node.confirm:
|
||||
msg = 'Would you like to keep the input: {0}'
|
||||
print(msg.format(pformat(x)))
|
||||
confirmer = TrueFalseBreak(prompt=YNB)
|
||||
status = self.visit(confirmer)
|
||||
if isinstance(status, str) and status == 'break':
|
||||
x = Unstorable
|
||||
break
|
||||
else:
|
||||
need_input = not status
|
||||
else:
|
||||
need_input = False
|
||||
return x
|
||||
|
||||
def visit_while(self, node):
|
||||
rtns = []
|
||||
origidx = self.indices.get(node.idxname, None)
|
||||
self.indices[node.idxname] = idx = node.beg
|
||||
while node.cond(visitor=self, node=node):
|
||||
rtn = list(map(self.visit, node.body))
|
||||
rtns.append(rtn)
|
||||
idx += 1
|
||||
self.indices[node.idxname] = idx
|
||||
if origidx is None:
|
||||
del self.indices[node.idxname]
|
||||
else:
|
||||
self.indices[node.idxname] = origidx
|
||||
return rtns
|
||||
|
||||
def visit_save(self, node):
|
||||
jstate = json.dumps(self.state, indent=1, sort_keys=True)
|
||||
if node.check:
|
||||
msg = 'The current state is:\n\n{0}\n'
|
||||
print(msg.format(textwrap.indent(jstate, ' ')))
|
||||
ap = 'Would you like to save this state, ' + YN
|
||||
asker = TrueFalse(prompt=ap)
|
||||
do_save = self.visit(asker)
|
||||
if not do_save:
|
||||
return Unstorable
|
||||
fname = self.visit_input(node)
|
||||
if fname is None or len(fname) == 0:
|
||||
fname = node.default_file
|
||||
if os.path.isfile(fname):
|
||||
backup_file(fname)
|
||||
else:
|
||||
os.makedirs(os.path.dirname(fname), exist_ok=True)
|
||||
with open(fname, 'w') as f:
|
||||
f.write(jstate)
|
||||
return fname
|
||||
|
||||
def visit_load(self, node):
|
||||
if node.check:
|
||||
ap = 'Would you like to load an existing file, ' + YN
|
||||
asker = TrueFalse(prompt=ap)
|
||||
do_load = self.visit(asker)
|
||||
if not do_load:
|
||||
return Unstorable
|
||||
fname = self.visit_input(node)
|
||||
if fname is None or len(fname) == 0:
|
||||
fname = node.default_file
|
||||
if os.path.isfile(fname):
|
||||
with open(fname, 'r') as f:
|
||||
self.state = json.load(f)
|
||||
print_color('{{GREEN}}{0!r} loaded.{{NO_COLOR}}'.format(fname))
|
||||
else:
|
||||
print_color(('{{RED}}{0!r} could not be found, '
|
||||
'continuing.{{NO_COLOR}}').format(fname))
|
||||
return fname
|
289
xonsh/xonfig.py
Normal file
289
xonsh/xonfig.py
Normal file
|
@ -0,0 +1,289 @@
|
|||
"""The xonsh configuration (xonfig) utility."""
|
||||
import os
|
||||
import ast
|
||||
import json
|
||||
import textwrap
|
||||
import builtins
|
||||
import functools
|
||||
from pprint import pformat
|
||||
from argparse import ArgumentParser
|
||||
|
||||
import ply
|
||||
|
||||
from xonsh import __version__ as XONSH_VERSION
|
||||
from xonsh import tools
|
||||
from xonsh.environ import is_template_string
|
||||
from xonsh.shell import (is_readline_available, is_prompt_toolkit_available,
|
||||
prompt_toolkit_version)
|
||||
from xonsh.wizard import (Wizard, Pass, Message, Save, Load, YesNo, Input,
|
||||
PromptVisitor, While, StoreNonEmpty, create_truefalse_cond, YN)
|
||||
|
||||
|
||||
HR = "'`-.,¸,.-*¯`-.,¸,.-*¯`-.,¸,.-*¯`-.,¸,.-*¯`-.,¸,.-*¯`-.,¸,.-*¯`-.,¸,.-*'"
|
||||
WIZARD_HEAD = """
|
||||
{{BOLD_WHITE}}Welcome to the xonsh configuration wizard!{{NO_COLOR}}
|
||||
{{YELLOW}}------------------------------------------{{NO_COLOR}}
|
||||
This will present a guided tour through setting up the xonsh static
|
||||
config file. Xonsh will automatically ask you if you want to run this
|
||||
wizard if the configuration file does not exist. However, you can
|
||||
always rerun this wizard with the xonfig command:
|
||||
|
||||
$ xonfig wizard
|
||||
|
||||
This wizard will load an existing configuration, if it is available.
|
||||
Also never fear when this wizard saves its results! It will create
|
||||
a backup of any existing configuration automatically.
|
||||
|
||||
This wizard has two main phases: foreign shell setup and environment
|
||||
variable setup. Each phase may be skipped in its entirety.
|
||||
|
||||
For the configuration to take effect, you will need to restart xonsh.
|
||||
|
||||
{hr}
|
||||
""".format(hr=HR)
|
||||
|
||||
WIZARD_FS = """
|
||||
{hr}
|
||||
|
||||
{{BOLD_WHITE}}Foreign Shell Setup{{NO_COLOR}}
|
||||
{{YELLOW}}-------------------{{NO_COLOR}}
|
||||
The xonsh shell has the ability to interface with foreign shells such
|
||||
as Bash, zsh, or fish.
|
||||
|
||||
For configuration, this means that xonsh can load the environment,
|
||||
aliases, and functions specified in the config files of these shells.
|
||||
Naturally, these shells must be available on the system to work.
|
||||
Being able to share configuration (and source) from foreign shells
|
||||
makes it easier to transition to and from xonsh.
|
||||
""".format(hr=HR)
|
||||
|
||||
WIZARD_ENV = """
|
||||
{hr}
|
||||
|
||||
{{BOLD_WHITE}}Environment Variable Setup{{NO_COLOR}}
|
||||
{{YELLOW}}--------------------------{{NO_COLOR}}
|
||||
The xonsh shell also allows you to setup environment variables from
|
||||
the static configuration file. Any variables set in this way are
|
||||
superceded by the definitions in the xonshrc or on the command line.
|
||||
Still, setting environment variables in this way can help define
|
||||
options that are global to the system or user.
|
||||
|
||||
The following lists the environment variable name, its documentation,
|
||||
the default value, and the current value. The default and current
|
||||
values are presented as pretty repr strings of their Python types.
|
||||
|
||||
{{BOLD_GREEN}}Note:{{NO_COLOR}} Simply hitting enter for any environment variable
|
||||
will accept the default value for that entry.
|
||||
|
||||
Would you like to set env vars now, """.format(hr=HR) + YN
|
||||
|
||||
WIZARD_TAIL = """
|
||||
Thanks for using the xonsh configuration wizard!"""
|
||||
|
||||
|
||||
def make_fs():
|
||||
"""Makes the foreign shell part of the wizard."""
|
||||
cond = create_truefalse_cond(prompt='Add a foreign shell, ' + YN)
|
||||
fs = While(cond=cond, body=[
|
||||
Input('shell name (e.g. bash): ', path='/foreign_shells/{idx}/shell'),
|
||||
StoreNonEmpty('interactive shell [bool, default=True]: ',
|
||||
converter=tools.to_bool,
|
||||
show_conversion=True,
|
||||
path='/foreign_shells/{idx}/interactive'),
|
||||
StoreNonEmpty('login shell [bool, default=False]: ',
|
||||
converter=tools.to_bool,
|
||||
show_conversion=True,
|
||||
path='/foreign_shells/{idx}/login'),
|
||||
StoreNonEmpty("env command [str, default='env']: ",
|
||||
path='/foreign_shells/{idx}/envcmd'),
|
||||
StoreNonEmpty("alias command [str, default='alias']: ",
|
||||
path='/foreign_shells/{idx}/aliascmd'),
|
||||
StoreNonEmpty(("extra command line arguments [list of str, "
|
||||
"default=[]]: "),
|
||||
converter=ast.literal_eval,
|
||||
show_conversion=True,
|
||||
path='/foreign_shells/{idx}/extra_args'),
|
||||
StoreNonEmpty('current environment [dict, default=None]: ',
|
||||
converter=ast.literal_eval,
|
||||
show_conversion=True,
|
||||
path='/foreign_shells/{idx}/currenv'),
|
||||
StoreNonEmpty('safely handle exceptions [bool, default=True]: ',
|
||||
converter=tools.to_bool,
|
||||
show_conversion=True,
|
||||
path='/foreign_shells/{idx}/safe'),
|
||||
StoreNonEmpty("pre-command [str, default='']: ",
|
||||
path='/foreign_shells/{idx}/prevcmd'),
|
||||
StoreNonEmpty("post-command [str, default='']: ",
|
||||
path='/foreign_shells/{idx}/postcmd'),
|
||||
StoreNonEmpty("foreign function command [str, default=None]: ",
|
||||
path='/foreign_shells/{idx}/funcscmd'),
|
||||
StoreNonEmpty("source command [str, default=None]: ",
|
||||
path='/foreign_shells/{idx}/sourcer'),
|
||||
Message(message='') # inserts a newline
|
||||
])
|
||||
return fs
|
||||
|
||||
|
||||
def _wrap_paragraphs(text, width=70, **kwargs):
|
||||
"""Wraps paragraphs instead."""
|
||||
pars = text.split('\n')
|
||||
pars = ['\n'.join(textwrap.wrap(p, width=width, **kwargs)) for p in pars]
|
||||
s = '\n'.join(pars)
|
||||
return s
|
||||
|
||||
ENVVAR_PROMPT = """
|
||||
{{BOLD_CYAN}}${name}{{NO_COLOR}}
|
||||
{docstr}
|
||||
{{RED}}default value:{{NO_COLOR}} {default}
|
||||
{{RED}}current value:{{NO_COLOR}} {current}
|
||||
{{BOLD_GREEN}}>>>{{NO_COLOR}} """
|
||||
|
||||
def make_envvar(name):
|
||||
"""Makes a StoreNonEmpty node for an environment variable."""
|
||||
env = builtins.__xonsh_env__
|
||||
vd = env.get_docs(name)
|
||||
if not vd.configurable:
|
||||
return
|
||||
default = vd.default
|
||||
if '\n' in default:
|
||||
default = '\n' + _wrap_paragraphs(default, width=69)
|
||||
curr = env.get(name)
|
||||
if tools.is_string(curr) and is_template_string(curr):
|
||||
curr = curr.replace('{', '{{').replace('}', '}}')
|
||||
curr = pformat(curr, width=69)
|
||||
if '\n' in curr:
|
||||
curr = '\n' + curr
|
||||
prompt = ENVVAR_PROMPT.format(name=name, default=default, current=curr,
|
||||
docstr=_wrap_paragraphs(vd.docstr, width=69))
|
||||
ens = env.get_ensurer(name)
|
||||
path = '/env/' + name
|
||||
node = StoreNonEmpty(prompt, converter=ens.convert, show_conversion=True,
|
||||
path=path)
|
||||
return node
|
||||
|
||||
|
||||
def make_env():
|
||||
"""Makes an environment variable wizard."""
|
||||
kids = map(make_envvar, sorted(builtins.__xonsh_env__.docs.keys()))
|
||||
kids = [k for k in kids if k is not None]
|
||||
wiz = Wizard(children=kids)
|
||||
return wiz
|
||||
|
||||
|
||||
def make_wizard(default_file=None, confirm=False):
|
||||
"""Makes a configuration wizard for xonsh config file.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
default_file : str, optional
|
||||
Default filename to save and load to. User will still be prompted.
|
||||
confirm : bool, optional
|
||||
Confirm that the main part of the wizard should be run.
|
||||
"""
|
||||
wiz = Wizard(children=[
|
||||
Message(message=WIZARD_HEAD),
|
||||
Load(default_file=default_file, check=True),
|
||||
Message(message=WIZARD_FS),
|
||||
make_fs(),
|
||||
YesNo(question=WIZARD_ENV, yes=make_env(), no=Pass()),
|
||||
Message(message='\n' + HR + '\n'),
|
||||
Save(default_file=default_file, check=True),
|
||||
Message(message=WIZARD_TAIL),
|
||||
])
|
||||
if confirm:
|
||||
q = 'Would you like to run the xonsh configuration wizard now, ' + YN
|
||||
wiz = YesNo(question=q, yes=wiz, no=Pass())
|
||||
return wiz
|
||||
|
||||
|
||||
def _wizard(ns):
|
||||
env = builtins.__xonsh_env__
|
||||
fname = env.get('XONSHCONFIG') if ns.file is None else ns.file
|
||||
wiz = make_wizard(default_file=fname, confirm=ns.confirm)
|
||||
tempenv = {'PROMPT': '', 'XONSH_STORE_STDOUT': False}
|
||||
pv = PromptVisitor(wiz, store_in_history=False, multiline=False)
|
||||
with env.swap(tempenv):
|
||||
try:
|
||||
pv.visit()
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
pass
|
||||
|
||||
|
||||
def _format_human(data):
|
||||
wcol1 = wcol2 = 0
|
||||
for key, val in data:
|
||||
wcol1 = max(wcol1, len(key))
|
||||
wcol2 = max(wcol2, len(str(val)))
|
||||
hr = '+' + ('-'*(wcol1+2)) + '+' + ('-'*(wcol2+2)) + '+\n'
|
||||
row = '| {key!s:<{wcol1}} | {val!s:<{wcol2}} |\n'
|
||||
s = hr
|
||||
for key, val in data:
|
||||
s += row.format(key=key, wcol1=wcol1, val=val, wcol2=wcol2)
|
||||
s += hr
|
||||
return s
|
||||
|
||||
|
||||
def _format_json(data):
|
||||
data = {k.replace(' ', '_'): v for k, v in data}
|
||||
s = json.dumps(data, sort_keys=True, indent=1) + '\n'
|
||||
return s
|
||||
|
||||
|
||||
def _info(ns):
|
||||
data = [
|
||||
('xonsh', XONSH_VERSION),
|
||||
('Python', '.'.join(map(str, tools.VER_FULL))),
|
||||
('PLY', ply.__version__),
|
||||
('have readline', is_readline_available()),
|
||||
('prompt toolkit', prompt_toolkit_version() if \
|
||||
is_prompt_toolkit_available() else False),
|
||||
('on posix', tools.ON_POSIX),
|
||||
('on linux', tools.ON_LINUX),
|
||||
('on arch', tools.ON_ARCH),
|
||||
('on windows', tools.ON_WINDOWS),
|
||||
('on mac', tools.ON_MAC),
|
||||
('are root user', tools.IS_ROOT),
|
||||
('default encoding', tools.DEFAULT_ENCODING),
|
||||
]
|
||||
formatter = _format_json if ns.json else _format_human
|
||||
s = formatter(data)
|
||||
return s
|
||||
|
||||
|
||||
@functools.lru_cache()
|
||||
def _create_parser():
|
||||
p = ArgumentParser(prog='xonfig',
|
||||
description='Manages xonsh configuration.')
|
||||
subp = p.add_subparsers(title='action', dest='action')
|
||||
info = subp.add_parser('info', help=('displays configuration information, '
|
||||
'default action'))
|
||||
info.add_argument('--json', action='store_true', default=False,
|
||||
help='reports results as json')
|
||||
wiz = subp.add_parser('wizard', help=('displays configuration information, '
|
||||
'default action'))
|
||||
wiz.add_argument('--file', default=None,
|
||||
help='config file location, default=$XONSHCONFIG')
|
||||
wiz.add_argument('--confirm', action='store_true', default=False,
|
||||
help='confirm that the wizard should be run.')
|
||||
return p
|
||||
|
||||
|
||||
_MAIN_ACTIONS = {
|
||||
'info': _info,
|
||||
'wizard': _wizard,
|
||||
}
|
||||
|
||||
def main(args=None):
|
||||
"""Main xonfig entry point."""
|
||||
if not args or (args[0] not in _MAIN_ACTIONS and
|
||||
args[0] not in {'-h', '--help'}):
|
||||
args.insert(0, 'info')
|
||||
parser = _create_parser()
|
||||
ns = parser.parse_args(args)
|
||||
if ns.action is None: # apply default action
|
||||
ns = parser.parse_args(['info'] + args)
|
||||
return _MAIN_ACTIONS[ns.action](ns)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Add table
Reference in a new issue