mirror of
https://github.com/xonsh/xonsh.git
synced 2025-03-04 08:24:40 +01:00
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:
parent
e25ab34e32
commit
0a4720b71a
6 changed files with 126 additions and 100 deletions
23
news/ap-xontrib.rst
Normal file
23
news/ap-xontrib.rst
Normal 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>
|
|
@ -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} "
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)}
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue