Skip to content

Commit 53da086

Browse files
authored
Merge pull request RustPython#2423 from fanninpm/update-argparse
Update argparse
2 parents 389f2bf + f747f1d commit 53da086

File tree

3 files changed

+368
-92
lines changed

3 files changed

+368
-92
lines changed

Lib/argparse.py

Lines changed: 139 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Author: Steven J. Bethard <[email protected]>.
2+
# New maintainer as of 29 August 2019: Raymond Hettinger <[email protected]>
23

34
"""Command-line parsing library
45
@@ -66,6 +67,7 @@
6667
'ArgumentParser',
6768
'ArgumentError',
6869
'ArgumentTypeError',
70+
'BooleanOptionalAction',
6971
'FileType',
7072
'HelpFormatter',
7173
'ArgumentDefaultsHelpFormatter',
@@ -127,7 +129,7 @@ def __repr__(self):
127129
return '%s(%s)' % (type_name, ', '.join(arg_strings))
128130

129131
def _get_kwargs(self):
130-
return sorted(self.__dict__.items())
132+
return list(self.__dict__.items())
131133

132134
def _get_args(self):
133135
return []
@@ -164,15 +166,12 @@ def __init__(self,
164166

165167
# default setting for width
166168
if width is None:
167-
try:
168-
width = int(_os.environ['COLUMNS'])
169-
except (KeyError, ValueError):
170-
width = 80
169+
import shutil
170+
width = shutil.get_terminal_size().columns
171171
width -= 2
172172

173173
self._prog = prog
174174
self._indent_increment = indent_increment
175-
self._max_help_position = max_help_position
176175
self._max_help_position = min(max_help_position,
177176
max(width - 20, indent_increment * 2))
178177
self._width = width
@@ -265,7 +264,7 @@ def add_argument(self, action):
265264
invocations.append(get_invocation(subaction))
266265

267266
# update the maximum item length
268-
invocation_length = max([len(s) for s in invocations])
267+
invocation_length = max(map(len, invocations))
269268
action_length = invocation_length + self._current_indent
270269
self._action_max_length = max(self._action_max_length,
271270
action_length)
@@ -407,13 +406,19 @@ def _format_actions_usage(self, actions, groups):
407406
inserts[start] += ' ['
408407
else:
409408
inserts[start] = '['
410-
inserts[end] = ']'
409+
if end in inserts:
410+
inserts[end] += ']'
411+
else:
412+
inserts[end] = ']'
411413
else:
412414
if start in inserts:
413415
inserts[start] += ' ('
414416
else:
415417
inserts[start] = '('
416-
inserts[end] = ')'
418+
if end in inserts:
419+
inserts[end] += ')'
420+
else:
421+
inserts[end] = ')'
417422
for i in range(start + 1, end):
418423
inserts[i] = '|'
419424

@@ -450,7 +455,7 @@ def _format_actions_usage(self, actions, groups):
450455
# if the Optional doesn't take a value, format is:
451456
# -s or --long
452457
if action.nargs == 0:
453-
part = '%s' % option_string
458+
part = action.format_usage()
454459

455460
# if the Optional takes a value, format is:
456461
# -s ARGS or --long ARGS
@@ -586,7 +591,11 @@ def _format_args(self, action, default_metavar):
586591
elif action.nargs == OPTIONAL:
587592
result = '[%s]' % get_metavar(1)
588593
elif action.nargs == ZERO_OR_MORE:
589-
result = '[%s [%s ...]]' % get_metavar(2)
594+
metavar = get_metavar(1)
595+
if len(metavar) == 2:
596+
result = '[%s [%s ...]]' % metavar
597+
else:
598+
result = '[%s ...]' % metavar
590599
elif action.nargs == ONE_OR_MORE:
591600
result = '%s [%s ...]' % get_metavar(2)
592601
elif action.nargs == REMAINDER:
@@ -596,7 +605,10 @@ def _format_args(self, action, default_metavar):
596605
elif action.nargs == SUPPRESS:
597606
result = ''
598607
else:
599-
formats = ['%s' for _ in range(action.nargs)]
608+
try:
609+
formats = ['%s' for _ in range(action.nargs)]
610+
except TypeError:
611+
raise ValueError("invalid nargs value") from None
600612
result = ' '.join(formats) % get_metavar(action.nargs)
601613
return result
602614

@@ -835,9 +847,52 @@ def _get_kwargs(self):
835847
]
836848
return [(name, getattr(self, name)) for name in names]
837849

850+
def format_usage(self):
851+
return self.option_strings[0]
852+
838853
def __call__(self, parser, namespace, values, option_string=None):
839854
raise NotImplementedError(_('.__call__() not defined'))
840855

856+
class BooleanOptionalAction(Action):
857+
def __init__(self,
858+
option_strings,
859+
dest,
860+
default=None,
861+
type=None,
862+
choices=None,
863+
required=False,
864+
help=None,
865+
metavar=None):
866+
867+
_option_strings = []
868+
for option_string in option_strings:
869+
_option_strings.append(option_string)
870+
871+
if option_string.startswith('--'):
872+
option_string = '--no-' + option_string[2:]
873+
_option_strings.append(option_string)
874+
875+
if help is not None and default is not None:
876+
help += f" (default: {default})"
877+
878+
super().__init__(
879+
option_strings=_option_strings,
880+
dest=dest,
881+
nargs=0,
882+
default=default,
883+
type=type,
884+
choices=choices,
885+
required=required,
886+
help=help,
887+
metavar=metavar)
888+
889+
def __call__(self, parser, namespace, values, option_string=None):
890+
if option_string in self.option_strings:
891+
setattr(namespace, self.dest, not option_string.startswith('--no-'))
892+
893+
def format_usage(self):
894+
return ' | '.join(self.option_strings)
895+
841896

842897
class _StoreAction(Action):
843898

@@ -853,7 +908,7 @@ def __init__(self,
853908
help=None,
854909
metavar=None):
855910
if nargs == 0:
856-
raise ValueError('nargs for store actions must be > 0; if you '
911+
raise ValueError('nargs for store actions must be != 0; if you '
857912
'have nothing to store, actions such as store '
858913
'true or store const may be more appropriate')
859914
if const is not None and nargs != OPTIONAL:
@@ -945,7 +1000,7 @@ def __init__(self,
9451000
help=None,
9461001
metavar=None):
9471002
if nargs == 0:
948-
raise ValueError('nargs for append actions must be > 0; if arg '
1003+
raise ValueError('nargs for append actions must be != 0; if arg '
9491004
'strings are not supplying the value to append, '
9501005
'the append const action may be more appropriate')
9511006
if const is not None and nargs != OPTIONAL:
@@ -1157,6 +1212,12 @@ def __call__(self, parser, namespace, values, option_string=None):
11571212
vars(namespace).setdefault(_UNRECOGNIZED_ARGS_ATTR, [])
11581213
getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).extend(arg_strings)
11591214

1215+
class _ExtendAction(_AppendAction):
1216+
def __call__(self, parser, namespace, values, option_string=None):
1217+
items = getattr(namespace, self.dest, None)
1218+
items = _copy_items(items)
1219+
items.extend(values)
1220+
setattr(namespace, self.dest, items)
11601221

11611222
# ==============
11621223
# Type classes
@@ -1201,8 +1262,9 @@ def __call__(self, string):
12011262
return open(string, self._mode, self._bufsize, self._encoding,
12021263
self._errors)
12031264
except OSError as e:
1204-
message = _("can't open '%s': %s")
1205-
raise ArgumentTypeError(message % (string, e))
1265+
args = {'filename': string, 'error': e}
1266+
message = _("can't open '%(filename)s': %(error)s")
1267+
raise ArgumentTypeError(message % args)
12061268

12071269
def __repr__(self):
12081270
args = self._mode, self._bufsize
@@ -1265,6 +1327,7 @@ def __init__(self,
12651327
self.register('action', 'help', _HelpAction)
12661328
self.register('action', 'version', _VersionAction)
12671329
self.register('action', 'parsers', _SubParsersAction)
1330+
self.register('action', 'extend', _ExtendAction)
12681331

12691332
# raise an exception if the conflict handler is invalid
12701333
self._get_handler()
@@ -1357,6 +1420,10 @@ def add_argument(self, *args, **kwargs):
13571420
if not callable(type_func):
13581421
raise ValueError('%r is not callable' % (type_func,))
13591422

1423+
if type_func is FileType:
1424+
raise ValueError('%r is a FileType class object, instance of it'
1425+
' must be passed' % (type_func,))
1426+
13601427
# raise an error if the metavar does not match the type
13611428
if hasattr(self, "_get_formatter"):
13621429
try:
@@ -1471,10 +1538,8 @@ def _get_optional_kwargs(self, *args, **kwargs):
14711538

14721539
# strings starting with two prefix characters are long options
14731540
option_strings.append(option_string)
1474-
if option_string[0] in self.prefix_chars:
1475-
if len(option_string) > 1:
1476-
if option_string[1] in self.prefix_chars:
1477-
long_option_strings.append(option_string)
1541+
if len(option_string) > 1 and option_string[1] in self.prefix_chars:
1542+
long_option_strings.append(option_string)
14781543

14791544
# infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x'
14801545
dest = kwargs.pop('dest', None)
@@ -1614,6 +1679,8 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
16141679
- conflict_handler -- String indicating how to handle conflicts
16151680
- add_help -- Add a -h/-help option
16161681
- allow_abbrev -- Allow long options to be abbreviated unambiguously
1682+
- exit_on_error -- Determines whether or not ArgumentParser exits with
1683+
error info when an error occurs
16171684
"""
16181685

16191686
def __init__(self,
@@ -1628,19 +1695,14 @@ def __init__(self,
16281695
argument_default=None,
16291696
conflict_handler='error',
16301697
add_help=True,
1631-
allow_abbrev=True):
1632-
_ActionsContainer.__init__(self,
1633-
description=description,
1634-
prefix_chars=prefix_chars,
1635-
argument_default=argument_default,
1636-
conflict_handler=conflict_handler)
1637-
# FIXME: get multiple inheritance method resolution right so we can use
1638-
# what's below instead of the modified version above
1639-
# superinit = super(ArgumentParser, self).__init__
1640-
# superinit(description=description,
1641-
# prefix_chars=prefix_chars,
1642-
# argument_default=argument_default,
1643-
# conflict_handler=conflict_handler)
1698+
allow_abbrev=True,
1699+
exit_on_error=True):
1700+
1701+
superinit = super(ArgumentParser, self).__init__
1702+
superinit(description=description,
1703+
prefix_chars=prefix_chars,
1704+
argument_default=argument_default,
1705+
conflict_handler=conflict_handler)
16441706

16451707
# default setting for prog
16461708
if prog is None:
@@ -1653,6 +1715,7 @@ def __init__(self,
16531715
self.fromfile_prefix_chars = fromfile_prefix_chars
16541716
self.add_help = add_help
16551717
self.allow_abbrev = allow_abbrev
1718+
self.exit_on_error = exit_on_error
16561719

16571720
add_group = self.add_argument_group
16581721
self._positionals = add_group(_('positional arguments'))
@@ -1783,15 +1846,19 @@ def parse_known_args(self, args=None, namespace=None):
17831846
setattr(namespace, dest, self._defaults[dest])
17841847

17851848
# parse the arguments and exit if there are any errors
1786-
try:
1849+
if self.exit_on_error:
1850+
try:
1851+
namespace, args = self._parse_known_args(args, namespace)
1852+
except ArgumentError:
1853+
err = _sys.exc_info()[1]
1854+
self.error(str(err))
1855+
else:
17871856
namespace, args = self._parse_known_args(args, namespace)
1788-
if hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR):
1789-
args.extend(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR))
1790-
delattr(namespace, _UNRECOGNIZED_ARGS_ATTR)
1791-
return namespace, args
1792-
except ArgumentError:
1793-
err = _sys.exc_info()[1]
1794-
self.error(str(err))
1857+
1858+
if hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR):
1859+
args.extend(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR))
1860+
delattr(namespace, _UNRECOGNIZED_ARGS_ATTR)
1861+
return namespace, args
17951862

17961863
def _parse_known_args(self, arg_strings, namespace):
17971864
# replace arg strings that are file references
@@ -2080,10 +2147,11 @@ def _match_argument(self, action, arg_strings_pattern):
20802147
OPTIONAL: _('expected at most one argument'),
20812148
ONE_OR_MORE: _('expected at least one argument'),
20822149
}
2083-
default = ngettext('expected %s argument',
2150+
msg = nargs_errors.get(action.nargs)
2151+
if msg is None:
2152+
msg = ngettext('expected %s argument',
20842153
'expected %s arguments',
20852154
action.nargs) % action.nargs
2086-
msg = nargs_errors.get(action.nargs, default)
20872155
raise ArgumentError(action, msg)
20882156

20892157
# return the number of arguments matched
@@ -2130,24 +2198,23 @@ def _parse_optional(self, arg_string):
21302198
action = self._option_string_actions[option_string]
21312199
return action, option_string, explicit_arg
21322200

2133-
if self.allow_abbrev:
2134-
# search through all possible prefixes of the option string
2135-
# and all actions in the parser for possible interpretations
2136-
option_tuples = self._get_option_tuples(arg_string)
2137-
2138-
# if multiple actions match, the option string was ambiguous
2139-
if len(option_tuples) > 1:
2140-
options = ', '.join([option_string
2141-
for action, option_string, explicit_arg in option_tuples])
2142-
args = {'option': arg_string, 'matches': options}
2143-
msg = _('ambiguous option: %(option)s could match %(matches)s')
2144-
self.error(msg % args)
2145-
2146-
# if exactly one action matched, this segmentation is good,
2147-
# so return the parsed action
2148-
elif len(option_tuples) == 1:
2149-
option_tuple, = option_tuples
2150-
return option_tuple
2201+
# search through all possible prefixes of the option string
2202+
# and all actions in the parser for possible interpretations
2203+
option_tuples = self._get_option_tuples(arg_string)
2204+
2205+
# if multiple actions match, the option string was ambiguous
2206+
if len(option_tuples) > 1:
2207+
options = ', '.join([option_string
2208+
for action, option_string, explicit_arg in option_tuples])
2209+
args = {'option': arg_string, 'matches': options}
2210+
msg = _('ambiguous option: %(option)s could match %(matches)s')
2211+
self.error(msg % args)
2212+
2213+
# if exactly one action matched, this segmentation is good,
2214+
# so return the parsed action
2215+
elif len(option_tuples) == 1:
2216+
option_tuple, = option_tuples
2217+
return option_tuple
21512218

21522219
# if it was not found as an option, but it looks like a negative
21532220
# number, it was meant to be positional
@@ -2171,16 +2238,17 @@ def _get_option_tuples(self, option_string):
21712238
# split at the '='
21722239
chars = self.prefix_chars
21732240
if option_string[0] in chars and option_string[1] in chars:
2174-
if '=' in option_string:
2175-
option_prefix, explicit_arg = option_string.split('=', 1)
2176-
else:
2177-
option_prefix = option_string
2178-
explicit_arg = None
2179-
for option_string in self._option_string_actions:
2180-
if option_string.startswith(option_prefix):
2181-
action = self._option_string_actions[option_string]
2182-
tup = action, option_string, explicit_arg
2183-
result.append(tup)
2241+
if self.allow_abbrev:
2242+
if '=' in option_string:
2243+
option_prefix, explicit_arg = option_string.split('=', 1)
2244+
else:
2245+
option_prefix = option_string
2246+
explicit_arg = None
2247+
for option_string in self._option_string_actions:
2248+
if option_string.startswith(option_prefix):
2249+
action = self._option_string_actions[option_string]
2250+
tup = action, option_string, explicit_arg
2251+
result.append(tup)
21842252

21852253
# single character options can be concatenated with their arguments
21862254
# but multiple character options always have to have their argument

0 commit comments

Comments
 (0)