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