diff -r 49130b06e3ac Doc/library/argparse.rst --- a/Doc/library/argparse.rst Sun Feb 22 16:15:14 2015 -0800 +++ b/Doc/library/argparse.rst Sat Feb 28 14:30:18 2015 +0100 @@ -1512,6 +1512,34 @@ Other utilities --------------- +Handling interspersed arguments +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. method:: ArgumentParser.disable_interspersed_args() + + Set parsing to stop on the first non-option. For example, if ``-a`` and + ``-b`` are both simple options that take no arguments, :mod:`optparse` + normally accepts this syntax:: + + prog -a arg1 -b arg2 + + and treats it as equivalent to :: + + prog -a -b arg1 arg2 + + To disable this feature, call :meth:`disable_interspersed_args`. This + restores traditional Unix syntax, where option parsing stops with the first + non-option argument. + + Use this if you have a command processor which runs another command which has + options of its own and you want to make sure these options don't get + confused. For example, each command might have a different set of options. + +.. method:: ArgumentParser.enable_interspersed_args() + + Set parsing to not stop on the first non-option, allowing interspersing + switches with command arguments. This is the default behavior. + Sub-commands ^^^^^^^^^^^^ diff -r 49130b06e3ac Lib/argparse.py --- a/Lib/argparse.py Sun Feb 22 16:15:14 2015 -0800 +++ b/Lib/argparse.py Sat Feb 28 14:30:18 2015 +0100 @@ -1629,6 +1629,7 @@ self._positionals = add_group(_('positional arguments')) self._optionals = add_group(_('optional arguments')) self._subparsers = None + self._disabled_interspersed_args = False # register types def identity(string): @@ -1654,6 +1655,12 @@ else: self._defaults.update(defaults) + def disable_interspersed_args(self): + self._disabled_interspersed_args = True + + def enable_interspersed_args(self): + self._disabled_interspersed_args = False + # ======================= # Pretty __repr__ methods # ======================= @@ -1935,6 +1942,7 @@ max_option_string_index = max(option_string_indices) else: max_option_string_index = -1 + while start_index <= max_option_string_index: # consume any Positionals preceding the next option @@ -1942,6 +1950,11 @@ index for index in option_string_indices if index >= start_index]) + + if self._disabled_interspersed_args and next_option_string_index != start_index: + arg_strings_pattern = arg_strings_pattern[:start_index] + "A" * (len(arg_strings_pattern) - start_index) + break + if start_index != next_option_string_index: positionals_end_index = consume_positionals(start_index) diff -r 49130b06e3ac Lib/test/test_argparse.py --- a/Lib/test/test_argparse.py Sun Feb 22 16:15:14 2015 -0800 +++ b/Lib/test/test_argparse.py Sat Feb 28 14:30:18 2015 +0100 @@ -1691,6 +1691,59 @@ NS(x='my_type{1}', y='my_type{42}')) +# =============================== +# Test Interspersed args and '--' +# =============================== + +class TestInterspersedArgs(TestCase): + def setUp(self): + self.parser = ErrorRaisingArgumentParser() + self.parser.add_argument('-a', action='store_true') + self.parser.add_argument('foo') + self.parser.add_argument('rem', nargs='*') + + def test_simple(self): + self.assertEqual(self.parser.parse_args('-a 3 5 6 9'.split()), + NS(a=True, foo='3', rem=['5', '6', '9'])) + self.assertEqual(self.parser.parse_args('3 -a'.split()), + NS(a=True, foo='3', rem=[])) + self.assertEqual(self.parser.parse_args('3 4 5 -a'.split()), + NS(a=True, foo='3', rem=['4', '5'])) + + def test_hyphens(self): + self.assertEqual(self.parser.parse_args('-a -- 3 5 6 9'.split()), + NS(a=True, foo='3', rem=['5', '6', '9'])) + self.assertEqual(self.parser.parse_args('-- 3 -a'.split()), + NS(a=False, foo='3', rem=['-a'])) + self.assertEqual(self.parser.parse_args('-- 3 4 5 -a'.split()), + NS(a=False, foo='3', rem=['4', '5', '-a'])) + + def test_interspersed_args(self): + self.parser.disable_interspersed_args() + self.assertEqual(self.parser.parse_args('-a 3 5 6 9'.split()), + NS(a=True, foo='3', rem=['5', '6', '9'])) + self.assertEqual(self.parser.parse_args('3 -a'.split()), + NS(a=False, foo='3', rem=['-a'])) + self.assertEqual(self.parser.parse_args('3 4 5 -a'.split()), + NS(a=False, foo='3', rem=['4', '5', '-a'])) + + self.parser.enable_interspersed_args() + self.assertEqual(self.parser.parse_args('3 4 5 -a'.split()), + NS(a=True, foo='3', rem=['4', '5'])) + + def test_with_remainder(self): + self.parser = ErrorRaisingArgumentParser() + self.parser.add_argument('-a', action='store_true') + self.parser.add_argument('-b') + self.parser.add_argument('rem', nargs=argparse.REMAINDER) + self.assertEqual(self.parser.parse_args('-b 4 -a c -b 5'.split()), + NS(a=True, b='4', rem=['c', '-b', '5'])) + + self.parser.disable_interspersed_args() + self.assertEqual(self.parser.parse_args('-b 4 -a c -b 5'.split()), + NS(a=True, b='4', rem=['c', '-b', '5'])) + + # ============ # Action tests # ============ diff -r 49130b06e3ac Misc/ACKS --- a/Misc/ACKS Sun Feb 22 16:15:14 2015 -0800 +++ b/Misc/ACKS Sat Feb 28 14:30:18 2015 +0100 @@ -1391,6 +1391,7 @@ Stephen Tonkin Matias Torchinsky Sandro Tosi +László Attila Tóth Richard Townsend David Townshend Nathan Trapuzzano diff -r 49130b06e3ac Misc/NEWS --- a/Misc/NEWS Sun Feb 22 16:15:14 2015 -0800 +++ b/Misc/NEWS Sat Feb 28 14:30:18 2015 +0100 @@ -65,6 +65,9 @@ argument which, if set to True, will pass messages to handlers taking handler levels into account. +- Issue #13966: Add argparse.ArgumentParser.disable_interspersed_args() and + enable_interspersed_args() based on optparse.OptionParser's similar methods. + Build -----