mirror of
https://github.com/RustPython/RustPython.git
synced 2026-06-02 19:39:49 +09:00
Update argparse from CPython 3.14.2
This commit is contained in:
committed by
Jeong, YunWon
parent
ef22bf4774
commit
d75f272c8b
379
Lib/argparse.py
vendored
379
Lib/argparse.py
vendored
@@ -18,11 +18,12 @@ command-line and writes the result to a file::
|
||||
'integers', metavar='int', nargs='+', type=int,
|
||||
help='an integer to be summed')
|
||||
parser.add_argument(
|
||||
'--log', default=sys.stdout, type=argparse.FileType('w'),
|
||||
'--log',
|
||||
help='the file where the sum should be written')
|
||||
args = parser.parse_args()
|
||||
args.log.write('%s' % sum(args.integers))
|
||||
args.log.close()
|
||||
with (open(args.log, 'w') if args.log is not None
|
||||
else contextlib.nullcontext(sys.stdout)) as log:
|
||||
log.write('%s' % sum(args.integers))
|
||||
|
||||
The module contains the following public classes:
|
||||
|
||||
@@ -39,7 +40,8 @@ The module contains the following public classes:
|
||||
|
||||
- FileType -- A factory for defining types of files to be created. As the
|
||||
example above shows, instances of FileType are typically passed as
|
||||
the type= argument of add_argument() calls.
|
||||
the type= argument of add_argument() calls. Deprecated since
|
||||
Python 3.14.
|
||||
|
||||
- Action -- The base class for parser actions. Typically actions are
|
||||
selected by passing strings like 'store_true' or 'append_const' to
|
||||
@@ -159,18 +161,21 @@ class HelpFormatter(object):
|
||||
provided by the class are considered an implementation detail.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
prog,
|
||||
indent_increment=2,
|
||||
max_help_position=24,
|
||||
width=None):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
prog,
|
||||
indent_increment=2,
|
||||
max_help_position=24,
|
||||
width=None,
|
||||
color=True,
|
||||
):
|
||||
# default setting for width
|
||||
if width is None:
|
||||
import shutil
|
||||
width = shutil.get_terminal_size().columns
|
||||
width -= 2
|
||||
|
||||
self._set_color(color)
|
||||
self._prog = prog
|
||||
self._indent_increment = indent_increment
|
||||
self._max_help_position = min(max_help_position,
|
||||
@@ -187,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
|
||||
# ===============================
|
||||
@@ -225,7 +240,11 @@ class HelpFormatter(object):
|
||||
if self.heading is not SUPPRESS and self.heading is not None:
|
||||
current_indent = self.formatter._current_indent
|
||||
heading_text = _('%(heading)s:') % dict(heading=self.heading)
|
||||
heading = '%*s%s\n' % (current_indent, '', heading_text)
|
||||
t = self.formatter._theme
|
||||
heading = (
|
||||
f'{" " * current_indent}'
|
||||
f'{t.heading}{heading_text}{t.reset}\n'
|
||||
)
|
||||
else:
|
||||
heading = ''
|
||||
|
||||
@@ -262,7 +281,7 @@ class HelpFormatter(object):
|
||||
if action.help is not SUPPRESS:
|
||||
|
||||
# find all invocations
|
||||
get_invocation = self._format_action_invocation
|
||||
get_invocation = lambda x: self._decolor(self._format_action_invocation(x))
|
||||
invocation_lengths = [len(get_invocation(action)) + self._current_indent]
|
||||
for subaction in self._iter_indented_subactions(action):
|
||||
invocation_lengths.append(len(get_invocation(subaction)) + self._current_indent)
|
||||
@@ -296,16 +315,23 @@ class HelpFormatter(object):
|
||||
if part and part is not SUPPRESS])
|
||||
|
||||
def _format_usage(self, usage, actions, groups, prefix):
|
||||
t = self._theme
|
||||
|
||||
if prefix is None:
|
||||
prefix = _('usage: ')
|
||||
|
||||
# if usage is specified, use that
|
||||
if usage is not None:
|
||||
usage = usage % dict(prog=self._prog)
|
||||
usage = (
|
||||
t.prog_extra
|
||||
+ usage
|
||||
% {"prog": f"{t.prog}{self._prog}{t.reset}{t.prog_extra}"}
|
||||
+ t.reset
|
||||
)
|
||||
|
||||
# if no optionals or positionals are available, usage is just prog
|
||||
elif usage is None and not actions:
|
||||
usage = '%(prog)s' % dict(prog=self._prog)
|
||||
usage = f"{t.prog}{self._prog}{t.reset}"
|
||||
|
||||
# if optionals and positionals are available, calculate usage
|
||||
elif usage is None:
|
||||
@@ -327,7 +353,7 @@ class HelpFormatter(object):
|
||||
|
||||
# wrap the usage parts if it's too long
|
||||
text_width = self._width - self._current_indent
|
||||
if len(prefix) + len(usage) > text_width:
|
||||
if len(prefix) + len(self._decolor(usage)) > text_width:
|
||||
|
||||
# break usage into wrappable parts
|
||||
opt_parts = self._get_actions_usage_parts(optionals, groups)
|
||||
@@ -343,12 +369,13 @@ class HelpFormatter(object):
|
||||
else:
|
||||
line_len = indent_length - 1
|
||||
for part in parts:
|
||||
if line_len + 1 + len(part) > text_width and line:
|
||||
part_len = len(self._decolor(part))
|
||||
if line_len + 1 + part_len > text_width and line:
|
||||
lines.append(indent + ' '.join(line))
|
||||
line = []
|
||||
line_len = indent_length - 1
|
||||
line.append(part)
|
||||
line_len += len(part) + 1
|
||||
line_len += part_len + 1
|
||||
if line:
|
||||
lines.append(indent + ' '.join(line))
|
||||
if prefix is not None:
|
||||
@@ -356,8 +383,9 @@ class HelpFormatter(object):
|
||||
return lines
|
||||
|
||||
# if prog is short, follow it with optionals or positionals
|
||||
if len(prefix) + len(prog) <= 0.75 * text_width:
|
||||
indent = ' ' * (len(prefix) + len(prog) + 1)
|
||||
prog_len = len(self._decolor(prog))
|
||||
if len(prefix) + prog_len <= 0.75 * text_width:
|
||||
indent = ' ' * (len(prefix) + prog_len + 1)
|
||||
if opt_parts:
|
||||
lines = get_lines([prog] + opt_parts, indent, prefix)
|
||||
lines.extend(get_lines(pos_parts, indent))
|
||||
@@ -380,12 +408,18 @@ class HelpFormatter(object):
|
||||
# join lines into usage
|
||||
usage = '\n'.join(lines)
|
||||
|
||||
usage = usage.removeprefix(prog)
|
||||
usage = f"{t.prog}{prog}{t.reset}{usage}"
|
||||
|
||||
# prefix with 'usage:'
|
||||
return '%s%s\n\n' % (prefix, usage)
|
||||
return f'{t.usage}{prefix}{t.reset}{usage}\n\n'
|
||||
|
||||
def _format_actions_usage(self, actions, groups):
|
||||
return ' '.join(self._get_actions_usage_parts(actions, groups))
|
||||
|
||||
def _is_long_option(self, string):
|
||||
return len(string) > 2
|
||||
|
||||
def _get_actions_usage_parts(self, actions, groups):
|
||||
# find group indices and identify actions in groups
|
||||
group_actions = set()
|
||||
@@ -398,17 +432,18 @@ class HelpFormatter(object):
|
||||
continue
|
||||
|
||||
try:
|
||||
start = actions.index(group._group_actions[0])
|
||||
start = min(actions.index(item) for item in group._group_actions)
|
||||
except ValueError:
|
||||
continue
|
||||
else:
|
||||
end = start + len(group._group_actions)
|
||||
if actions[start:end] == group._group_actions:
|
||||
if set(actions[start:end]) == set(group._group_actions):
|
||||
group_actions.update(group._group_actions)
|
||||
inserts[start, end] = group
|
||||
|
||||
# collect all actions format strings
|
||||
parts = []
|
||||
t = self._theme
|
||||
for action in actions:
|
||||
|
||||
# suppressed arguments are marked with None
|
||||
@@ -418,7 +453,11 @@ class HelpFormatter(object):
|
||||
# produce all arg strings
|
||||
elif not action.option_strings:
|
||||
default = self._get_default_metavar_for_positional(action)
|
||||
part = self._format_args(action, default)
|
||||
part = (
|
||||
t.summary_action
|
||||
+ self._format_args(action, default)
|
||||
+ t.reset
|
||||
)
|
||||
|
||||
# if it's in a group, strip the outer []
|
||||
if action in group_actions:
|
||||
@@ -428,18 +467,26 @@ 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()
|
||||
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)
|
||||
part = '%s %s' % (option_string, args_string)
|
||||
part = (
|
||||
f"{option_color}{option_string} "
|
||||
f"{t.summary_label}{args_string}{t.reset}"
|
||||
)
|
||||
|
||||
# make it look optional if it's not required or in a group
|
||||
if not action.required and action not in group_actions:
|
||||
@@ -486,6 +533,7 @@ class HelpFormatter(object):
|
||||
help_width = max(self._width - help_position, 11)
|
||||
action_width = help_position - self._current_indent - 2
|
||||
action_header = self._format_action_invocation(action)
|
||||
action_header_no_color = self._decolor(action_header)
|
||||
|
||||
# no help; start on same line and add a final newline
|
||||
if not action.help:
|
||||
@@ -493,9 +541,15 @@ class HelpFormatter(object):
|
||||
action_header = '%*s%s\n' % tup
|
||||
|
||||
# short action name; start on the same line and pad two spaces
|
||||
elif len(action_header) <= action_width:
|
||||
tup = self._current_indent, '', action_width, action_header
|
||||
elif len(action_header_no_color) <= action_width:
|
||||
# calculate widths without color codes
|
||||
action_header_color = action_header
|
||||
tup = self._current_indent, '', action_width, action_header_no_color
|
||||
action_header = '%*s%-*s ' % tup
|
||||
# swap in the colored header
|
||||
action_header = action_header.replace(
|
||||
action_header_no_color, action_header_color
|
||||
)
|
||||
indent_first = 0
|
||||
|
||||
# long action name; start on the next line
|
||||
@@ -528,23 +582,42 @@ class HelpFormatter(object):
|
||||
return self._join_parts(parts)
|
||||
|
||||
def _format_action_invocation(self, action):
|
||||
t = self._theme
|
||||
|
||||
if not action.option_strings:
|
||||
default = self._get_default_metavar_for_positional(action)
|
||||
return ' '.join(self._metavar_formatter(action, default)(1))
|
||||
return (
|
||||
t.action
|
||||
+ ' '.join(self._metavar_formatter(action, default)(1))
|
||||
+ t.reset
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
def color_option_strings(strings):
|
||||
parts = []
|
||||
for s in strings:
|
||||
if self._is_long_option(s):
|
||||
parts.append(f"{t.long_option}{s}{t.reset}")
|
||||
else:
|
||||
parts.append(f"{t.short_option}{s}{t.reset}")
|
||||
return parts
|
||||
|
||||
# if the Optional doesn't take a value, format is:
|
||||
# -s, --long
|
||||
if action.nargs == 0:
|
||||
return ', '.join(action.option_strings)
|
||||
option_strings = color_option_strings(action.option_strings)
|
||||
return ', '.join(option_strings)
|
||||
|
||||
# if the Optional takes a value, format is:
|
||||
# -s, --long ARGS
|
||||
else:
|
||||
default = self._get_default_metavar_for_optional(action)
|
||||
args_string = self._format_args(action, default)
|
||||
return ', '.join(action.option_strings) + ' ' + args_string
|
||||
option_strings = color_option_strings(action.option_strings)
|
||||
args_string = (
|
||||
f"{t.label}{self._format_args(action, default)}{t.reset}"
|
||||
)
|
||||
return ', '.join(option_strings) + ' ' + args_string
|
||||
|
||||
def _metavar_formatter(self, action, default_metavar):
|
||||
if action.metavar is not None:
|
||||
@@ -590,16 +663,19 @@ class HelpFormatter(object):
|
||||
return result
|
||||
|
||||
def _expand_help(self, action):
|
||||
help_string = self._get_help_string(action)
|
||||
if '%' not in help_string:
|
||||
return help_string
|
||||
params = dict(vars(action), prog=self._prog)
|
||||
for name in list(params):
|
||||
if params[name] is SUPPRESS:
|
||||
value = params[name]
|
||||
if value is SUPPRESS:
|
||||
del params[name]
|
||||
for name in list(params):
|
||||
if hasattr(params[name], '__name__'):
|
||||
params[name] = params[name].__name__
|
||||
elif hasattr(value, '__name__'):
|
||||
params[name] = value.__name__
|
||||
if params.get('choices') is not None:
|
||||
params['choices'] = ', '.join(map(str, params['choices']))
|
||||
return self._get_help_string(action) % params
|
||||
return help_string % params
|
||||
|
||||
def _iter_indented_subactions(self, action):
|
||||
try:
|
||||
@@ -844,22 +920,16 @@ class Action(_AttributeHolder):
|
||||
return self.option_strings[0]
|
||||
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
raise NotImplementedError(_('.__call__() not defined'))
|
||||
raise NotImplementedError('.__call__() not defined')
|
||||
|
||||
|
||||
# FIXME: remove together with `BooleanOptionalAction` deprecated arguments.
|
||||
_deprecated_default = object()
|
||||
|
||||
class BooleanOptionalAction(Action):
|
||||
def __init__(self,
|
||||
option_strings,
|
||||
dest,
|
||||
default=None,
|
||||
type=_deprecated_default,
|
||||
choices=_deprecated_default,
|
||||
required=False,
|
||||
help=None,
|
||||
metavar=_deprecated_default,
|
||||
deprecated=False):
|
||||
|
||||
_option_strings = []
|
||||
@@ -867,38 +937,19 @@ class BooleanOptionalAction(Action):
|
||||
_option_strings.append(option_string)
|
||||
|
||||
if option_string.startswith('--'):
|
||||
if option_string.startswith('--no-'):
|
||||
raise ValueError(f'invalid option name {option_string!r} '
|
||||
f'for BooleanOptionalAction')
|
||||
option_string = '--no-' + option_string[2:]
|
||||
_option_strings.append(option_string)
|
||||
|
||||
# We need `_deprecated` special value to ban explicit arguments that
|
||||
# match default value. Like:
|
||||
# parser.add_argument('-f', action=BooleanOptionalAction, type=int)
|
||||
for field_name in ('type', 'choices', 'metavar'):
|
||||
if locals()[field_name] is not _deprecated_default:
|
||||
import warnings
|
||||
warnings._deprecated(
|
||||
field_name,
|
||||
"{name!r} is deprecated as of Python 3.12 and will be "
|
||||
"removed in Python {remove}.",
|
||||
remove=(3, 14))
|
||||
|
||||
if type is _deprecated_default:
|
||||
type = None
|
||||
if choices is _deprecated_default:
|
||||
choices = None
|
||||
if metavar is _deprecated_default:
|
||||
metavar = None
|
||||
|
||||
super().__init__(
|
||||
option_strings=_option_strings,
|
||||
dest=dest,
|
||||
nargs=0,
|
||||
default=default,
|
||||
type=type,
|
||||
choices=choices,
|
||||
required=required,
|
||||
help=help,
|
||||
metavar=metavar,
|
||||
deprecated=deprecated)
|
||||
|
||||
|
||||
@@ -1180,6 +1231,7 @@ class _SubParsersAction(Action):
|
||||
self._name_parser_map = {}
|
||||
self._choices_actions = []
|
||||
self._deprecated = set()
|
||||
self._color = True
|
||||
|
||||
super(_SubParsersAction, self).__init__(
|
||||
option_strings=option_strings,
|
||||
@@ -1195,23 +1247,30 @@ class _SubParsersAction(Action):
|
||||
if kwargs.get('prog') is None:
|
||||
kwargs['prog'] = '%s %s' % (self._prog_prefix, name)
|
||||
|
||||
# set color
|
||||
if kwargs.get('color') is None:
|
||||
kwargs['color'] = self._color
|
||||
|
||||
aliases = kwargs.pop('aliases', ())
|
||||
|
||||
if name in self._name_parser_map:
|
||||
raise ArgumentError(self, _('conflicting subparser: %s') % name)
|
||||
raise ValueError(f'conflicting subparser: {name}')
|
||||
for alias in aliases:
|
||||
if alias in self._name_parser_map:
|
||||
raise ArgumentError(
|
||||
self, _('conflicting subparser alias: %s') % alias)
|
||||
raise ValueError(f'conflicting subparser alias: {alias}')
|
||||
|
||||
# create a pseudo-action to hold the choice help
|
||||
if 'help' in kwargs:
|
||||
help = kwargs.pop('help')
|
||||
choice_action = self._ChoicesPseudoAction(name, aliases, help)
|
||||
self._choices_actions.append(choice_action)
|
||||
else:
|
||||
choice_action = None
|
||||
|
||||
# create the parser and add it to the map
|
||||
parser = self._parser_class(**kwargs)
|
||||
if choice_action is not None:
|
||||
parser._check_help(choice_action)
|
||||
self._name_parser_map[name] = parser
|
||||
|
||||
# make parser available under aliases also
|
||||
@@ -1276,7 +1335,7 @@ class _ExtendAction(_AppendAction):
|
||||
# ==============
|
||||
|
||||
class FileType(object):
|
||||
"""Factory for creating file object types
|
||||
"""Deprecated factory for creating file object types
|
||||
|
||||
Instances of FileType are typically passed as type= arguments to the
|
||||
ArgumentParser add_argument() method.
|
||||
@@ -1293,6 +1352,12 @@ class FileType(object):
|
||||
"""
|
||||
|
||||
def __init__(self, mode='r', bufsize=-1, encoding=None, errors=None):
|
||||
import warnings
|
||||
warnings.warn(
|
||||
"FileType is deprecated. Simply open files after parsing arguments.",
|
||||
category=PendingDeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
self._mode = mode
|
||||
self._bufsize = bufsize
|
||||
self._encoding = encoding
|
||||
@@ -1396,7 +1461,7 @@ class _ActionsContainer(object):
|
||||
self._defaults = {}
|
||||
|
||||
# determines whether an "option" looks like a negative number
|
||||
self._negative_number_matcher = _re.compile(r'^-\d+$|^-\d*\.\d+$')
|
||||
self._negative_number_matcher = _re.compile(r'-\.?\d')
|
||||
|
||||
# whether or not there are any optionals that look like negative
|
||||
# numbers -- uses a list so it can be shared and edited
|
||||
@@ -1449,7 +1514,8 @@ class _ActionsContainer(object):
|
||||
chars = self.prefix_chars
|
||||
if not args or len(args) == 1 and args[0][0] not in chars:
|
||||
if args and 'dest' in kwargs:
|
||||
raise ValueError('dest supplied twice for positional argument')
|
||||
raise TypeError('dest supplied twice for positional argument,'
|
||||
' did you mean metavar?')
|
||||
kwargs = self._get_positional_kwargs(*args, **kwargs)
|
||||
|
||||
# otherwise, we're adding an optional argument
|
||||
@@ -1465,27 +1531,34 @@ class _ActionsContainer(object):
|
||||
kwargs['default'] = self.argument_default
|
||||
|
||||
# create the action object, and add it to the parser
|
||||
action_name = kwargs.get('action')
|
||||
action_class = self._pop_action_class(kwargs)
|
||||
if not callable(action_class):
|
||||
raise ValueError('unknown action "%s"' % (action_class,))
|
||||
raise ValueError(f'unknown action {action_class!r}')
|
||||
action = action_class(**kwargs)
|
||||
|
||||
# raise an error if action for positional argument does not
|
||||
# consume arguments
|
||||
if not action.option_strings and action.nargs == 0:
|
||||
raise ValueError(f'action {action_name!r} is not valid for positional arguments')
|
||||
|
||||
# raise an error if the action type is not callable
|
||||
type_func = self._registry_get('type', action.type, action.type)
|
||||
if not callable(type_func):
|
||||
raise ValueError('%r is not callable' % (type_func,))
|
||||
raise TypeError(f'{type_func!r} is not callable')
|
||||
|
||||
if type_func is FileType:
|
||||
raise ValueError('%r is a FileType class object, instance of it'
|
||||
' must be passed' % (type_func,))
|
||||
raise TypeError(f'{type_func!r} is a FileType class object, '
|
||||
f'instance of it must be passed')
|
||||
|
||||
# raise an error if the metavar does not match the type
|
||||
if hasattr(self, "_get_formatter"):
|
||||
formatter = self._get_formatter()
|
||||
try:
|
||||
self._get_formatter()._format_args(action, None)
|
||||
formatter._format_args(action, None)
|
||||
except TypeError:
|
||||
raise ValueError("length of metavar tuple does not match nargs")
|
||||
|
||||
self._check_help(action)
|
||||
return self._add_action(action)
|
||||
|
||||
def add_argument_group(self, *args, **kwargs):
|
||||
@@ -1529,8 +1602,8 @@ class _ActionsContainer(object):
|
||||
if group.title in title_group_map:
|
||||
# This branch could happen if a derived class added
|
||||
# groups with duplicated titles in __init__
|
||||
msg = _('cannot merge actions - two groups are named %r')
|
||||
raise ValueError(msg % (group.title))
|
||||
msg = f'cannot merge actions - two groups are named {group.title!r}'
|
||||
raise ValueError(msg)
|
||||
title_group_map[group.title] = group
|
||||
|
||||
# map each action to its group
|
||||
@@ -1571,13 +1644,15 @@ class _ActionsContainer(object):
|
||||
def _get_positional_kwargs(self, dest, **kwargs):
|
||||
# make sure required is not specified
|
||||
if 'required' in kwargs:
|
||||
msg = _("'required' is an invalid argument for positionals")
|
||||
msg = "'required' is an invalid argument for positionals"
|
||||
raise TypeError(msg)
|
||||
|
||||
# mark positional arguments as required if at least one is
|
||||
# always required
|
||||
nargs = kwargs.get('nargs')
|
||||
if nargs not in [OPTIONAL, ZERO_OR_MORE, REMAINDER, SUPPRESS, 0]:
|
||||
if nargs == 0:
|
||||
raise ValueError('nargs for positionals must be != 0')
|
||||
if nargs not in [OPTIONAL, ZERO_OR_MORE, REMAINDER, SUPPRESS]:
|
||||
kwargs['required'] = True
|
||||
|
||||
# return the keyword arguments with no option strings
|
||||
@@ -1590,11 +1665,9 @@ class _ActionsContainer(object):
|
||||
for option_string in args:
|
||||
# error on strings that don't start with an appropriate prefix
|
||||
if not option_string[0] in self.prefix_chars:
|
||||
args = {'option': option_string,
|
||||
'prefix_chars': self.prefix_chars}
|
||||
msg = _('invalid option string %(option)r: '
|
||||
'must start with a character %(prefix_chars)r')
|
||||
raise ValueError(msg % args)
|
||||
raise ValueError(
|
||||
f'invalid option string {option_string!r}: '
|
||||
f'must start with a character {self.prefix_chars!r}')
|
||||
|
||||
# strings starting with two prefix characters are long options
|
||||
option_strings.append(option_string)
|
||||
@@ -1610,8 +1683,8 @@ class _ActionsContainer(object):
|
||||
dest_option_string = option_strings[0]
|
||||
dest = dest_option_string.lstrip(self.prefix_chars)
|
||||
if not dest:
|
||||
msg = _('dest= is required for options like %r')
|
||||
raise ValueError(msg % option_string)
|
||||
msg = f'dest= is required for options like {option_string!r}'
|
||||
raise TypeError(msg)
|
||||
dest = dest.replace('-', '_')
|
||||
|
||||
# return the updated keyword arguments
|
||||
@@ -1627,8 +1700,8 @@ class _ActionsContainer(object):
|
||||
try:
|
||||
return getattr(self, handler_func_name)
|
||||
except AttributeError:
|
||||
msg = _('invalid conflict_resolution value: %r')
|
||||
raise ValueError(msg % self.conflict_handler)
|
||||
msg = f'invalid conflict_resolution value: {self.conflict_handler!r}'
|
||||
raise ValueError(msg)
|
||||
|
||||
def _check_conflict(self, action):
|
||||
|
||||
@@ -1667,10 +1740,26 @@ class _ActionsContainer(object):
|
||||
if not action.option_strings:
|
||||
action.container._remove_action(action)
|
||||
|
||||
def _check_help(self, action):
|
||||
if action.help and hasattr(self, "_get_formatter"):
|
||||
formatter = self._get_formatter()
|
||||
try:
|
||||
formatter._expand_help(action)
|
||||
except (ValueError, TypeError, KeyError) as exc:
|
||||
raise ValueError('badly formed help string') from exc
|
||||
|
||||
|
||||
class _ArgumentGroup(_ActionsContainer):
|
||||
|
||||
def __init__(self, container, title=None, description=None, **kwargs):
|
||||
if 'prefix_chars' in kwargs:
|
||||
import warnings
|
||||
depr_msg = (
|
||||
"The use of the undocumented 'prefix_chars' parameter in "
|
||||
"ArgumentParser.add_argument_group() is deprecated."
|
||||
)
|
||||
warnings.warn(depr_msg, DeprecationWarning, stacklevel=3)
|
||||
|
||||
# add any missing keyword arguments by checking the container
|
||||
update = kwargs.setdefault
|
||||
update('conflict_handler', container.conflict_handler)
|
||||
@@ -1702,14 +1791,7 @@ class _ArgumentGroup(_ActionsContainer):
|
||||
self._group_actions.remove(action)
|
||||
|
||||
def add_argument_group(self, *args, **kwargs):
|
||||
import warnings
|
||||
warnings.warn(
|
||||
"Nesting argument groups is deprecated.",
|
||||
category=DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
return super().add_argument_group(*args, **kwargs)
|
||||
|
||||
raise ValueError('argument groups cannot be nested')
|
||||
|
||||
class _MutuallyExclusiveGroup(_ArgumentGroup):
|
||||
|
||||
@@ -1720,7 +1802,7 @@ class _MutuallyExclusiveGroup(_ArgumentGroup):
|
||||
|
||||
def _add_action(self, action):
|
||||
if action.required:
|
||||
msg = _('mutually exclusive arguments must be optional')
|
||||
msg = 'mutually exclusive arguments must be optional'
|
||||
raise ValueError(msg)
|
||||
action = self._container._add_action(action)
|
||||
self._group_actions.append(action)
|
||||
@@ -1730,14 +1812,29 @@ class _MutuallyExclusiveGroup(_ArgumentGroup):
|
||||
self._container._remove_action(action)
|
||||
self._group_actions.remove(action)
|
||||
|
||||
def add_mutually_exclusive_group(self, *args, **kwargs):
|
||||
import warnings
|
||||
warnings.warn(
|
||||
"Nesting mutually exclusive groups is deprecated.",
|
||||
category=DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
return super().add_mutually_exclusive_group(*args, **kwargs)
|
||||
def add_mutually_exclusive_group(self, **kwargs):
|
||||
raise ValueError('mutually exclusive groups cannot be nested')
|
||||
|
||||
def _prog_name(prog=None):
|
||||
if prog is not None:
|
||||
return prog
|
||||
arg0 = _sys.argv[0]
|
||||
try:
|
||||
modspec = _sys.modules['__main__'].__spec__
|
||||
except (KeyError, AttributeError):
|
||||
# possibly PYTHONSTARTUP or -X presite or other weird edge case
|
||||
# no good answer here, so fall back to the default
|
||||
modspec = None
|
||||
if modspec is None:
|
||||
# simple script
|
||||
return _os.path.basename(arg0)
|
||||
py = _os.path.basename(_sys.executable)
|
||||
if modspec.name != '__main__':
|
||||
# imported module or package
|
||||
modname = modspec.name.removesuffix('.__main__')
|
||||
return f'{py} -m {modname}'
|
||||
# directory or ZIP file
|
||||
return f'{py} {arg0}'
|
||||
|
||||
|
||||
class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||
@@ -1760,6 +1857,9 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||
- allow_abbrev -- Allow long options to be abbreviated unambiguously
|
||||
- exit_on_error -- Determines whether or not ArgumentParser exits with
|
||||
error info when an error occurs
|
||||
- suggest_on_error - Enables suggestions for mistyped argument choices
|
||||
and subparser names (default: ``False``)
|
||||
- color - Allow color output in help messages (default: ``False``)
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
@@ -1775,19 +1875,18 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||
conflict_handler='error',
|
||||
add_help=True,
|
||||
allow_abbrev=True,
|
||||
exit_on_error=True):
|
||||
|
||||
exit_on_error=True,
|
||||
*,
|
||||
suggest_on_error=False,
|
||||
color=True,
|
||||
):
|
||||
superinit = super(ArgumentParser, self).__init__
|
||||
superinit(description=description,
|
||||
prefix_chars=prefix_chars,
|
||||
argument_default=argument_default,
|
||||
conflict_handler=conflict_handler)
|
||||
|
||||
# default setting for prog
|
||||
if prog is None:
|
||||
prog = _os.path.basename(_sys.argv[0])
|
||||
|
||||
self.prog = prog
|
||||
self.prog = _prog_name(prog)
|
||||
self.usage = usage
|
||||
self.epilog = epilog
|
||||
self.formatter_class = formatter_class
|
||||
@@ -1795,6 +1894,8 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||
self.add_help = add_help
|
||||
self.allow_abbrev = allow_abbrev
|
||||
self.exit_on_error = exit_on_error
|
||||
self.suggest_on_error = suggest_on_error
|
||||
self.color = color
|
||||
|
||||
add_group = self.add_argument_group
|
||||
self._positionals = add_group(_('positional arguments'))
|
||||
@@ -1844,7 +1945,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||
|
||||
def add_subparsers(self, **kwargs):
|
||||
if self._subparsers is not None:
|
||||
raise ArgumentError(None, _('cannot have multiple subparser arguments'))
|
||||
raise ValueError('cannot have multiple subparser arguments')
|
||||
|
||||
# add the parser class to the arguments if it's not present
|
||||
kwargs.setdefault('parser_class', type(self))
|
||||
@@ -1859,15 +1960,19 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||
# prog defaults to the usage message of this parser, skipping
|
||||
# optional arguments and with no "usage:" prefix
|
||||
if kwargs.get('prog') is None:
|
||||
formatter = self._get_formatter()
|
||||
# Create formatter without color to avoid storing ANSI codes in prog
|
||||
formatter = self.formatter_class(prog=self.prog)
|
||||
formatter._set_color(False)
|
||||
positionals = self._get_positional_actions()
|
||||
groups = self._mutually_exclusive_groups
|
||||
formatter.add_usage(self.usage, positionals, groups, '')
|
||||
formatter.add_usage(None, positionals, groups, '')
|
||||
kwargs['prog'] = formatter.format_help().strip()
|
||||
|
||||
# create the parsers action and add it to the positionals list
|
||||
parsers_class = self._pop_action_class(kwargs, 'parsers')
|
||||
action = parsers_class(option_strings=[], **kwargs)
|
||||
action._color = self.color
|
||||
self._check_help(action)
|
||||
self._subparsers._add_action(action)
|
||||
|
||||
# return the created parsers action
|
||||
@@ -2498,7 +2603,6 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||
value = action.default
|
||||
if isinstance(value, str) and value is not SUPPRESS:
|
||||
value = self._get_value(action, value)
|
||||
self._check_value(action, value)
|
||||
|
||||
# when nargs='*' on a positional, if there were no command-line
|
||||
# args, use the default if it is anything other than None
|
||||
@@ -2506,11 +2610,8 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||
not action.option_strings):
|
||||
if action.default is not None:
|
||||
value = action.default
|
||||
self._check_value(action, value)
|
||||
else:
|
||||
# since arg_strings is always [] at this point
|
||||
# there is no need to use self._check_value(action, value)
|
||||
value = arg_strings
|
||||
value = []
|
||||
|
||||
# single argument or optional argument produces a single value
|
||||
elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]:
|
||||
@@ -2543,8 +2644,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||
def _get_value(self, action, arg_string):
|
||||
type_func = self._registry_get('type', action.type, action.type)
|
||||
if not callable(type_func):
|
||||
msg = _('%r is not callable')
|
||||
raise ArgumentError(action, msg % type_func)
|
||||
raise TypeError(f'{type_func!r} is not callable')
|
||||
|
||||
# convert the value to the appropriate type
|
||||
try:
|
||||
@@ -2568,14 +2668,27 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||
def _check_value(self, action, value):
|
||||
# converted value must be one of the choices (if specified)
|
||||
choices = action.choices
|
||||
if choices is not None:
|
||||
if isinstance(choices, str):
|
||||
choices = iter(choices)
|
||||
if value not in choices:
|
||||
args = {'value': str(value),
|
||||
'choices': ', '.join(map(str, action.choices))}
|
||||
msg = _('invalid choice: %(value)r (choose from %(choices)s)')
|
||||
raise ArgumentError(action, msg % args)
|
||||
if choices is None:
|
||||
return
|
||||
|
||||
if isinstance(choices, str):
|
||||
choices = iter(choices)
|
||||
|
||||
if value not in choices:
|
||||
args = {'value': str(value),
|
||||
'choices': ', '.join(map(str, action.choices))}
|
||||
msg = _('invalid choice: %(value)r (choose from %(choices)s)')
|
||||
|
||||
if self.suggest_on_error and isinstance(value, str):
|
||||
if all(isinstance(choice, str) for choice in action.choices):
|
||||
import difflib
|
||||
suggestions = difflib.get_close_matches(value, action.choices, 1)
|
||||
if suggestions:
|
||||
args['closest'] = suggestions[0]
|
||||
msg = _('invalid choice: %(value)r, maybe you meant %(closest)r? '
|
||||
'(choose from %(choices)s)')
|
||||
|
||||
raise ArgumentError(action, msg % args)
|
||||
|
||||
# =======================
|
||||
# Help-formatting methods
|
||||
@@ -2611,7 +2724,9 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||
return formatter.format_help()
|
||||
|
||||
def _get_formatter(self):
|
||||
return self.formatter_class(prog=self.prog)
|
||||
formatter = self.formatter_class(prog=self.prog)
|
||||
formatter._set_color(self.color)
|
||||
return formatter
|
||||
|
||||
# =====================
|
||||
# Help-printing methods
|
||||
|
||||
995
Lib/test/test_argparse.py
vendored
995
Lib/test/test_argparse.py
vendored
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user