diff -r b5ccdf7c032a Doc/library/argparse.rst --- a/Doc/library/argparse.rst Sun Aug 21 00:39:18 2011 +0200 +++ b/Doc/library/argparse.rst Mon Aug 22 18:58:51 2011 +0200 @@ -1498,6 +1498,17 @@ >>> parser.parse_args(['co', 'bar']) Namespace(foo='bar') + Subparsers now support abbreviation when used. + So for example the following three calls all can be used + - as long as the choosen abbreviation is unambiguous: + + >>> parser.parse_args(['checkout', 'bar']) + Namespace(foo='bar') + >>> parser.parse_args(['check', 'bar']) + Namespace(foo='bar') + >>> parser.parse_args(['che', 'bar']) + Namespace(foo='bar') + One particularly effective way of handling sub-commands is to combine the use of the :meth:`add_subparsers` method with calls to :meth:`set_defaults` so that each subparser knows which Python function it should execute. For diff -r b5ccdf7c032a Lib/argparse.py --- a/Lib/argparse.py Sun Aug 21 00:39:18 2011 +0200 +++ b/Lib/argparse.py Mon Aug 22 18:58:51 2011 +0200 @@ -1108,14 +1108,18 @@ parser_name = values[0] arg_strings = values[1:] + # select the parser + parser = None + for p in self._name_parser_map: + if p.startswith(parser_name): + parser = self._name_parser_map[p] + break + # set the parser name if requested if self.dest is not SUPPRESS: - setattr(namespace, self.dest, parser_name) - - # select the parser - try: - parser = self._name_parser_map[parser_name] - except KeyError: + setattr(namespace, self.dest, p) + + if not parser: args = {'parser_name': parser_name, 'choices': ', '.join(self._name_parser_map)} msg = _('unknown parser %(parser_name)r (choices: %(choices)s)') % args @@ -2286,11 +2290,18 @@ def _check_value(self, action, value): # converted value must be one of the choices (if specified) - if action.choices is not None and value not in action.choices: - args = {'value': value, - 'choices': ', '.join(map(repr, action.choices))} - msg = _('invalid choice: %(value)r (choose from %(choices)s)') - raise ArgumentError(action, msg % args) + if action.choices is not None: + ac = [ax for ax in action.choices if str(ax).startswith(str(value))] + if len(ac) == 0: + args = {'value': value, + 'choices': ', '.join(map(repr, action.choices))} + msg = _('invalid choice: %(value)r (choose from %(choices)s)') + raise ArgumentError(action, msg % args) + elif len(ac) > 1: + args = {'value': value, + 'choices': ', '.join(ac)} + msg = _('ambiguous choice: %(value)r could match %(choices)s') + raise ArgumentError(action, msg % args) # ======================= # Help-formatting methods diff -r b5ccdf7c032a Lib/test/test_argparse.py --- a/Lib/test/test_argparse.py Sun Aug 21 00:39:18 2011 +0200 +++ b/Lib/test/test_argparse.py Mon Aug 22 18:58:51 2011 +0200 @@ -1758,6 +1758,22 @@ parser2.add_argument('-y', choices='123', help='y help') parser2.add_argument('z', type=complex, nargs='*', help='z help') + # add third sub-parser (to test abbreviations) + parser3_kwargs = dict(description='3 description') + if subparser_help: + parser3_kwargs['help'] = '3 help' + parser3 = subparsers.add_parser('3', **parser3_kwargs) + parser3.add_argument('-w', type=int, help='w help') + parser3.add_argument('x', choices='abc', help='x help') + + # add fourth sub-parser, with longer name (to test abbreviations) + parser3_kwargs = dict(description='3long description') + if subparser_help: + parser3_kwargs['help'] = '3long help' + parser3 = subparsers.add_parser('3long', **parser3_kwargs) + parser3.add_argument('-w', type=int, help='w help') + parser3.add_argument('x', choices='abc', help='x help') + # return the main parser return parser @@ -1773,6 +1789,23 @@ args = args_str.split() self.assertArgumentParserError(self.parser.parse_args, args) + def test_parse_args_abbreviation(self): + # check some non-failure cases: + self.assertEqual( + self.parser.parse_args('0.5 3long b -w 7'.split()), + NS(foo=False, bar=0.5, w=7, x='b'), + ) + self.assertEqual( + self.parser.parse_args('0.5 3lon b -w 7'.split()), + NS(foo=False, bar=0.5, w=7, x='b'), + ) + self.assertEqual( + self.parser.parse_args('0.5 3l b -w 7'.split()), + NS(foo=False, bar=0.5, w=7, x='b'), + ) + # check a failure case: '3' is ambiguous + self.assertArgumentParserError(self.parser.parse_args,'0.5 3 b -w 7'.split()) + def test_parse_args(self): # check some non-failure cases: self.assertEqual( @@ -1821,77 +1854,79 @@ def test_help(self): self.assertEqual(self.parser.format_usage(), - 'usage: PROG [-h] [--foo] bar {1,2} ...\n') + 'usage: PROG [-h] [--foo] bar {1,2,3,3long} ...\n') self.assertEqual(self.parser.format_help(), textwrap.dedent('''\ - usage: PROG [-h] [--foo] bar {1,2} ... + usage: PROG [-h] [--foo] bar {1,2,3,3long} ... main description positional arguments: - bar bar help - {1,2} command help + bar bar help + {1,2,3,3long} command help optional arguments: - -h, --help show this help message and exit - --foo foo help + -h, --help show this help message and exit + --foo foo help ''')) def test_help_extra_prefix_chars(self): # Make sure - is still used for help if it is a non-first prefix char parser = self._get_parser(prefix_chars='+:-') self.assertEqual(parser.format_usage(), - 'usage: PROG [-h] [++foo] bar {1,2} ...\n') + 'usage: PROG [-h] [++foo] bar {1,2,3,3long} ...\n') self.assertEqual(parser.format_help(), textwrap.dedent('''\ - usage: PROG [-h] [++foo] bar {1,2} ... + usage: PROG [-h] [++foo] bar {1,2,3,3long} ... main description positional arguments: - bar bar help - {1,2} command help + bar bar help + {1,2,3,3long} command help optional arguments: - -h, --help show this help message and exit - ++foo foo help + -h, --help show this help message and exit + ++foo foo help ''')) def test_help_alternate_prefix_chars(self): parser = self._get_parser(prefix_chars='+:/') self.assertEqual(parser.format_usage(), - 'usage: PROG [+h] [++foo] bar {1,2} ...\n') + 'usage: PROG [+h] [++foo] bar {1,2,3,3long} ...\n') self.assertEqual(parser.format_help(), textwrap.dedent('''\ - usage: PROG [+h] [++foo] bar {1,2} ... + usage: PROG [+h] [++foo] bar {1,2,3,3long} ... main description positional arguments: - bar bar help - {1,2} command help + bar bar help + {1,2,3,3long} command help optional arguments: - +h, ++help show this help message and exit - ++foo foo help + +h, ++help show this help message and exit + ++foo foo help ''')) def test_parser_command_help(self): self.assertEqual(self.command_help_parser.format_usage(), - 'usage: PROG [-h] [--foo] bar {1,2} ...\n') + 'usage: PROG [-h] [--foo] bar {1,2,3,3long} ...\n') self.assertEqual(self.command_help_parser.format_help(), textwrap.dedent('''\ - usage: PROG [-h] [--foo] bar {1,2} ... + usage: PROG [-h] [--foo] bar {1,2,3,3long} ... main description positional arguments: - bar bar help - {1,2} command help - 1 1 help - 2 2 help + bar bar help + {1,2,3,3long} command help + 1 1 help + 2 2 help + 3 3 help + 3long 3long help optional arguments: - -h, --help show this help message and exit - --foo foo help + -h, --help show this help message and exit + --foo foo help ''')) def test_subparser_title_help(self): @@ -1998,6 +2033,8 @@ 1 (1alias1, 1alias2) 1 help 2 2 help + 3 3 help + 3long 3long help """)) # ============