feat: use ArgparserAlias for xontrib (#4441)

* feat: use ArgparserAlias for xontrib

* test: add test for xontrib-name-completer

* fix: bashisms tests should unload module after tests

* test: make assertion failure message obvious

* fix: failing tests because autovox is already loaded in some py versions
This commit is contained in:
Noorhteen Raja NJ 2021-09-03 00:47:45 +05:30 committed by GitHub
parent e25ab34e32
commit 0a4720b71a
Failed to generate hash of commit
6 changed files with 126 additions and 100 deletions

23
news/ap-xontrib.rst Normal file
View file

@ -0,0 +1,23 @@
**Added:**
* <news item>
**Changed:**
* Using ``ArgparserAlias`` to improve ``xontrib`` completions
**Deprecated:**
* <news item>
**Removed:**
* <news item>
**Fixed:**
* <news item>
**Security:**
* <news item>

View file

@ -1,9 +1,3 @@
from xonsh.parsers.completion_context import (
CommandArg,
CommandContext,
CompletionContext,
)
from xonsh.completers.xompletions import complete_xontrib
import pytest
@ -29,9 +23,32 @@ def test_xonfig(args, prefix, exp, xsh_with_aliases, monkeypatch, check_complete
assert check_completer(args, prefix=prefix) == exp
def test_xontrib():
assert complete_xontrib(
CompletionContext(
CommandContext(args=(CommandArg("xontrib"),), arg_index=1, prefix="l")
)
) == {"list", "load"}
@pytest.mark.parametrize(
"args, prefix, exp, exp_part",
[
(
"xontrib",
"l",
{"list", "load"},
None,
),
(
"xontrib load",
"",
None,
{
# the list may vary wrt the env. so testing only part of the coreutils.
"abbrevs",
"pdb",
"bashisms",
"coreutils",
},
),
],
)
def test_xontrib(args, prefix, exp, exp_part, xsh_with_aliases, check_completer):
result = check_completer(args, prefix=prefix)
if exp:
assert result == exp
if exp_part:
assert result.issuperset(exp_part), f"{result} doesn't contain {exp_part} "

View file

@ -1,5 +1,14 @@
"""Tests bashisms xontrib."""
import pytest
import sys
@pytest.fixture(name="bash_preproc", scope="module")
def _bash_preproc():
from xontrib.bashisms import bash_preproc
yield bash_preproc
del sys.modules["xontrib.bashisms"]
@pytest.mark.parametrize(
@ -33,9 +42,8 @@ import pytest
(["aa 1 2", "ab 3 4"], "echo !ab >log", "echo ab 3 4 >log"),
],
)
def test_preproc(history, inp, exp, xession):
def test_preproc(history, inp, exp, xession, bash_preproc):
"""Test the bash preprocessor."""
from xontrib.bashisms import bash_preproc
xession.history.inps = history
obs = bash_preproc(inp)

View file

@ -16,7 +16,6 @@ from xonsh.completers.commands import (
complete_end_proc_tokens,
complete_end_proc_keywords,
)
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 +35,6 @@ def default_completers():
("pip", complete_pip),
("cd", complete_cd),
("rmdir", complete_rmdir),
("xontrib", complete_xontrib),
("import", complete_import),
("bash", complete_from_bash),
("man", complete_from_man),

View file

@ -1,31 +0,0 @@
"""Provides completions for xonsh internal utilities"""
import xonsh.xontribs as xx
import xonsh.xontribs_meta as xmt
from xonsh.completers.tools import contextual_command_completer_for
from xonsh.parsers.completion_context import CommandContext
def _list_installed_xontribs():
meta = xmt.get_xontribs()
installed = []
for name in meta:
spec = xx.find_xontrib(name)
if spec is not None:
installed.append(spec.name.rsplit(".")[-1])
return installed
@contextual_command_completer_for("xontrib")
def complete_xontrib(command: CommandContext):
"""Completion for ``xontrib``."""
curix = command.arg_index
if curix == 1:
possible = {"list", "load"}
elif curix == 2 and command.args[1].value == "load":
possible = _list_installed_xontribs()
else:
raise StopIteration
return {i for i in possible if i.startswith(command.prefix)}

View file

@ -1,17 +1,18 @@
"""Tools for helping manage xontributions."""
import argparse
import functools
import importlib
import importlib.util
import json
import pkgutil
import sys
import typing as tp
from enum import IntEnum
from pathlib import Path
from xonsh.built_ins import XSH
from xonsh.cli_utils import ArgParserAlias, Arg, Annotated
from xonsh.completers.tools import RichCompletion
from xonsh.xontribs_meta import get_xontribs
from xonsh.tools import print_color, print_exception, unthreadable
from xonsh.tools import print_color, print_exception
class ExitCode(IntEnum):
@ -78,8 +79,38 @@ def update_context(name, ctx=None):
return ctx.update(modctx)
def xontribs_load(names, verbose=False):
"""Load xontribs from a list of names"""
def xontrib_names_completer(**_):
meta = get_xontribs()
spec = importlib.util.find_spec("xontrib")
for module in pkgutil.walk_packages(spec.submodule_search_locations):
xont = meta.get(module.name)
full_name = f"xontrib.{module.name}"
if xont and full_name not in sys.modules:
yield RichCompletion(
module.name, append_space=True, description=xont.description
)
def xontribs_load(
names: Annotated[
tp.Sequence[str],
Arg(nargs="+", completer=xontrib_names_completer),
] = (),
verbose: Annotated[
bool,
Arg("-v", "--verbose", action="store_true"),
] = False,
):
"""Load xontribs from a list of names
Parameters
----------
names
names of xontribs
verbose
verbose output
"""
ctx = XSH.ctx
res = ExitCode.OK
for name in names:
@ -92,16 +123,11 @@ def xontribs_load(names, verbose=False):
print_exception("Failed to load xontrib {}.".format(name))
if hasattr(update_context, "bad_imports"):
res = ExitCode.NOT_FOUND
prompt_xontrib_install(update_context.bad_imports)
del update_context.bad_imports
prompt_xontrib_install(update_context.bad_imports) # type: ignore
del update_context.bad_imports # type: ignore
return res
def _load(ns):
"""load xontribs"""
return xontribs_load(ns.names, verbose=ns.verbose)
def xontrib_installed(names: tp.Set[str]):
"""Returns list of installed xontribs."""
installed_xontribs = set()
@ -118,11 +144,11 @@ def xontrib_installed(names: tp.Set[str]):
return installed_xontribs
def xontrib_data(ns):
def xontrib_data(names=()):
"""Collects and returns the data about xontribs."""
meta = get_xontribs()
data = {}
names: tp.Set[str] = set() if not ns else set(ns.names)
names: tp.Set[str] = set(names or ())
for xo_name in meta:
if xo_name not in names:
continue
@ -148,10 +174,21 @@ def xontribs_loaded(ns=None):
return [k for k, v in xontrib_data(ns).items() if v["loaded"]]
def _list(ns):
"""Lists xontribs."""
data = xontrib_data(ns)
if ns.json:
def _list(
names: Annotated[tuple, Arg(nargs="*")] = (),
to_json: Annotated[bool, Arg("--json", action="store_true")] = False,
):
"""List xontribs, whether they are installed, and loaded.
Parameters
----------
to_json
reports results as json
names
names of xontribs
"""
data = xontrib_data(names)
if to_json:
s = json.dumps(data)
print(s)
else:
@ -172,40 +209,14 @@ def _list(ns):
print_color(s[:-1])
@functools.lru_cache()
def _create_xontrib_parser():
# parse command line args
parser = argparse.ArgumentParser(
prog="xontrib", description="Manages xonsh extensions"
)
subp = parser.add_subparsers(title="action", dest="action")
load = subp.add_parser("load", help="loads xontribs")
load.add_argument(
"-v", "--verbose", action="store_true", default=False, dest="verbose"
)
load.add_argument("names", nargs="+", default=(), help="names of xontribs")
lyst = subp.add_parser(
"list", help=("list xontribs, whether they are " "installed, and loaded.")
)
lyst.add_argument(
"--json", action="store_true", default=False, help="reports results as json"
)
lyst.add_argument("names", nargs="*", default=(), help="names of xontribs")
return parser
class XontribAlias(ArgParserAlias):
"""Manage xonsh extensions"""
def build(self):
parser = self.create_parser()
parser.add_command(xontribs_load, prog="load")
parser.add_command(_list)
return parser
_MAIN_XONTRIB_ACTIONS = {"load": _load, "list": _list}
@unthreadable
def xontribs_main(args=None, stdin=None):
"""Alias that loads xontribs"""
if not args or (
args[0] not in _MAIN_XONTRIB_ACTIONS and args[0] not in {"-h", "--help"}
):
args.insert(0, "load")
parser = _create_xontrib_parser()
ns = parser.parse_args(args)
if ns.action is None: # apply default action
ns = parser.parse_args(["load"] + args)
return _MAIN_XONTRIB_ACTIONS[ns.action](ns)
xontribs_main = XontribAlias(threadable=False)