gh-133653: Fix argparse.ArgumentParser with the formatter_class argument (GH-133813)

* Fix TypeError when formatter_class is a custom subclass of
  HelpFormatter.
* Fix TypeError when formatter_class is not a subclass of
  HelpFormatter and non-standard prefix_char is used.
* Fix support of colorizing when formatter_class is not a subclass of
  HelpFormatter.
* Remove the prefix_chars parameter of HelpFormatter.
This commit is contained in:
Serhiy Storchaka 2025-05-12 20:27:34 +03:00 committed by GitHub
parent 14305a83d3
commit 734e15b70d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 154 additions and 43 deletions

View file

@ -167,7 +167,6 @@ class HelpFormatter(object):
indent_increment=2,
max_help_position=24,
width=None,
prefix_chars='-',
color=False,
):
# default setting for width
@ -176,16 +175,7 @@ class HelpFormatter(object):
width = shutil.get_terminal_size().columns
width -= 2
from _colorize import can_colorize, decolor, get_theme
if color and can_colorize():
self._theme = get_theme(force_color=True).argparse
self._decolor = decolor
else:
self._theme = get_theme(force_no_color=True).argparse
self._decolor = lambda text: text
self._prefix_chars = prefix_chars
self._set_color(color)
self._prog = prog
self._indent_increment = indent_increment
self._max_help_position = min(max_help_position,
@ -202,6 +192,16 @@ class HelpFormatter(object):
self._whitespace_matcher = _re.compile(r'\s+', _re.ASCII)
self._long_break_matcher = _re.compile(r'\n\n\n+')
def _set_color(self, color):
from _colorize import can_colorize, decolor, get_theme
if color and can_colorize():
self._theme = get_theme(force_color=True).argparse
self._decolor = decolor
else:
self._theme = get_theme(force_no_color=True).argparse
self._decolor = lambda text: text
# ===============================
# Section and indentation methods
# ===============================
@ -415,14 +415,7 @@ class HelpFormatter(object):
return ' '.join(self._get_actions_usage_parts(actions, groups))
def _is_long_option(self, string):
return len(string) >= 2 and string[1] in self._prefix_chars
def _is_short_option(self, string):
return (
not self._is_long_option(string)
and len(string) >= 1
and string[0] in self._prefix_chars
)
return len(string) > 2
def _get_actions_usage_parts(self, actions, groups):
# find group indices and identify actions in groups
@ -471,25 +464,22 @@ class HelpFormatter(object):
# produce the first way to invoke the option in brackets
else:
option_string = action.option_strings[0]
if self._is_long_option(option_string):
option_color = t.summary_long_option
else:
option_color = t.summary_short_option
# if the Optional doesn't take a value, format is:
# -s or --long
if action.nargs == 0:
part = action.format_usage()
if self._is_long_option(part):
part = f"{t.summary_long_option}{part}{t.reset}"
elif self._is_short_option(part):
part = f"{t.summary_short_option}{part}{t.reset}"
part = f"{option_color}{part}{t.reset}"
# if the Optional takes a value, format is:
# -s ARGS or --long ARGS
else:
default = self._get_default_metavar_for_optional(action)
args_string = self._format_args(action, default)
if self._is_long_option(option_string):
option_color = t.summary_long_option
elif self._is_short_option(option_string):
option_color = t.summary_short_option
part = (
f"{option_color}{option_string} "
f"{t.summary_label}{args_string}{t.reset}"
@ -606,10 +596,8 @@ class HelpFormatter(object):
for s in strings:
if self._is_long_option(s):
parts.append(f"{t.long_option}{s}{t.reset}")
elif self._is_short_option(s):
parts.append(f"{t.short_option}{s}{t.reset}")
else:
parts.append(s)
parts.append(f"{t.short_option}{s}{t.reset}")
return parts
# if the Optional doesn't take a value, format is:
@ -2723,16 +2711,9 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
return formatter.format_help()
def _get_formatter(self):
if isinstance(self.formatter_class, type) and issubclass(
self.formatter_class, HelpFormatter
):
return self.formatter_class(
prog=self.prog,
prefix_chars=self.prefix_chars,
color=self.color,
)
else:
return self.formatter_class(prog=self.prog)
formatter = self.formatter_class(prog=self.prog)
formatter._set_color(self.color)
return formatter
# =====================
# Help-printing methods