From 6d5754312131f5489454abb0640536fdc07b3f56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Thu, 27 Aug 2026 08:15:56 -0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix:=20keep=20apostrophes=20in?= =?UTF-8?q?=20help=20text=20intact?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The quote substitution matched any pair of single quotes as a quoted span, so help such as "don't use it's value" paired the apostrophes of don't and it's, produced a broken inline literal and failed builds run with -W. Both quote patterns now demand a non-word character before the opening quote and after the closing one, which separates a quoted span from an apostrophe inside a word while keeping 'thing' and "thing" wrapped. --- CHANGELOG.md | 2 ++ roots/test-help-apostrophe/conf.py | 8 ++++++ roots/test-help-apostrophe/index.rst | 3 +++ roots/test-help-apostrophe/parser.py | 10 +++++++ src/sphinx_argparse_cli/_logic.py | 5 ++-- tests/test_logic.py | 39 +++++++++++++++++++++++----- 6 files changed, 58 insertions(+), 9 deletions(-) create mode 100644 roots/test-help-apostrophe/conf.py create mode 100644 roots/test-help-apostrophe/index.rst create mode 100644 roots/test-help-apostrophe/parser.py diff --git a/CHANGELOG.md b/CHANGELOG.md index cb681d9..df85f2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ All notable changes to this project will be documented in this file. - Add `force_refs_lower` to enable `:ref:` links with mixed-case program names and arguments. - Fix Sphinx smart quotes rewriting `--` to an en dash in `--option` names within descriptions, epilogs, and help text. - Register flags and positional arguments as Sphinx program options so the `:option:` role links to them. +- Leave apostrophes inside words (`don't`, `it's`) alone in help text instead of turning them into broken inline + literals. ## 1.13.1 diff --git a/roots/test-help-apostrophe/conf.py b/roots/test-help-apostrophe/conf.py new file mode 100644 index 0000000..9f2a54a --- /dev/null +++ b/roots/test-help-apostrophe/conf.py @@ -0,0 +1,8 @@ +from __future__ import annotations + +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent)) +extensions = ["sphinx_argparse_cli"] +nitpicky = True diff --git a/roots/test-help-apostrophe/index.rst b/roots/test-help-apostrophe/index.rst new file mode 100644 index 0000000..708ad9c --- /dev/null +++ b/roots/test-help-apostrophe/index.rst @@ -0,0 +1,3 @@ +.. sphinx_argparse_cli:: + :module: parser + :func: make diff --git a/roots/test-help-apostrophe/parser.py b/roots/test-help-apostrophe/parser.py new file mode 100644 index 0000000..50068d2 --- /dev/null +++ b/roots/test-help-apostrophe/parser.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +from argparse import ArgumentParser + + +def make() -> ArgumentParser: + parser = ArgumentParser(prog="prog", add_help=False) + parser.add_argument("--a", help="don't use it's value") + parser.add_argument("--b", help="it's a 'thing' to see") + return parser diff --git a/src/sphinx_argparse_cli/_logic.py b/src/sphinx_argparse_cli/_logic.py index 21f3bef..c655bb7 100644 --- a/src/sphinx_argparse_cli/_logic.py +++ b/src/sphinx_argparse_cli/_logic.py @@ -411,8 +411,9 @@ def make_id(key: str) -> str: _HELP_SUBSTITUTIONS: Final[list[tuple[re.Pattern[str], str]]] = [ - (re.compile(r"[']+(.+?)[']+"), "``'\\1'``"), - (re.compile(r'["]+(.+?)["]+'), '``"\\1"``'), + # a quote glued to a word character is an apostrophe (don't, it's), not the edge of a quoted span + (re.compile(r"(? None: assert "complex first [-h]" in build_outcome.split("a-first-desc", maxsplit=1)[0] +@pytest.mark.sphinx(buildername="text", testroot="help-apostrophe") +def test_help_apostrophe(build_outcome: str, warning: StringIO) -> None: + assert ( + build_outcome + == """prog - CLI interface +******************** + + prog [--a A] [--b B] + + +prog options +============ + +* **"--a"** "A" - don't use it's value + +* **"--b"** "B" - it's a "'thing'" to see +""" + ) + assert not warning.getvalue() + + @pytest.mark.sphinx(buildername="text", testroot="suppressed-action") def test_suppressed_action(build_outcome: str) -> None: assert "--activities-since" not in build_outcome @@ -183,13 +204,17 @@ def test_suppressed_action(build_outcome: str) -> None: @pytest.mark.parametrize( ("example", "output"), [ - ("", ""), - ("{", "{"), - ('"', '"'), - ("'", "'"), - ("{a}", "``{a}``"), - ('"a"', '``"a"``'), - ("'a'", "``'a'``"), + pytest.param("", "", id="empty"), + pytest.param("{", "{", id="lone-brace"), + pytest.param('"', '"', id="lone-double-quote"), + pytest.param("'", "'", id="lone-single-quote"), + pytest.param("{a}", "``{a}``", id="braces"), + pytest.param('"a"', '``"a"``', id="double-quoted"), + pytest.param("'a'", "``'a'``", id="single-quoted"), + pytest.param("don't use it's value", "don't use it's value", id="apostrophes"), + pytest.param("it's a 'thing' to see", "it's a ``'thing'`` to see", id="apostrophe-and-quoted"), + pytest.param("'a' and 'b'", "``'a'`` and ``'b'``", id="two-quoted"), + pytest.param('say "hi" or "bye"', 'say ``"hi"`` or ``"bye"``', id="two-double-quoted"), ], ) def test_help_loader(example: str, output: str) -> None: