Source code for src.hformatter

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Provides utility classes to format help text for command-line arguments.

:module: hformatter
:author: Le Bars, Yoann
"""

# Defines the public names of this module.
__all__ = ['InvalidCommandLine', 'HelpFormatter',
           'PersonalisedArgumentParser']


from typing import Iterable
import argparse
from sys import stderr
from functools import partial

# Title for usage section.
USAGE_STRING: str = 'Usage: '
# Title for default value description.
DEFAULT_STRING: str = 'default'


[docs] class InvalidCommandLine(Exception): """ Exception raised when the command line is invalid. """ def __init__(self, message: str) -> None: """ Class constructor. :param str message: Error message. """ super().__init__(message)
[docs] class HelpFormatter(argparse.ArgumentDefaultsHelpFormatter): """ Class to format help output. :ivar str _usage_string: Title for usage section. :ivar str _default_string: Title for default value description. """ _usage_string: str _default_string: str def __init__(self, prog: str, indent_increment: int = 2, max_help_position: int = 24, # pylint: disable=too-many-arguments, too-many-positional-arguments width: int | None = None, usage_str: str | None = None, default_str: str | None = None) -> None: """ Class constructor. :param str prog: Program name. :param int indent_increment: Number of spaces for each indentation level. :param int max_help_position: Maximum text indent. :param int width: Line width in help message. :param str usage_str: Title for usage section. :param str default_str: Title for default value description. """ self._usage_string = usage_str if usage_str is not None else USAGE_STRING self._default_string = default_str if default_str is not None else DEFAULT_STRING super().__init__(prog, indent_increment, max_help_position, width) def _get_help_string(self, action: argparse.Action) -> str | None: """ Create the help message for a given action. :param argparse.Action action: Action descriptor. :returns: The formatted help string for the action. """ # Let the parent class do its default formatting first. help_string = super()._get_help_string(action) # If the parent class added a default value placeholder, replace the standard # "default" string with our custom one. if help_string and '%(default)s' in help_string: help_string = help_string.replace('(default: %(default)s)', f'({self._default_string}: %(default)s)') return help_string
[docs] def add_usage(self, usage: str, actions: Iterable[argparse.Action], groups: Iterable[argparse._ArgumentGroup], prefix: str | None = None) -> None: """ Reformat usage message. :param str usage: Program command line description. :param Iterable[argparse.Action] actions: Action identifier. :param Iterable[argparse._ArgumentGroup] groups: Groups identifier. :param str prefix: Prefix to usage explanation. """ # To robustly handle all cases, including locale-specific prefixes from # argparse, we prepend our custom string to the generated usage message # instead of trying to replace the prefix. # The parent method is called with prefix='' to prevent duplication. # We pass `usage=None` to let the parent class generate the usage string # from the actions, and we provide our custom prefix. super().add_usage(None, actions, groups, prefix=self._usage_string)
[docs] class PersonalisedArgumentParser(argparse.ArgumentParser): """ A personalised argument parser that raises an exception on error instead of exiting. This overrides the default `error` method to raise an `InvalidCommandLine` exception, which allows the main application to catch parsing errors and exit gracefully with a specific error code. """
[docs] def error(self, message: str) -> None: """ Handles a parsing error by printing the usage and raising an exception. :param str message: Error message. :raises InvalidCommandLine: If the command line is invalid. """ self.print_usage(stderr) raise InvalidCommandLine(message)
[docs] @classmethod def create(cls, program_description: str, positional_name: str, optional_name: str, # pylint: disable=too-many-arguments, too-many-positional-arguments program_version: str, version_message: str, help_message: str, usage_message: str = USAGE_STRING, default_message: str = DEFAULT_STRING) -> 'PersonalisedArgumentParser': """ Factory method to create a personalised `ArgumentParser` with custom formatting and error handling. :param str program_description: String describing the aims of the program. :param str positional_name: String for positional arguments title. :param str optional_name: String for optional arguments title. :param str program_version: String describing program version. :param str version_message: Help text for the --version option. :param str help_message: String describing the help program option. :param str usage_message: Title for usage section. Defaults to “Usage: ”. :param str default_message: Title for default value description. Defaults to “default”. :returns: An initialised argument parser. """ formatter = partial( HelpFormatter, usage_str=usage_message, default_str=default_message) parser = cls( add_help=False, formatter_class=formatter, description=program_description) parser._positionals.title = positional_name parser._optionals.title = optional_name parser.add_argument('-V', '--version', action='version', version=f'%(prog)s {program_version}', help=version_message) parser.add_argument('-h', '--help', action='help', default=argparse.SUPPRESS, help=help_message) return parser