FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

GH-142035: Fix wrapping of colorized argparse help text by savannahostrowski · Pull Request #154634 · python/cpython · GitHub

Repository navigation

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .py  (2) .rst  (1) All 2 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
30 changes: 29 additions & 1 deletion Lib/argparse.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,35 @@ def _split_lines(self, text, width):
# The textwrap module is used only for formatting help.
# Delay its import for speeding up the common usage of argparse.
import textwrap
return textwrap.wrap(text, width)
decolored = self._decolor(text)
if decolored == text:
return textwrap.wrap(text, width)

# gh-142035: colors inflate textwrap's length counts, so wrap
# the decolored text and re-apply colors per word; if textwrap
# split a word, keep the plain lines (colors can't be mapped).
plain = self._whitespace_matcher.sub(' ', decolored).strip()
if not plain:
# nothing visible to wrap (e.g. an empty interpolated value)
return [text]
plain_lines = textwrap.wrap(plain, width)
plain_words = plain.split()
colored_words = text.split()
# Drop escape-only tokens (e.g. an empty interpolated value).
if len(colored_words) != len(plain_words):
colored_words = [
word for word in colored_words if self._decolor(word)
]
colored_lines = []
start = 0
for plain_line in plain_lines:
plain_line_words = plain_line.split()
end = start + len(plain_line_words)
if plain_words[start:end] != plain_line_words:
return plain_lines
colored_lines.append(' '.join(colored_words[start:end]))
start = end
return colored_lines

def _fill_text(self, text, width, indent):
text = self._whitespace_matcher.sub(' ', text).strip()
Expand Down
56 changes: 56 additions & 0 deletions Lib/test/test_argparse.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -7580,6 +7580,62 @@ def test_argparse_color_custom_usage(self):
),
)

def test_argparse_color_wrapping_matches_uncolored(self):
# gh-142035: color codes must not affect where help text wraps.
# Stripping the escapes from colored help must yield exactly the
# same text as the uncolored help across representative widths.
def build(color, path="output.txt"):
parser = argparse.ArgumentParser(prog="PROG", color=color)
parser.add_argument(
"--mode",
default="auto",
choices=("auto", "fast", "slow"),
help="select the operating mode from the available choices "
"%(choices)s and note the default is %(default)s here",
)
parser.add_argument(
"--path",
default=path,
help="write output to %(default)s and continue processing",
)
return parser

env = self.enterContext(os_helper.EnvironmentVarGuard())
paths = (
"output.txt",
"/var/lib/application/cache/unusually_long_generated_filename",
"production-read-only-replica",
)
for path in paths:
for columns in ("80", "60", "45", "30", "20"):
with self.subTest(path=path, columns=columns):
env["COLUMNS"] = columns
colored = build(color=True, path=path).format_help()
plain = build(color=False, path=path).format_help()
self.assertIn(
f"{self.theme.interpolated_value}auto"
f"{self.theme.reset}",
colored,
)
self.assertEqual(_colorize.decolor(colored), plain)

def test_argparse_color_preserved_when_wrapping_between_words(self):
parser = argparse.ArgumentParser(prog="PROG", color=True)
parser.add_argument(
"--mode", default="auto",
help="select the %(default)s operating mode from the available "
"options and continue with several more words",
)

env = self.enterContext(os_helper.EnvironmentVarGuard())
env["COLUMNS"] = "40"
help_text = parser.format_help()

self.assertIn(
f"{self.theme.interpolated_value}auto{self.theme.reset}",
help_text,
)

def test_custom_formatter_function(self):
def custom_formatter(prog):
return argparse.RawTextHelpFormatter(prog, indent_increment=5)
Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix incorrect wrapping of :mod:`argparse` help text when color is enabled.
Loading

Back | FazBrowse Home | New Git URL