Source code for clack._helpers

"""Defines miscellaneous helper functions."""

from __future__ import annotations

import argparse
from typing import Any, Callable, List, Mapping, MutableSequence

from ._parser import monkey_patch_parser
from .types import ClackNewCommand, ClackRunner


[docs] def new_command_factory( parser: argparse.ArgumentParser, *, dest: str = "command", required: bool = True, description: str = None, **kwargs: Any, ) -> ClackNewCommand: """Returns a `new_command()` function that can be used to add subcommands. Args: parser: The argparse parser that we want to add subcommands to. dest: The attribute name that the subcommand name will be stored under inside the Namespace object. required: Will this subcommand be required or optional? description: This argument describes what the subcommand is used for. kwargs: These keyword arguments are relayed to the ``parser.add_subparsers()`` function call. """ subparsers = parser.add_subparsers( dest=dest, required=required, description=description, **kwargs ) def new_command( name: str, *, help: str, # pylint: disable=redefined-builtin **inner_kwargs: Any, ) -> argparse.ArgumentParser: result: argparse.ArgumentParser = subparsers.add_parser( name, formatter_class=parser.formatter_class, help=help, description=help, **inner_kwargs, ) monkey_patch_parser(result) return result return new_command
[docs] def register_runner_factory( mut_runner_registry: MutableSequence[ClackRunner], ) -> Callable[[ClackRunner], ClackRunner]: """Creates a decorator that can be used to register runner functions.""" def register_runner(runner: ClackRunner) -> ClackRunner: mut_runner_registry.append(runner) return runner return register_runner
[docs] def filter_cli_args( args: argparse.Namespace | Mapping[str, Any], ) -> dict[str, Any]: """Filters args produced by 'parser' passed into clack.main_factory(). Used to filter out argparse arguments which were NOT specified on the command-line. """ from . import _dynvars as dyn from ._parser import ARGPARSE_ARGUMENT_DEFAULT if not isinstance(args, argparse.Namespace): kwargs = args else: kwargs = vars(args) config_defaults = dyn.get_config_defaults() result = {} for key, value in kwargs.items(): if key in config_defaults and config_defaults[key] == value: continue if value is ARGPARSE_ARGUMENT_DEFAULT: continue result[key] = value return result
[docs] class comma_list_or_file: """Namespace class for comma list CLI arguments. These CLI arguments can either be a comma-separated list or a new-line separated file. """
[docs] @staticmethod def parse(arg: str) -> List[str]: """ Used to interpret a CLI argument that is allowed to be either a comma-separated list or a file containing values separated by newlines. Args: arg: This argument should be one of the following: [i] A comma-separated list of values. [ii] A filename corresponding to a file containing a list of newline-separated values. Returns: The list of corresponding option values. """ try: results = [] for line in open(arg, "r"): line = line.rstrip("\n") if not line or " " in line: raise RuntimeError( "This file doesn't look like it contains option" " values. Maybe this file was intended to be a" " single-value, comma-separated list (i.e. no commas)?" ) results.append(line) return results except (IOError, RuntimeError): return arg.split(",")
[docs] @staticmethod def help(msg: str) -> str: """ Updates the help message of an argparse argument which accepts either a comma-separated list or a file containing values separated by newlines. """ COMMA_LIST_OR_FILE_DESC = ( "This option can accept either a comma-separated list or a file" " which contains a list of values (separated by newlines)." ) new_msg = msg if not new_msg.endswith(" "): new_msg += " " return new_msg + COMMA_LIST_OR_FILE_DESC