Source code for onyo.argparse_helpers

from __future__ import annotations

import argparse
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from typing import (
        Literal,
        Sequence,
    )


[docs] class StoreMultipleKeyValuePairs(argparse.Action): r"""Store a list of dictionaries of key-value pairs."""
[docs] def __call__(self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, key_values: list[str], option_string: str | None = None) -> None: r"""Turn a list of 'KEY=VALUE' pairs into a list of dictionaries. Each key can be defined either 1 or N times (where N is the number of dictionaries to be created). A key that is declared once will apply to all dictionaries. All keys appearing N times must appear the same number of times. If not, a message will print to standard error and the program will exit with status code 2. Parameters ---------- parser ArgumentParser object that contains this action. namespace Namespace object returned by :py:meth:`argparse.ArgumentParser.parse_args`. key_values List of strings containing key-value pairs. option_string Option string used to invoke this action. """ for kv in key_values: if "=" not in kv: parser.error(f"Invalid argument '{kv}'. Expected key-value pairs '<key>=<value>'.") pairs = [p.split('=', maxsplit=1) for p in key_values] key_lists = {k: [] for k, v in pairs} [key_lists[k].append(v) for k, v in pairs] key_counts = {k: len(v) for k, v in key_lists.items()} key_max_count = max(key_counts.values()) if any([True for k, c in key_counts.items() if 1 < c < key_max_count]): parser.error(f"All keys given multiple times must be given the same number of times:\n" f"{'\n'.join(['{}: {}'.format(k, c) for k, c in key_counts.items() if 1 < c])}") results = [] for i in range(key_max_count): d = dict() for k, values in key_lists.items(): v = values[0] if len(values) == 1 else values[i] d[k] = v results.append(d) setattr(namespace, self.dest, results)
[docs] class StoreSingleKeyValuePairs(argparse.Action): r"""Store a dictionary of key-value pairs."""
[docs] def __call__(self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, key_values: list[str], option_string: str | None = None) -> None: r"""Turn a list of 'KEY=VALUE' pairs into a dictionary. Each KEY can be defined once. If defined more than once, a message will print to standard error and the program will exit with status code 2. Parameters ---------- parser ArgumentParser object that contains this action. namespace Namespace object returned by :py:meth:`argparse.ArgumentParser.parse_args`. key_values List of strings containing key-value pairs. option_string Option string used to invoke this action. """ for kv in key_values: if "=" not in kv: parser.error(f"Invalid argument '{kv}'. Expected key-value pairs '<key>=<value>'.") pairs = [p.split('=', maxsplit=1) for p in key_values] results = dict() for k, v in pairs: if k in results: parser.error(f"Duplicate key '{k}' found.\n" f"Keys must not be given multiple times.") results[k] = v setattr(namespace, self.dest, results)
[docs] class StoreMatchOption(argparse.Action): r"""Store match statements and retain their order across multiple invocations."""
[docs] def __call__(self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, keys: list[str], option_string: str | None = None) -> None: r"""Store a list of match statements in a list. This way multiple invocations of ``--match`` won't have their lists merged together, and can be considered independently of one another (i.e. ``OR``). Parameters ---------- parser ArgumentParser object that contains this action. namespace Namespace object returned by :py:meth:`argparse.ArgumentParser.parse_args`. keys List of strings containing match statements. option_string Option string used to invoke this action. """ items = getattr(namespace, self.dest, None) items = list() if items is None else items items.append(keys) setattr(namespace, self.dest, items)
[docs] class StoreSortOption(argparse.Action): r"""Store keys-to-sort and retain their order across multiple invoking options. The results destination is hardcoded to ``sort``. This allows multiple argparse options to use this action and retain the order of arguments passed at the CLI across option flags. """
[docs] def __init__(self, option_strings: Sequence[str], dest: str | None = None, sort_direction: Literal['ascending', 'descending'] = 'ascending', **kwargs) -> None: r"""Instantiate a ``StoreSortOption`` with its sort direction. The ``dest`` attribute is ignored and is hardcoded to ``'sort'``. The ``default`` attribute is incompatible with this Action, as it's not possible to know when to use/vs discard the default across multiple options. Parameters ---------- option_strings List of option strings to associate with this action. Passed to :py:meth:`argparse.Action` dest This attribute is ignored and is hardcoded to ``'sort'``. sort_direction Sort direction. **kwargs Passed to :py:meth:`argparse.Action` Raises ------ ValueError The ``default`` attribute is used. """ if 'default' in kwargs.keys(): raise ValueError("'default' must not be used with `StoreSortOption`") self._sorting = sort_direction super().__init__(option_strings, "sort", **kwargs)
[docs] def __call__(self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, keys: list[str], option_string: str | None = None) -> None: r"""Store keys in a dictionary with the associated sort direction. Parameters ---------- parser ArgumentParser object that contains this action. namespace Namespace object returned by :py:meth:`argparse.ArgumentParser.parse_args`. keys List of strings containing keys to sort. option_string Option string used to invoke this action. """ partial_dict = {k: self._sorting for k in keys} items = getattr(namespace, self.dest, None) items = dict() if items is None else items items.update(partial_dict) setattr(namespace, self.dest, items)