From 45e9a0dda91642235248ea98de9e5161b62a2e10 Mon Sep 17 00:00:00 2001 From: Nathan O'Sullivan Date: Tue, 13 Jan 2026 19:08:53 +1000 Subject: [PATCH] fix: escape percent signs in argparse help text Help text containing literal % characters (e.g., "100%") caused argparse formatting errors because % is interpreted as a format specifier. Override _get_help_string in CommandHelpFormatter to escape % as %% while preserving valid %(...)s format specifiers like %(choices)s. --- src/binarylane/console/parser/help_formatter.py | 8 ++++++++ tests/integration/test_help.py | 11 +++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/binarylane/console/parser/help_formatter.py b/src/binarylane/console/parser/help_formatter.py index 39efae21..53d5a0c5 100644 --- a/src/binarylane/console/parser/help_formatter.py +++ b/src/binarylane/console/parser/help_formatter.py @@ -27,6 +27,14 @@ def _split_lines(self, text: str, width: int) -> typing.List[str]: return [text for text in text.splitlines() for text in textwrap.wrap(text, width)] + def _get_help_string(self, action: argparse.Action) -> str: + """Escape help text for argparse. + + Argparse uses %-formatting for help strings, so literal % must be escaped as %% + while %(...)s needs to be left as-is. + """ + return (super()._get_help_string(action) or "").replace("%", "%%").replace("%%(", "%(") + def add_usage( self, usage: Optional[str], diff --git a/tests/integration/test_help.py b/tests/integration/test_help.py index 7e740d2a..15a7a27e 100644 --- a/tests/integration/test_help.py +++ b/tests/integration/test_help.py @@ -37,3 +37,14 @@ def test_required_argument_help(app: App, capsys: CaptureFixture[str]) -> None: captured = capsys.readouterr() assert "\nArguments:\n" in captured.out assert "\nParameters:\n" in captured.out + + +def test_help_with_percent_in_description(app: App, capsys: CaptureFixture[str]) -> None: + # Help text containing "100%" should not cause argparse format string errors + # The % character must be escaped as %% to prevent interpretation as format specifier + with pytest.raises(SystemExit): + app.run(["server", "alert", "get", "--help"]) + + captured = capsys.readouterr() + assert "usage: bl server alert get" in captured.out + assert "100%" in captured.out