From fd311a712d5876c3a3efff265978452eea759f85 Mon Sep 17 00:00:00 2001 From: Steven Bethard Date: Sat, 18 Dec 2010 11:19:23 +0000 Subject: [PATCH] Add subparser aliases for argparse. Resolves issue 9324. Approved by Georg for beta2 on the tracker. --- Doc/library/argparse.rst | 12 ++++++++- Lib/argparse.py | 17 ++++++++++--- Lib/test/test_argparse.py | 51 +++++++++++++++++++++++++++++++++++++-- Misc/ACKS | 1 + Misc/NEWS | 2 ++ 5 files changed, 77 insertions(+), 6 deletions(-) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index 0ca8aa066176..72878c547485 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -1428,6 +1428,16 @@ Sub-commands {foo,bar} additional help + Furthermore, ``add_parser`` supports an additional ``aliases`` argument, + which allows multiple strings to refer to the same subparser. This example, + like ``svn``, aliases ``co`` as a shorthand for ``checkout``:: + + >>> parser = argparse.ArgumentParser() + >>> subparsers = parser.add_subparsers() + >>> checkout = subparsers.add_parser('checkout', aliases=['co']) + >>> checkout.add_argument('foo') + >>> parser.parse_args(['co', '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 @@ -1466,7 +1476,7 @@ Sub-commands >>> args.func(args) ((XYZYX)) - This way, you can let :meth:`parse_args` does the job of calling the + This way, you can let :meth:`parse_args` do the job of calling the appropriate function after argument parsing is complete. Associating functions with actions like this is typically the easiest way to handle the different actions for each of your subparsers. However, if it is necessary diff --git a/Lib/argparse.py b/Lib/argparse.py index 557cc00fa7c0..57eaaadcff8b 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -1023,9 +1023,13 @@ class _SubParsersAction(Action): class _ChoicesPseudoAction(Action): - def __init__(self, name, help): + def __init__(self, name, aliases, help): + metavar = dest = name + if aliases: + metavar += ' (%s)' % ', '.join(aliases) sup = super(_SubParsersAction._ChoicesPseudoAction, self) - sup.__init__(option_strings=[], dest=name, help=help) + sup.__init__(option_strings=[], dest=dest, help=help, + metavar=metavar) def __init__(self, option_strings, @@ -1053,15 +1057,22 @@ class _SubParsersAction(Action): if kwargs.get('prog') is None: kwargs['prog'] = '%s %s' % (self._prog_prefix, name) + aliases = kwargs.pop('aliases', ()) + # create a pseudo-action to hold the choice help if 'help' in kwargs: help = kwargs.pop('help') - choice_action = self._ChoicesPseudoAction(name, help) + choice_action = self._ChoicesPseudoAction(name, aliases, help) self._choices_actions.append(choice_action) # create the parser and add it to the map parser = self._parser_class(**kwargs) self._name_parser_map[name] = parser + + # make parser available under aliases also + for alias in aliases: + self._name_parser_map[alias] = parser + return parser def _get_subactions(self): diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 0b7ed5e1b3e3..d536be96523d 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -1708,7 +1708,8 @@ class TestAddSubparsers(TestCase): def assertArgumentParserError(self, *args, **kwargs): self.assertRaises(ArgumentParserError, *args, **kwargs) - def _get_parser(self, subparser_help=False, prefix_chars=None): + def _get_parser(self, subparser_help=False, prefix_chars=None, + aliases=False): # create a parser with a subparsers argument if prefix_chars: parser = ErrorRaisingArgumentParser( @@ -1724,13 +1725,21 @@ class TestAddSubparsers(TestCase): 'bar', type=float, help='bar help') # check that only one subparsers argument can be added - subparsers = parser.add_subparsers(help='command help') + subparsers_kwargs = {} + if aliases: + subparsers_kwargs['metavar'] = 'COMMAND' + subparsers_kwargs['title'] = 'commands' + else: + subparsers_kwargs['help'] = 'command help' + subparsers = parser.add_subparsers(**subparsers_kwargs) self.assertArgumentParserError(parser.add_subparsers) # add first sub-parser parser1_kwargs = dict(description='1 description') if subparser_help: parser1_kwargs['help'] = '1 help' + if aliases: + parser1_kwargs['aliases'] = ['1alias1', '1alias2'] parser1 = subparsers.add_parser('1', **parser1_kwargs) parser1.add_argument('-w', type=int, help='w help') parser1.add_argument('x', choices='abc', help='x help') @@ -1947,6 +1956,44 @@ class TestAddSubparsers(TestCase): -y {1,2,3} y help ''')) + def test_alias_invocation(self): + parser = self._get_parser(aliases=True) + self.assertEqual( + parser.parse_known_args('0.5 1alias1 b'.split()), + (NS(foo=False, bar=0.5, w=None, x='b'), []), + ) + self.assertEqual( + parser.parse_known_args('0.5 1alias2 b'.split()), + (NS(foo=False, bar=0.5, w=None, x='b'), []), + ) + + def test_error_alias_invocation(self): + parser = self._get_parser(aliases=True) + self.assertArgumentParserError(parser.parse_args, + '0.5 1alias3 b'.split()) + + def test_alias_help(self): + parser = self._get_parser(aliases=True, subparser_help=True) + self.maxDiff = None + self.assertEqual(parser.format_help(), textwrap.dedent("""\ + usage: PROG [-h] [--foo] bar COMMAND ... + + main description + + positional arguments: + bar bar help + + optional arguments: + -h, --help show this help message and exit + --foo foo help + + commands: + COMMAND + 1 (1alias1, 1alias2) + 1 help + 2 2 help + """)) + # ============ # Groups tests # ============ diff --git a/Misc/ACKS b/Misc/ACKS index eaf98a3299f4..1bd0f3347c81 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -734,6 +734,7 @@ Hajime Saitou George Sakkis Rich Salz Kevin Samborn +Adrian Sampson Ilya Sandler Mark Sapiro Ty Sarna diff --git a/Misc/NEWS b/Misc/NEWS index 37a23333a8c7..fdda4826167c 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -84,6 +84,8 @@ Build - The Windows build now uses Tcl/Tk 8.5.9 and sqlite3 3.7.4. +- Issue #9234: argparse supports alias names for subparsers. + What's New in Python 3.2 Beta 1? ================================ -- 2.47.3