This commit is contained in:
Anthony Scopatz 2016-05-10 03:56:26 -04:00
parent 5aa8b09b63
commit a62fe6a0cb
3 changed files with 99 additions and 26 deletions

View file

@ -11,7 +11,7 @@ from collections.abc import Mapping, Sequence
from xonsh.tools import to_bool, to_bool_or_break, backup_file, print_color
#
# Nodes themselves
# Nodes themselves
#
class Node(object):
"""Base type of all nodes."""
@ -79,7 +79,7 @@ class Input(Node):
attrs = ('prompt', 'converter', 'show_conversion', 'confirm', 'path')
def __init__(self, prompt='>>> ', converter=None, show_conversion=False,
def __init__(self, prompt='>>> ', converter=None, show_conversion=False,
confirm=False, path=None):
"""
Parameters
@ -90,11 +90,11 @@ class Input(Node):
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
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,
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.
@ -120,9 +120,9 @@ class While(Node):
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
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.
@ -146,7 +146,7 @@ class While(Node):
class YesNo(Question):
"""Represents a simple yes/no question."""
def __init__(self, question, yes, no, path=None):
"""
Parameters
@ -176,7 +176,7 @@ 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,
super().__init__(prompt=prompt, converter=to_bool_or_break,
show_conversion=False, confirm=False, path=path)
@ -185,10 +185,10 @@ class StoreNonEmpty(Input):
This works by wrapping the converter function.
"""
def __init__(self, prompt='>>> ', converter=None, show_conversion=False,
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
"""Converts non-empty values and converts empty inputs to
Unstorable.
"""
if len(x) == 0:
@ -217,12 +217,12 @@ class StateFile(Input):
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
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,
super().__init__(prompt='filename: ', converter=None,
confirm=False, path=None)
self.default_file = default_file
self.check = check
@ -268,7 +268,7 @@ def create_truefalse_cond(prompt='yes or no [default: no]? ', path=None):
#
# Tools for trees of nodes.
#
#
_lowername = lambda cls: cls.__name__.lower()
@ -293,11 +293,11 @@ class Visitor(object):
break
else:
msg = 'could not find valid visitor method for {0} on {1}'
nodename = node.__class__.__name__
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"""
@ -332,7 +332,7 @@ class PrettyFormatter(Visitor):
return s + '], path={0!r})'.format(node.path)
s += '\n'
self.level += 1
s += textwrap.indent(',\n'.join(map(self.visit, node.children)),
s += textwrap.indent(',\n'.join(map(self.visit, node.children)),
self.indent)
self.level -= 1
if node.path is None:
@ -355,7 +355,7 @@ class PrettyFormatter(Visitor):
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 += 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)
@ -388,7 +388,7 @@ class PrettyFormatter(Visitor):
if len(node.body) > 0:
s += '\n'
self.level += 1
s += textwrap.indent(',\n'.join(map(self.visit, node.body)),
s += textwrap.indent(',\n'.join(map(self.visit, node.body)),
self.indent)
self.level -= 1
s += '\n' + self.indent
@ -441,7 +441,7 @@ class UnstorableType(object):
def __new__(cls, *args, **kwargs):
if cls._inst is None:
cls._inst = super(UnstorableType, cls).__new__(cls, *args,
cls._inst = super(UnstorableType, cls).__new__(cls, *args,
**kwargs)
return cls._inst
@ -468,6 +468,8 @@ class StateVisitor(Visitor):
raise RuntimeError('no node or tree given!')
rtn = super().visit(node)
path = getattr(node, 'path', None)
if callable(path):
path = path(visitor=self, node=node, val=rtn)
if path is not None and rtn is not Unstorable:
self.store(path, rtn, indices=self.indices)
return rtn
@ -488,6 +490,10 @@ class StateVisitor(Visitor):
loc.extend(ex)
loc = loc[p]
p = path[-1]
if isinstance(p, int) and abs(p) + (p >= 0) > len(loc):
i = abs(p) + (p >= 0) - len(loc)
ex = [None]*i
loc.extend(ex)
loc[p] = val

View file

@ -20,7 +20,8 @@ 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)
PromptVisitor, While, StoreNonEmpty, create_truefalse_cond, YN, Unstorable)
from xonsh.xontribs import xontrib_metadata, find_xontrib
HR = "'`-.,_,.-*'`-.,_,.-*'`-.,_,.-*'`-.,_,.-*'`-.,_,.-*'`-.,_,.-*'`-.,_,.-*'"
@ -82,6 +83,24 @@ will accept the default value for that entry.
WIZARD_ENV_QUESTION = "Would you like to set env vars now, " + YN
WIZARD_XONTRIB = """
{hr}
{{BOLD_WHITE}}Xontribs{{NO_COLOR}}
{{YELLOW}}--------{{NO_COLOR}}
No shell is complete without extentions, and xonsh is no exception. Xonsh
extensions are called {{BOLD_GREEN}}xontribs{{NO_COLOR}}, or xonsh contributions.
Xontribs are dynamically loadable, either by importing them directly or by
using the 'xontrib' command. However, you can also configure xonsh to load
xontribs autoumatically on startup prior to loading the run control files.
This allows the xontrib to be used immeadiately in your xonshrc files.
The following describes all xontribs that have been registered with xonsh.
These come from users, 3rd party developers, or xonsh itself!
""".format(hr=HR)
WIZARD_XONTRIB_QUESTION = "Would you like to enable xontribs now, " + YN
WIZARD_TAIL = """
Thanks for using the xonsh configuration wizard!"""
@ -171,9 +190,8 @@ def make_envvar(name):
return mnode, pnode
def make_env():
"""Makes an environment variable wizard."""
kids = map(make_envvar, sorted(builtins.__xonsh_env__.docs.keys()))
def _make_flat_wiz(kidfunc, *args):
kids = map(kidfunc, *args)
flatkids = []
for k in kids:
if k is None:
@ -183,6 +201,53 @@ def make_env():
return wiz
def make_env():
"""Makes an environment variable wizard."""
w = _make_flat_wiz(make_envvar, sorted(builtins.__xonsh_env__.docs.keys()))
return w
XONTRIB_PROMPT = '{BOLD_GREEN}Add this xontrib{NO_COLOR}, ' + YN
def _xontrib_path(visitor=None, node=None, val=None):
# need this to append only based on user-selected size
return ('xontribs', len(visitor.state.get('xontribs', ())))
def make_xontrib(xontrib, package):
"""Makes a message and StoreNonEmpty node for a xontrib."""
name = xontrib.get('name', '<unknown-xontrib-name>')
msg = '\n{BOLD_CYAN}' + name + '{NO_COLOR}\n'
if 'url' in xontrib:
msg += '{RED}url:{NO_COLOR} ' + xontrib['url'] + '\n'
if 'package' in xontrib:
msg += '{RED}package:{NO_COLOR} ' + xontrib['package'] + '\n'
if 'url' in package:
if 'url' in xontrib and package['url'] != xontrib['url']:
msg += '{RED}package-url:{NO_COLOR} ' + package['url'] + '\n'
if 'license' in package:
msg += '{RED}license:{NO_COLOR} ' + package['license'] + '\n'
desc = xontrib.get('description', '')
if not isinstance(desc, str):
desc = ''.join(desc)
msg += _wrap_paragraphs(desc, width=69)
if msg.endswith('\n'):
msg = msg[:-1]
mnode = Message(message=msg)
convert = lambda x: name if tools.to_bool(x) else Unstorable
pnode = StoreNonEmpty(XONTRIB_PROMPT, converter=convert,
path=_xontrib_path)
return mnode, pnode
def make_xontribs():
"""Makes a xontrib wizard."""
md = xontrib_metadata()
pkgs = [md['packages'].get(d.get('package', None), {}) for d in md['xontribs']]
w = _make_flat_wiz(make_xontrib, md['xontribs'], pkgs)
return w
def make_wizard(default_file=None, confirm=False):
"""Makes a configuration wizard for xonsh config file.
@ -200,6 +265,8 @@ def make_wizard(default_file=None, confirm=False):
make_fs(),
Message(message=WIZARD_ENV),
YesNo(question=WIZARD_ENV_QUESTION, yes=make_env(), no=Pass()),
Message(message=WIZARD_XONTRIB),
YesNo(question=WIZARD_XONTRIB_QUESTION, yes=make_xontribs(), no=Pass()),
Message(message='\n' + HR + '\n'),
Save(default_file=default_file, check=True),
Message(message=WIZARD_TAIL),

View file

@ -3,7 +3,7 @@
"package": "xonsh",
"url": "http://xon.sh",
"description": ["Matplotlib hooks for xonsh, including the new 'mpl' alias ",
"that dumps the current figure to the screen."]
"that displays the current figure on the screen."]
}
],
"packages": {