Source code for gather.commands
"""Registration and dispatch to sub-commands"""
from __future__ import annotations
import argparse
import os
import subprocess
import sys
from typing import Sequence, Any, Tuple
import attrs
from commander_data.run import Runner
from .api import Wrapper, unique
@attrs.frozen
class _Argument:
args: Sequence[Any]
kwargs: Sequence[Tuple[str, Any]]
[docs]
def add_argument(*args, **kwargs):
"""
Add argument to a registered command.
See :code:`argparse.ArgumentParser.add_argument`
for a description of the argument semantics.
"""
return _Argument(args, frozenset(kwargs.items()))
def _transform(*args):
glue = Wrapper.glue(args)
return glue
[docs]
def make_command_register(collector):
"""
Return a decorator that registers a command.
Args:
collector: Collector to add commands to
Returns:
Callable that expects positional add_argument arguments,
and returns a decorator that registers the function
to the collector.
"""
def _register(*args, name=None):
a_transform = _transform(*args)
return collector.register(transform=a_transform, name=name)
return _register
[docs]
def set_parser(*, collected, parser=None):
"""
Set (or create) a parser.
The parser will dispatch to the functions collected.
The parser will configure the argument parsing according to the
function's :code:`add_argument` in the registration.
Args:
collected: Return value from :code:`Collector.collected`
parser: an argument parser
Returns:
An argument parser
"""
if parser is None:
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
commands = unique(collected)
for name, details in commands.items():
original = details.original
args = details.extra
a_subparser = subparsers.add_parser(name)
a_subparser.set_defaults(
__gather_name__=name,
__gather_command__=original,
)
for arg_details in args:
a_subparser.add_argument(*arg_details.args, **dict(arg_details.kwargs))
return parser
def run_maybe_dry(
*,
parser,
argv=sys.argv,
env=os.environ,
sp_run=subprocess.run,
is_subcommand=False,
prefix=None,
):
"""
Run commands that only take ``args``.
This runs commands that take ``args``.
Commands can assume that the following attributes
exist:
* ``run``: Run with logging, only if `--no-dry-run` is passed
* ``safe_run``: Run with logging
* ``orig_run``: Original function
"""
def error(args):
parser.print_help()
raise SystemExit(1)
argv = list(argv)
if is_subcommand:
argv[0:0] = [prefix or "base-command"]
argv[1] = argv[1].rsplit("/", 1)[-1]
if prefix is not None:
argv[1] = argv[1].removeprefix(prefix + "-")
args = parser.parse_args(argv[1:])
args.orig_run = sp_run
args.env = env
a_runner = Runner.from_args(args)
args.run, args.safe_run = a_runner.run, a_runner.safe_run
try:
command = args.__gather_command__
except AttributeError:
command = error
return command(
args=args,
)
[docs]
def run(*, parser, argv=sys.argv, env=os.environ, sp_run=subprocess.run):
"""
Parse arguments and run the command.
Pass non-default args in testing scenarios.
Args:
argv: sys.argv or something that looks like it
env: os.environ or something that looks like it
sp_run: subprocess.run or something that looks like it
Returns:
Return value from dispatched command
"""
args = parser.parse_args(argv[1:])
command = args.__gather_command__
return command(
args=args,
env=env,
run=sp_run,
)