mirror of
https://github.com/xonsh/xonsh.git
synced 2025-03-04 08:24:40 +01:00
ArgparserAlias - xonfig2 (#4436)
* feat: use ArgparserAlias for xonfig * test: update test for xonfig completions * refactor: update docs * refactor: use function based completer * fix: remove old import * docs: * fix: merge issues * test: move xsh_with_aliases to root conftest.py * docs: * feat: set subparser's prog from func-name
This commit is contained in:
parent
31146604fa
commit
c4b54e06a1
10 changed files with 180 additions and 161 deletions
23
news/ap-xonfig.rst
Normal file
23
news/ap-xonfig.rst
Normal file
|
@ -0,0 +1,23 @@
|
|||
**Added:**
|
||||
|
||||
* <news item>
|
||||
|
||||
**Changed:**
|
||||
|
||||
* ``xonfig`` now has colored help message when ran interactively.
|
||||
|
||||
**Deprecated:**
|
||||
|
||||
* <news item>
|
||||
|
||||
**Removed:**
|
||||
|
||||
* <news item>
|
||||
|
||||
**Fixed:**
|
||||
|
||||
* <news item>
|
||||
|
||||
**Security:**
|
||||
|
||||
* <news item>
|
|
@ -1,15 +1,6 @@
|
|||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def xsh_with_aliases(xession, monkeypatch):
|
||||
from xonsh.aliases import Aliases, make_default_aliases
|
||||
|
||||
xsh = xession
|
||||
monkeypatch.setattr(xsh, "aliases", Aliases(make_default_aliases()))
|
||||
return xsh
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_completer(monkeypatch, xsh_with_aliases):
|
||||
xsh = xsh_with_aliases
|
||||
|
|
|
@ -3,31 +3,30 @@ from xonsh.parsers.completion_context import (
|
|||
CommandContext,
|
||||
CompletionContext,
|
||||
)
|
||||
from xonsh.completers.xompletions import complete_xonfig, complete_xontrib, xt
|
||||
from xonsh.completers.xompletions import complete_xontrib
|
||||
import pytest
|
||||
|
||||
|
||||
def test_xonfig():
|
||||
assert complete_xonfig(
|
||||
CompletionContext(
|
||||
CommandContext(args=(CommandArg("xonfig"),), arg_index=1, prefix="-")
|
||||
)
|
||||
) == {"-h"}
|
||||
@pytest.mark.parametrize(
|
||||
"args, prefix, exp",
|
||||
[
|
||||
(
|
||||
"xonfig",
|
||||
"-",
|
||||
{"-h", "--help"},
|
||||
),
|
||||
(
|
||||
"xonfig colors",
|
||||
"b",
|
||||
{"blue", "brown"},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_xonfig(args, prefix, exp, xsh_with_aliases, monkeypatch, check_completer):
|
||||
from xonsh import xonfig
|
||||
|
||||
|
||||
def test_xonfig_colors(monkeypatch):
|
||||
monkeypatch.setattr(xt, "color_style_names", lambda: ["blue", "brown", "other"])
|
||||
assert (
|
||||
complete_xonfig(
|
||||
CompletionContext(
|
||||
CommandContext(
|
||||
args=(CommandArg("xonfig"), CommandArg("colors")),
|
||||
arg_index=2,
|
||||
prefix="b",
|
||||
)
|
||||
)
|
||||
)
|
||||
== {"blue", "brown"}
|
||||
)
|
||||
monkeypatch.setattr(xonfig, "color_style_names", lambda: ["blue", "brown", "other"])
|
||||
assert check_completer(args, prefix=prefix) == exp
|
||||
|
||||
|
||||
def test_xontrib():
|
||||
|
|
|
@ -141,6 +141,15 @@ def xession(xonsh_builtins) -> XonshSession:
|
|||
return XSH
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def xsh_with_aliases(xession, monkeypatch):
|
||||
from xonsh.aliases import Aliases, make_default_aliases
|
||||
|
||||
xsh = xession
|
||||
monkeypatch.setattr(xsh, "aliases", Aliases(make_default_aliases()))
|
||||
return xsh
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def completion_context_parse():
|
||||
return CompletionContextParser().parse
|
||||
|
|
|
@ -13,7 +13,7 @@ import json
|
|||
import pytest # noqa F401
|
||||
|
||||
from xonsh.tools import ON_WINDOWS
|
||||
from xonsh.xonfig import XONFIG_MAIN_ACTIONS, xonfig_main
|
||||
from xonsh.xonfig import xonfig_main
|
||||
|
||||
|
||||
def test_xonfg_help(capsys, xonsh_builtins):
|
||||
|
@ -25,8 +25,15 @@ def test_xonfg_help(capsys, xonsh_builtins):
|
|||
m = pat.match(capout)
|
||||
assert m[1]
|
||||
verbs = set(v.strip().lower() for v in m[1].split(","))
|
||||
exp = set(v.lower() for v in XONFIG_MAIN_ACTIONS)
|
||||
assert verbs == exp
|
||||
assert verbs == {
|
||||
"jupyter-kernel",
|
||||
"info",
|
||||
"styles",
|
||||
"wizard",
|
||||
"web",
|
||||
"colors",
|
||||
"tutorial",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
|
|
@ -703,11 +703,12 @@ def xexec(args, stdin=None):
|
|||
)
|
||||
|
||||
|
||||
def xonfig(args, stdin=None):
|
||||
@lazyobject
|
||||
def xonfig():
|
||||
"""Runs the xonsh configuration utility."""
|
||||
from xonsh.xonfig import xonfig_main # lazy import
|
||||
|
||||
return xonfig_main(args)
|
||||
return xonfig_main
|
||||
|
||||
|
||||
@unthreadable
|
||||
|
|
|
@ -297,7 +297,10 @@ class ArgParser(ap.ArgumentParser):
|
|||
doc = get_doc(func)
|
||||
kwargs.setdefault("description", doc)
|
||||
kwargs.setdefault("help", doc)
|
||||
parser = self.commands.add_parser(kwargs.pop("prog", func.__name__), **kwargs)
|
||||
name = kwargs.pop("prog", None)
|
||||
if not name:
|
||||
name = func.__name__.lstrip("_").replace("_", "-")
|
||||
parser = self.commands.add_parser(name, **kwargs)
|
||||
add_args(parser, func, allowed_params=args)
|
||||
return parser
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ from xonsh.completers.commands import (
|
|||
complete_end_proc_tokens,
|
||||
complete_end_proc_keywords,
|
||||
)
|
||||
from xonsh.completers.xompletions import complete_xonfig, complete_xontrib
|
||||
from xonsh.completers.xompletions import complete_xontrib
|
||||
from xonsh.completers._aliases import complete_argparser_aliases
|
||||
from xonsh.completers.environment import complete_environment_vars
|
||||
|
||||
|
@ -36,7 +36,6 @@ def default_completers():
|
|||
("pip", complete_pip),
|
||||
("cd", complete_cd),
|
||||
("rmdir", complete_rmdir),
|
||||
("xonfig", complete_xonfig),
|
||||
("xontrib", complete_xontrib),
|
||||
("import", complete_import),
|
||||
("bash", complete_from_bash),
|
||||
|
|
|
@ -1,24 +1,9 @@
|
|||
"""Provides completions for xonsh internal utilities"""
|
||||
|
||||
from xonsh.parsers.completion_context import CommandContext
|
||||
import xonsh.xontribs as xx
|
||||
import xonsh.xontribs_meta as xmt
|
||||
import xonsh.tools as xt
|
||||
from xonsh.xonfig import XONFIG_MAIN_ACTIONS
|
||||
from xonsh.completers.tools import contextual_command_completer_for
|
||||
|
||||
|
||||
@contextual_command_completer_for("xonfig")
|
||||
def complete_xonfig(command: CommandContext):
|
||||
"""Completion for ``xonfig``."""
|
||||
curix = command.arg_index
|
||||
if curix == 1:
|
||||
possible = set(XONFIG_MAIN_ACTIONS.keys()) | {"-h"}
|
||||
elif curix == 2 and command.args[1].value == "colors":
|
||||
possible = set(xt.color_style_names())
|
||||
else:
|
||||
raise StopIteration
|
||||
return {i for i in possible if i.startswith(command.prefix)}
|
||||
from xonsh.parsers.completion_context import CommandContext
|
||||
|
||||
|
||||
def _list_installed_xontribs():
|
||||
|
|
214
xonsh/xonfig.py
214
xonsh/xonfig.py
|
@ -9,8 +9,6 @@ import random
|
|||
import pprint
|
||||
import tempfile
|
||||
import textwrap
|
||||
import argparse
|
||||
import functools
|
||||
import itertools
|
||||
import contextlib
|
||||
import collections
|
||||
|
@ -21,6 +19,7 @@ from xonsh.ply import ply
|
|||
import xonsh.wizard as wiz
|
||||
from xonsh import __version__ as XONSH_VERSION
|
||||
from xonsh.built_ins import XSH
|
||||
from xonsh.cli_utils import ArgParserAlias, Annotated, Arg, add_args
|
||||
from xonsh.prompt.base import is_template_string
|
||||
from xonsh.platform import (
|
||||
is_readline_available,
|
||||
|
@ -445,15 +444,25 @@ def make_xonfig_wizard(default_file=None, confirm=False, no_wizard_file=None):
|
|||
return w
|
||||
|
||||
|
||||
def _wizard(ns):
|
||||
def _wizard(
|
||||
rcfile: Annotated[str, Arg("--file")] = None,
|
||||
confirm: Annotated[bool, Arg("--confirm", action="store_true")] = False,
|
||||
):
|
||||
"""Launch configurator in terminal
|
||||
|
||||
Parameters
|
||||
-------
|
||||
rcfile
|
||||
config file location, default=$XONSHRC
|
||||
confirm
|
||||
confirm that the wizard should be run.
|
||||
"""
|
||||
env = XSH.env
|
||||
shell = XSH.shell.shell
|
||||
xonshrcs = env.get("XONSHRC", [])
|
||||
fname = xonshrcs[-1] if xonshrcs and ns.file is None else ns.file
|
||||
fname = xonshrcs[-1] if xonshrcs and rcfile is None else rcfile
|
||||
no_wiz = os.path.join(env.get("XONSH_CONFIG_DIR"), "no-wizard")
|
||||
w = make_xonfig_wizard(
|
||||
default_file=fname, confirm=ns.confirm, no_wizard_file=no_wiz
|
||||
)
|
||||
w = make_xonfig_wizard(default_file=fname, confirm=confirm, no_wizard_file=no_wiz)
|
||||
tempenv = {"PROMPT": "", "XONSH_STORE_STDOUT": False}
|
||||
pv = wiz.PromptVisitor(w, store_in_history=False, multiline=False)
|
||||
|
||||
|
@ -504,9 +513,18 @@ def _xonfig_format_json(data):
|
|||
return s
|
||||
|
||||
|
||||
def _info(ns):
|
||||
def _info(
|
||||
to_json: Annotated[bool, Arg("--json", action="store_true")] = False,
|
||||
) -> str:
|
||||
"""Displays configuration information
|
||||
|
||||
Parameters
|
||||
----------
|
||||
to_json
|
||||
reports results as json
|
||||
"""
|
||||
env = XSH.env
|
||||
data = [("xonsh", XONSH_VERSION)]
|
||||
data: tp.List[tp.Any] = [("xonsh", XONSH_VERSION)]
|
||||
hash_, date_ = githash()
|
||||
if hash_:
|
||||
data.append(("Git SHA", hash_))
|
||||
|
@ -526,7 +544,7 @@ def _info(ns):
|
|||
)
|
||||
if ON_LINUX:
|
||||
data.append(("distro", linux_distro()))
|
||||
data.append(("on wsl", bool(ON_WSL))),
|
||||
data.append(("on wsl", bool(ON_WSL)))
|
||||
if ON_WSL:
|
||||
data.append(("wsl version", 1 if ON_WSL1 else 2))
|
||||
data.extend(
|
||||
|
@ -554,16 +572,25 @@ def _info(ns):
|
|||
data.extend([("xontrib", xontribs_loaded())])
|
||||
data.extend([("RC file", XSH.rc_files)])
|
||||
|
||||
formatter = _xonfig_format_json if ns.json else _xonfig_format_human
|
||||
formatter = _xonfig_format_json if to_json else _xonfig_format_human
|
||||
s = formatter(data)
|
||||
return s
|
||||
|
||||
|
||||
def _styles(ns):
|
||||
def _styles(
|
||||
to_json: Annotated[bool, Arg("--json", action="store_true")] = False, _stdout=None
|
||||
):
|
||||
"""Prints available xonsh color styles
|
||||
|
||||
Parameters
|
||||
----------
|
||||
to_json
|
||||
reports results as json
|
||||
"""
|
||||
env = XSH.env
|
||||
curr = env.get("XONSH_COLOR_STYLE")
|
||||
styles = sorted(color_style_names())
|
||||
if ns.json:
|
||||
if to_json:
|
||||
s = json.dumps(styles, sort_keys=True, indent=1)
|
||||
print(s)
|
||||
return
|
||||
|
@ -574,7 +601,7 @@ def _styles(ns):
|
|||
else:
|
||||
lines.append(" " + style)
|
||||
s = "\n".join(lines)
|
||||
print_color(s)
|
||||
print_color(s, file=_stdout)
|
||||
|
||||
|
||||
def _str_colors(cmap, cols):
|
||||
|
@ -621,16 +648,29 @@ def _tok_colors(cmap, cols):
|
|||
return toks
|
||||
|
||||
|
||||
def _colors(args):
|
||||
def xonfig_color_completer(*_, **__):
|
||||
yield from color_style_names()
|
||||
|
||||
|
||||
def _colors(
|
||||
style: Annotated[str, Arg(nargs="?", completer=xonfig_color_completer)] = None
|
||||
):
|
||||
"""Preview color style
|
||||
|
||||
Parameters
|
||||
----------
|
||||
style
|
||||
name of the style to preview. If not given, current style name is used.
|
||||
"""
|
||||
columns, _ = shutil.get_terminal_size()
|
||||
columns -= int(ON_WINDOWS)
|
||||
columns -= int(bool(ON_WINDOWS))
|
||||
style_stash = XSH.env["XONSH_COLOR_STYLE"]
|
||||
|
||||
if args.style is not None:
|
||||
if args.style not in color_style_names():
|
||||
print("Invalid style: {}".format(args.style))
|
||||
if style is not None:
|
||||
if style not in color_style_names():
|
||||
print("Invalid style: {}".format(style))
|
||||
return
|
||||
XSH.env["XONSH_COLOR_STYLE"] = args.style
|
||||
XSH.env["XONSH_COLOR_STYLE"] = style
|
||||
|
||||
color_map = color_style()
|
||||
akey = next(iter(color_map))
|
||||
|
@ -642,20 +682,46 @@ def _colors(args):
|
|||
XSH.env["XONSH_COLOR_STYLE"] = style_stash
|
||||
|
||||
|
||||
def _tutorial(args):
|
||||
def _tutorial():
|
||||
"""Launch tutorial in browser."""
|
||||
import webbrowser
|
||||
|
||||
webbrowser.open("http://xon.sh/tutorial.html")
|
||||
|
||||
|
||||
def _web(args):
|
||||
def _web(
|
||||
_args,
|
||||
browser: Annotated[bool, Arg("--no-browser", action="store_false")] = True,
|
||||
):
|
||||
"""Launch configurator in browser.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
browser
|
||||
don't open browser
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
|
||||
subprocess.run([sys.executable, "-m", "xonsh.webconfig"] + args.orig_args[1:])
|
||||
subprocess.run([sys.executable, "-m", "xonsh.webconfig"] + _args[1:])
|
||||
|
||||
|
||||
def _jupyter_kernel(args):
|
||||
"""Make xonsh available as a Jupyter kernel."""
|
||||
def _jupyter_kernel(
|
||||
user: Annotated[bool, Arg("--user", action="store_true")] = False,
|
||||
prefix: Annotated[str, Arg("--prefix")] = None,
|
||||
root: Annotated[str, Arg("--root")] = None,
|
||||
):
|
||||
"""Generate xonsh kernel for jupyter.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
user
|
||||
Install kernel spec in user config directory.
|
||||
prefix
|
||||
Installation prefix for bin, lib, etc.
|
||||
root
|
||||
Install relative to this alternate root directory.
|
||||
"""
|
||||
try:
|
||||
from jupyter_client.kernelspec import KernelSpecManager, NoSuchKernel
|
||||
except ImportError as e:
|
||||
|
@ -663,9 +729,7 @@ def _jupyter_kernel(args):
|
|||
|
||||
ksm = KernelSpecManager()
|
||||
|
||||
root = args.root
|
||||
prefix = args.prefix if args.prefix else sys.prefix
|
||||
user = args.user
|
||||
prefix = prefix or sys.prefix
|
||||
spec = {
|
||||
"argv": [
|
||||
sys.executable,
|
||||
|
@ -719,87 +783,25 @@ def _jupyter_kernel(args):
|
|||
return 0
|
||||
|
||||
|
||||
@functools.lru_cache(1)
|
||||
def _xonfig_create_parser():
|
||||
p = argparse.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"
|
||||
)
|
||||
web = subp.add_parser("web", help="Launch configurator in browser.")
|
||||
web.add_argument(
|
||||
"--no-browser",
|
||||
action="store_false",
|
||||
dest="browser",
|
||||
default=True,
|
||||
help="don't open browser",
|
||||
)
|
||||
wiz = subp.add_parser("wizard", help="Launch configurator in terminal")
|
||||
wiz.add_argument(
|
||||
"--file", default=None, help="config file location, default=$XONSHRC"
|
||||
)
|
||||
wiz.add_argument(
|
||||
"--confirm",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="confirm that the wizard should be run.",
|
||||
)
|
||||
sty = subp.add_parser("styles", help="prints available xonsh color styles")
|
||||
sty.add_argument(
|
||||
"--json", action="store_true", default=False, help="reports results as json"
|
||||
)
|
||||
colors = subp.add_parser("colors", help="preview color style")
|
||||
colors.add_argument(
|
||||
"style", nargs="?", default=None, help="style to preview, default: <current>"
|
||||
)
|
||||
subp.add_parser("tutorial", help="Launch tutorial in browser.")
|
||||
kern = subp.add_parser("jupyter-kernel", help="Generate xonsh kernel for jupyter.")
|
||||
kern.add_argument(
|
||||
"--user",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Install kernel spec in user config directory.",
|
||||
)
|
||||
kern.add_argument(
|
||||
"--root",
|
||||
default=None,
|
||||
help="Install relative to this alternate root directory.",
|
||||
)
|
||||
kern.add_argument(
|
||||
"--prefix", default=None, help="Installation prefix for bin, lib, etc."
|
||||
)
|
||||
class XonfigAlias(ArgParserAlias):
|
||||
"""Manage xonsh configuration."""
|
||||
|
||||
return p
|
||||
def build(self):
|
||||
parser = self.create_parser(prog="xonfig")
|
||||
# register as default action
|
||||
add_args(parser, _info, allowed_params=())
|
||||
parser.add_command(_info)
|
||||
parser.add_command(_web)
|
||||
parser.add_command(_wizard)
|
||||
parser.add_command(_styles)
|
||||
parser.add_command(_colors)
|
||||
parser.add_command(_tutorial)
|
||||
parser.add_command(_jupyter_kernel)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
XONFIG_MAIN_ACTIONS = {
|
||||
"info": _info,
|
||||
"web": _web,
|
||||
"wizard": _wizard,
|
||||
"styles": _styles,
|
||||
"colors": _colors,
|
||||
"tutorial": _tutorial,
|
||||
"jupyter-kernel": _jupyter_kernel,
|
||||
}
|
||||
|
||||
|
||||
def xonfig_main(args=None):
|
||||
"""Main xonfig entry point."""
|
||||
if not args or (
|
||||
args[0] not in XONFIG_MAIN_ACTIONS and args[0] not in {"-h", "--help"}
|
||||
):
|
||||
args.insert(0, "info")
|
||||
parser = _xonfig_create_parser()
|
||||
ns = parser.parse_args(args)
|
||||
ns.orig_args = args
|
||||
if ns.action is None: # apply default action
|
||||
ns = parser.parse_args(["info"] + args)
|
||||
return XONFIG_MAIN_ACTIONS[ns.action](ns)
|
||||
xonfig_main = XonfigAlias()
|
||||
|
||||
|
||||
@lazyobject
|
||||
|
|
Loading…
Add table
Reference in a new issue