diff -r 46cadd3955d0 Doc/library/argparse.rst --- a/Doc/library/argparse.rst Sat Mar 16 09:15:47 2013 -0700 +++ b/Doc/library/argparse.rst Fri Mar 22 10:09:55 2013 -0700 @@ -168,6 +168,9 @@ * usage_ - The string describing the program usage (default: generated) + * args_default_to_positional_ - Parse unrecognized arguments as + positionals, as opposed to optionals (default: False). + The following sections describe how each of these are used. @@ -602,6 +605,36 @@ The ``%(prog)s`` format specifier is available to fill in the program name in your usage messages. +args_default_to_positional +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:class:`ArgumentParser` parses the argument strings, classifying them as +optionals or positionals (arguments). By default, a string starting with one of the +``prefix_chars`` is assumed to be a command-line option. If ``args_default_to_positional=True``, +such a string will be parsed as an argument, unless it matches one of the defined +command-line options:: + + >>> parser = argparse.ArgumentParser(args_default_to_positional=True) + >>> parser.add_argument('--foo') + >>> parser.add_argument('--bar', nargs=2) + >>> parser.parse_args('--foo --baz --bar -one -two'.split()) + Namespace(bar=['-one', '-two'], foo='--baz') + +This approximates the behavior of :mod:`optparse`, which freely accepts +arguments that begin with ``'-'``. Note however that in this example ``--foo --bar`` +gives an error, since ``--bar`` is defined as a command-line option:: + + >>> parser.parse_args('--foo --bar'.split()) + usage: [-h] [--foo FOO] [--bar BAR BAR] + : error: argument --foo: expected one argument + +Joining the option and value with `=` gets around this limitation:: + + >>> parser.parse_args('--foo=--bar'.split()) + Namespace(bar=None, foo='--bar') + +See also `Arguments containing -`_. + The add_argument() method ------------------------- @@ -1320,6 +1353,7 @@ usage: PROG [-h] [--foo FOO] [bar] PROG: error: extra arguments found: badger +.. _`Arguments containing -`: Arguments containing ``-`` ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -1344,6 +1378,10 @@ >>> parser.parse_args(['-x', '-1', '-5']) Namespace(foo='-5', x='-1') + >>> # complex and scientific notation are accepted as well + >>> parser.parse_args(['-x', '-1e-3', '-1-4j']) + Namespace(foo='-1-4j', x='-1e-3') + >>> parser = argparse.ArgumentParser(prog='PROG') >>> parser.add_argument('-1', dest='one') >>> parser.add_argument('foo', nargs='?') @@ -1352,16 +1390,20 @@ >>> parser.parse_args(['-1', 'X']) Namespace(foo=None, one='X') - >>> # negative number options present, so -2 is an option + >>> # negative number options present, so -2 is an unrecognized option >>> parser.parse_args(['-2']) usage: PROG [-h] [-1 ONE] [foo] - PROG: error: no such option: -2 + PROG: error: unrecognized arguments: -2 >>> # negative number options present, so both -1s are options >>> parser.parse_args(['-1', '-1']) usage: PROG [-h] [-1 ONE] [foo] PROG: error: argument -1: expected one argument + >>> # negative number options present, '-1...' is -1 option plus argument + >>> parser.test(['-1.3e4']) + Namespace(foo=None, one='.3e4') + If you have positional arguments that must begin with ``-`` and don't look like negative numbers, you can insert the pseudo-argument ``'--'`` which tells :meth:`~ArgumentParser.parse_args` that everything after that is a positional @@ -1370,6 +1412,23 @@ >>> parser.parse_args(['--', '-f']) Namespace(foo='-f', one=None) +:class:`ArgumentParser` optional argument, args_default_to_positional_ +alters this behavior, allowing a positional argument to begin with ``-``, +provided it does not match a defined option. Use this alternative with caution +if positional arguments could be confused with command options:: + + >>> parser = argparse.ArgumentParser(args_default_to_positional=True) + >>> parser.add_argument('-1', dest='one') + >>> parser.add_argument('foo', nargs='?') + + >>> # default_to_positional, '-2-4j' is now recognized as an argument + >>> parser.parse_args(['-1', '-2-4j', '-3-4j']) + Namespace(foo='-3-4j', one='-2-4j') + + >>> # default_to_positional, '-1.3e4' still matches the -1 option + >>> parser.parse_args(['-1.3e4']) + Namespace(foo=None, one='.3e4') + Argument abbreviations ^^^^^^^^^^^^^^^^^^^^^^ diff -r 46cadd3955d0 Lib/argparse.py --- a/Lib/argparse.py Sat Mar 16 09:15:47 2013 -0700 +++ b/Lib/argparse.py Fri Mar 22 10:09:55 2013 -0700 @@ -1594,7 +1594,9 @@ fromfile_prefix_chars=None, argument_default=None, conflict_handler='error', - add_help=True): + add_help=True, + args_default_to_positional=False, + ): superinit = super(ArgumentParser, self).__init__ superinit(description=description, @@ -1612,6 +1614,7 @@ self.formatter_class = formatter_class self.fromfile_prefix_chars = fromfile_prefix_chars self.add_help = add_help + self.args_default_to_positional = args_default_to_positional add_group = self.add_argument_group self._positionals = add_group(_('positional arguments')) @@ -2107,12 +2110,20 @@ option_tuple, = option_tuples return option_tuple - # if it was not found as an option, but it looks like a negative - # number, it was meant to be positional + # behave more like optparse even if the argument looks like a option + if self.args_default_to_positional: + return None + + # if it is not found as an option, but looks like a number + # it is meant to be positional # unless there are negative-number-like options - if self._negative_number_matcher.match(arg_string): - if not self._has_negative_number_optionals: + # try complex() is more general than self._negative_number_matcher + if not self._has_negative_number_optionals: + try: + complex(arg_string) return None + except ValueError: + pass # if it contains a space, it was meant to be a positional if ' ' in arg_string: diff -r 46cadd3955d0 Lib/test/test_argparse.py --- a/Lib/test/test_argparse.py Sat Mar 16 09:15:47 2013 -0700 +++ b/Lib/test/test_argparse.py Fri Mar 22 10:09:55 2013 -0700 @@ -1266,7 +1266,8 @@ failures = ['-x', '-y2.5', '-xa', '-x -a', '-x -3', '-x -3.5', '-3 -3.5', '-x -2.5', '-x -2.5 a', '-3 -.5', - 'a x -1', '-x -1 a', '-3 -1 a'] + 'a x -1', '-x -1 a', '-3 -1 a', + '-1E3'] successes = [ ('', NS(x=None, y=None, z=[])), ('-x 2.5', NS(x=2.5, y=None, z=[])), @@ -1279,8 +1280,176 @@ ('a -x 1', NS(x=1.0, y=None, z=['a'])), ('-x 1 a', NS(x=1.0, y=None, z=['a'])), ('-3 1 a', NS(x=None, y=1.0, z=['a'])), + # scientific notation should work + ('-3-.5e1', NS(x=None, y=-5.0, z=[])), + ('-x-0.5e1', NS(x=-5.0, y=None, z=[])), ] +class TestScientificComplex(ParserTestCase): + """Tests scientific notation and complex""" + + argument_signatures = [ + Sig('--xdiv', nargs=2, type=float, default=[0,1]), + Sig('--zdiv', nargs=2, type=complex, default=argparse.SUPPRESS) + ] + failures = [] + successes = [ + ('', NS(xdiv=[0,1])), + ('--xdiv -.5 .5', NS(xdiv=[-0.5, 0.5])), + ('--xdiv -5e-1 5e-1', NS(xdiv=[-0.5, 0.5])), + ('--zdiv -1e-1-1j 5e1+3j', NS(xdiv=[0,1], zdiv=[(-0.1-1j), (50+3j)])), + ] + +class TestDefaultToPositional(ParserTestCase): + """Tests args_default_to_positional option""" + + parser_signature = Sig(args_default_to_positional=True) + argument_signatures = [ + Sig('--foo', nargs=2), + Sig('-a','--asciidoc-opts') + ] + failures = ['--asciidoc-opts --foo','--asciidoc-opts=--foo --foo - -a', + ] + successes = [ + ('', NS(foo=None, asciidoc_opts=None)), + ('--foo one two', NS(foo=['one','two'], asciidoc_opts=None)), + ('--foo -one two', NS(foo=['-one','two'], asciidoc_opts=None)), + ('--asciidoc-opts --safe', NS(asciidoc_opts='--safe', foo=None)), + ('--asciidoc-opts=--safe', NS(asciidoc_opts='--safe', foo=None)), + ('-a--safe', NS(asciidoc_opts='--safe', foo=None)), + ('--asciidoc-opts=--foo --foo - -f', NS(asciidoc_opts='--foo', foo=['-', '-f'])), + ] + +class TestDefaultToPositionalWithOptionLike(ParserTestCase): + """Tests args_default_to_positional option with number-like options + compare to TestOptionLike""" + + parser_signature = Sig(args_default_to_positional=True) + + argument_signatures = [ + Sig('-x', type=float), + Sig('-3', type=float, dest='y'), + Sig('z', nargs='*'), + ] + failures = [ + '-x', + '-xa', + '-x -a', + '-x -3', + '-x -3.5', + '-3 -3.5', + ] + successes = [ + # TestOptionLike successes remain successes + ('', NS(x=None, y=None, z=[])), + ('-x 2.5', NS(x=2.5, y=None, z=[])), + ('-x 2.5 a', NS(x=2.5, y=None, z=['a'])), + ('-3.5', NS(x=None, y=0.5, z=[])), + ('-3-.5', NS(x=None, y=-0.5, z=[])), + ('-3 .5', NS(x=None, y=0.5, z=[])), + ('a -3.5', NS(x=None, y=0.5, z=['a'])), + ('a', NS(x=None, y=None, z=['a'])), + ('a -x 1', NS(x=1.0, y=None, z=['a'])), + ('-x 1 a', NS(x=1.0, y=None, z=['a'])), + ('-3 1 a', NS(x=None, y=1.0, z=['a'])), + ('-3-.5e1', NS(x=None, y=-5.0, z=[])), + ('-x-0.5e1', NS(x=-5.0, y=None, z=[])), + # failure cases that have become successes + ('-y2.5', NS(x=None, y=None, z=['-y2.5'])), + ('-x -2.5', NS(x=-2.5, y=None, z=[])), + ('-x -2.5 a', NS(x=-2.5, y=None, z=['a'])), + ('-3 -.5', NS(x=None, y=-0.5, z=[])), + ('a x -1', NS(x=None, y=None, z=['a', 'x', '-1'])), + ('-x -1 a', NS(x=-1, y=None, z=['a'])), + ('-3 -1 a', NS(x=None, y=-1, z=['a'])), + ('-1E3', NS(x=None, y=None, z=['-1E3'])), + ] + +class TestOptparseLike(ParserTestCase): + """optparse TestStandard cases, without positionals""" + parser_signature = Sig() + # value of args_default_to_positional does not matter + argument_signatures = [ + Sig("-a"), + Sig("-b", "--boo", type=int, dest='boo'), + Sig("--foo", action="append"), + ] + failures = ["-a", "-b 5x","--boo13","--boo=x5"] + successes = [ + ('', NS(**{'a': None, 'boo': None, 'foo': None})), + # test_shortopt_empty_longopt_append + (["-a", "", "--foo=blah", "--foo="], #"-a --foo=blah --foo=", + NS(**{'a': "", 'boo': None, 'foo': ["blah", ""]})), + # test_shortopt_empty_longopt_append + (["--foo", "bar", "--foo", "", "--foo=x"], + NS(**{'a': None, 'boo': None, 'foo': ["bar", "", "x"]})), + # test_long_option_append + (["--foo", "bar", "--foo", "", "--foo=x"], + NS(**{'a': None, 'boo': None, 'foo': ["bar", "", "x"]})), + # test_option_argument_joined + (["-abc"], NS(**{'a': "bc", 'boo': None, 'foo': None})), + # test_option_argument_split + (["-a", "34"], NS(**{'a': "34", 'boo': None, 'foo': None})), + # test_option_argument_joined_integer + (["-b34"], NS(**{'a': None, 'boo': 34, 'foo': None})), + # test_option_argument_split_negative_integer + (["-b", "-5"], NS(**{'a': None, 'boo': -5, 'foo': None})), + # test_long_option_argument_joined + (["--boo=13"], NS(**{'a': None, 'boo': 13, 'foo': None})), + # test_long_option_argument_split + (["--boo", "111"], NS(**{'a': None, 'boo': 111, 'foo': None})), + # test_long_option_short_option + (["--foo=bar", "-axyz"], + NS(**{'a': 'xyz', 'boo': None, 'foo': ["bar"]})), + # test_abbrev_long_option + (["--f=bar", "-axyz"], + NS(**{'a': 'xyz', 'boo': None, 'foo': ["bar"]})), + # test_short_and_long_option_split + (["-a", "xyz", "--foo", "bar"], + NS(**{'a': 'xyz', 'boo': None, 'foo': ["bar"]})), + # test_short_option_split_long_option_append + (["--foo=bar", "-b", "123", "--foo", "baz"], + NS(**{'a': None, 'boo': 123, 'foo': ["bar", "baz"]})), + ] + +class TestOptparseWithPositionalLike(ParserTestCase): + """optparse TestStandard cases, with positionals""" + # value of args_default_to_positional does not matter + parser_signature = Sig() + argument_signatures = [ + Sig("-a"), + Sig("-b", "--boo", type=int, dest='boo'), + Sig("--foo", action="append"), + Sig("pos", nargs='*', default=argparse.SUPPRESS) + ] + failures = [ + # test_short_option_consumes_separator + # even with default_to_positionals, '--' is still a separator + # (["-a", "--", "foo", "bar"], + # NS(**{'a': "--", 'boo': None, 'foo': None, 'pos': ["foo", "bar"]})), + '-a -- foo bar', + # (["-a", "--", "--foo", "bar"], + # NS(**{'a': "--", 'boo': None, 'foo': ["bar"]})), + '-a -- --foo bar', + # test_option_consumes_optionlike_string + # even with default_to_positionals, -b is not an argument + # (["-a", "-b3"], NS(**{'a': "-b3", 'boo': None, 'foo': None})), + '-a -b3' + ] + successes = [ + # test_short_option_split_one_positional_arg + (["-a", "foo", "bar"], + NS(**{'a': "foo", 'boo': None, 'foo': None, 'pos': ["bar"]})), + # test_short_option_joined_and_separator + (["-ab", "--", "--foo", "bar"], + NS(**{'a': "b", 'boo': None, 'foo': None, 'pos': ["--foo", "bar"]})), + # test_hyphen_becomes_positional_arg + (["-ab", "-", "--foo", "bar"], + NS(**{'a': "b", 'boo': None, 'foo': ["bar"], 'pos': ["-"]})), + # test_no_append_versus_append + (["-b3", "-b", "5", "--foo=bar", "--foo", "baz"], + NS(**{'a': None, 'boo': 5, 'foo': ["bar", "baz"]})), + ] class TestDefaultSuppress(ParserTestCase): """Test actions with suppressed defaults""" diff -r 46cadd3955d0 Misc/ACKS --- a/Misc/ACKS Sat Mar 16 09:15:47 2013 -0700 +++ b/Misc/ACKS Fri Mar 22 10:09:55 2013 -0700 @@ -6,7 +6,7 @@ bug reports, ideas, moral support, endorsement, or even complaints.... Without you, I would've stopped working on Python long ago! - --Guido + --Guido PS: In the standard Python distribution, this file is encoded in UTF-8 and the list is in rough alphabetical order by last names. @@ -562,6 +562,7 @@ Bob Ippolito Roger Irwin Atsuo Ishimoto +Paul Jacobson Adam Jackson Ben Jackson Paul Jackson