Skip to content

Commit 735963b

Browse files
committed
Do not allow colors for parsing the CLI
Signed-off-by: Bernát Gábor <[email protected]>
1 parent 11200f7 commit 735963b

File tree

2 files changed

+30
-5
lines changed

2 files changed

+30
-5
lines changed

src/sphinx_argparse_cli/_logic.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import os
34
import re
45
import sys
56
from argparse import (
@@ -14,8 +15,10 @@
1415
_SubParsersAction,
1516
)
1617
from collections import defaultdict
18+
from contextlib import contextmanager
1719
from pathlib import Path
1820
from typing import TYPE_CHECKING, Any, ClassVar, NamedTuple, cast
21+
from unittest.mock import patch
1922

2023
from docutils.nodes import (
2124
Element,
@@ -334,6 +337,10 @@ def _mk_sub_command(self, aliases: list[str], help_msg: str, parser: ArgumentPar
334337
sub_title_prefix: str = self.options["group_sub_title_prefix"]
335338
title_prefix: str = self.options["group_title_prefix"]
336339

340+
if sys.version_info >= (3, 14):
341+
# https://github.com/python/cpython/issues/139809
342+
parser.prog = _strip_ansi_colors(parser.prog)
343+
337344
title_text = self._build_sub_cmd_title(parser, sub_title_prefix, title_prefix)
338345
title_ref: str = parser.prog
339346
if aliases:
@@ -366,7 +373,8 @@ def _mk_sub_command(self, aliases: list[str], help_msg: str, parser: ArgumentPar
366373
return group_section
367374

368375
def _build_sub_cmd_title(self, parser: ArgumentParser, sub_title_prefix: str, title_prefix: str) -> str:
369-
title_text, elements = "", parser.prog.split(" ")
376+
prog = _strip_ansi_colors(parser.prog)
377+
title_text, elements = "", prog.split(" ")
370378
if title_prefix is not None:
371379
title_prefix = title_prefix.replace("{prog}", elements[0])
372380
if title_prefix:
@@ -379,7 +387,7 @@ def _build_sub_cmd_title(self, parser: ArgumentParser, sub_title_prefix: str, ti
379387
title_text += f"{elements[0]} "
380388
title_text = self._append_title(title_text, sub_title_prefix, elements[0], elements[1])
381389
else:
382-
title_text += parser.prog
390+
title_text += prog
383391
return title_text.rstrip()
384392

385393
@staticmethod
@@ -392,10 +400,16 @@ def _append_title(title_text: str, sub_title_prefix: str, prog: str, sub_cmd: st
392400

393401
def _mk_usage(self, parser: ArgumentParser) -> literal_block:
394402
parser.formatter_class = lambda prog: HelpFormatter(prog, width=self.options.get("usage_width", 100))
395-
texts = parser.format_usage()[len("usage: ") :].splitlines()
403+
with self.no_color():
404+
texts = parser.format_usage()[len("usage: ") :].splitlines()
396405
texts = [line if at == 0 else f"{' ' * (len(parser.prog) + 1)}{line.lstrip()}" for at, line in enumerate(texts)]
397406
return literal_block("", Text("\n".join(texts)))
398407

408+
@contextmanager
409+
def no_color(self) -> Iterator[None]:
410+
with patch.dict(os.environ, {"NO_COLOR": "1"}, clear=False):
411+
yield None
412+
399413

400414
SINGLE_QUOTE = re.compile(r"[']+(.+?)[']+")
401415
DOUBLE_QUOTE = re.compile(r'["]+(.+?)["]+')
@@ -417,6 +431,15 @@ def _parse_known_args_hook(self: ArgumentParser, *args: Any, **kwargs: Any) -> N
417431
raise HookError(self)
418432

419433

434+
_ANSI_COLOR_RE = re.compile(r"\x1b\[[0-9;]*m")
435+
436+
437+
def _strip_ansi_colors(text: str) -> str:
438+
"""Remove ANSI color/style escape sequences (SGR codes) from text."""
439+
# needed due to https://github.com/python/cpython/issues/139809
440+
return _ANSI_COLOR_RE.sub("", text)
441+
442+
420443
__all__ = [
421444
"SphinxArgparseCli",
422445
]

tests/test_logic.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
if TYPE_CHECKING:
1313
from io import StringIO
1414

15-
from _pytest.fixtures import SubRequest
15+
from _pytest.fixtres import SubRequest
1616
from sphinx.testing.util import SphinxTestApp
1717

1818

@@ -23,7 +23,7 @@ def opt_grp_name() -> tuple[str, str]:
2323

2424

2525
@pytest.fixture
26-
def build_outcome(app: SphinxTestApp, request: SubRequest) -> str:
26+
def build_outcome(app: SphinxTestApp, request: SubRequest, monkeypatch: pytest.MonkeyPatch) -> str:
2727
prepare_marker = request.node.get_closest_marker("prepare")
2828
if prepare_marker:
2929
directive_args: list[str] | None = prepare_marker.kwargs.get("directive_args")
@@ -41,6 +41,8 @@ def build_outcome(app: SphinxTestApp, request: SubRequest) -> str:
4141
assert sphinx_marker is not None
4242
ext = ext_mapping[sphinx_marker.kwargs.get("buildername")]
4343

44+
monkeypatch.setenv("FORCE_COLOR", "1")
45+
monkeypatch.delenv("NO_COLOR", raising=False)
4446
app.build()
4547
return (Path(app.outdir) / f"index.{ext}").read_text()
4648

0 commit comments

Comments
 (0)