diff options
-rw-r--r-- | .gitreview | 2 | ||||
-rw-r--r-- | .zuul.yaml | 4 | ||||
-rw-r--r-- | README.rst | 2 | ||||
-rw-r--r-- | cliff/_argparse.py | 80 | ||||
-rw-r--r-- | cliff/command.py | 1 | ||||
-rw-r--r-- | cliff/tests/test_command.py | 30 | ||||
-rw-r--r-- | doc/source/install/index.rst | 2 | ||||
-rw-r--r-- | setup.cfg | 4 | ||||
-rw-r--r-- | tox.ini | 4 |
9 files changed, 115 insertions, 14 deletions
@@ -1,4 +1,4 @@ [gerrit] -host=review.openstack.org +host=review.opendev.org port=29418 project=openstack/cliff.git @@ -17,7 +17,7 @@ vars: # Set work dir to neutronclient so that if it's triggered by one of the # other repos the tests will run in the same place - zuul_work_dir: src/git.openstack.org/openstack/python-neutronclient + zuul_work_dir: src/opendev.org/openstack/python-neutronclient - project: templates: @@ -26,8 +26,8 @@ - lib-forward-testing-python3 - openstack-lower-constraints-jobs - openstack-python-jobs - - openstack-python35-jobs - openstack-python36-jobs + - openstack-python37-jobs - publish-openstack-docs-pti check: jobs: @@ -19,5 +19,5 @@ other extensions. * Free software: Apache license * Documentation: https://docs.openstack.org/cliff/latest/ -* Source: https://git.openstack.org/cgit/openstack/cliff +* Source: https://opendev.org/openstack/cliff * Bugs: https://bugs.launchpad.net/python-cliff diff --git a/cliff/_argparse.py b/cliff/_argparse.py index e48dc79..5358e72 100644 --- a/cliff/_argparse.py +++ b/cliff/_argparse.py @@ -14,11 +14,14 @@ from __future__ import absolute_import from argparse import * # noqa +import argparse import sys +import warnings -if sys.version_info < (3, 5): - class ArgumentParser(ArgumentParser): # noqa +class ArgumentParser(argparse.ArgumentParser): + + if sys.version_info < (3, 5): def __init__(self, *args, **kwargs): self.allow_abbrev = kwargs.pop("allow_abbrev", True) super(ArgumentParser, self).__init__(*args, **kwargs) @@ -28,3 +31,76 @@ if sys.version_info < (3, 5): return super(ArgumentParser, self)._get_option_tuples( option_string) return () + + # NOTE(dhellmann): We have to override the methods for creating + # groups to return our objects that know how to deal with the + # special conflict handler. + + def add_argument_group(self, *args, **kwargs): + group = _ArgumentGroup(self, *args, **kwargs) + self._action_groups.append(group) + return group + + def add_mutually_exclusive_group(self, **kwargs): + group = _MutuallyExclusiveGroup(self, **kwargs) + self._mutually_exclusive_groups.append(group) + return group + + def _handle_conflict_ignore(self, action, conflicting_actions): + _handle_conflict_ignore( + self, + self._option_string_actions, + action, + conflicting_actions, + ) + + +def _handle_conflict_ignore(container, option_string_actions, + new_action, conflicting_actions): + + # Remember the option strings the new action starts with so we can + # restore them as part of error reporting if we need to. + original_option_strings = new_action.option_strings + + # Remove all of the conflicting option strings from the new action + # and report an error if none are left at the end. + for option_string, action in conflicting_actions: + + # remove the conflicting option from the new action + new_action.option_strings.remove(option_string) + warnings.warn( + ('Ignoring option string {} for new action ' + 'because it conflicts with an existing option.').format( + option_string)) + + # if the option now has no option string, remove it from the + # container holding it + if not new_action.option_strings: + new_action.option_strings = original_option_strings + raise argparse.ArgumentError( + new_action, + ('Cannot resolve conflicting option string, ' + 'all names conflict.'), + ) + + +class _ArgumentGroup(argparse._ArgumentGroup): + + def _handle_conflict_ignore(self, action, conflicting_actions): + _handle_conflict_ignore( + self, + self._option_string_actions, + action, + conflicting_actions, + ) + + +class _MutuallyExclusiveGroup(argparse._MutuallyExclusiveGroup): + + def _handle_conflict_ignore(self, action, conflicting_actions): + _handle_conflict_ignore( + self, + self._option_string_actions, + action, + conflicting_actions, + ) diff --git a/cliff/command.py b/cliff/command.py index 13b872d..760832b 100644 --- a/cliff/command.py +++ b/cliff/command.py @@ -156,6 +156,7 @@ class Command(object): epilog=self.get_epilog(), prog=prog_name, formatter_class=_SmartHelpFormatter, + conflict_handler='ignore', ) for hook in self._hooks: hook.obj.get_parser(parser) diff --git a/cliff/tests/test_command.py b/cliff/tests/test_command.py index 6aecff3..29c8c33 100644 --- a/cliff/tests/test_command.py +++ b/cliff/tests/test_command.py @@ -45,7 +45,9 @@ class TestCommand(command.Command): ) parser.add_argument( '-z', - help='used in TestArgumentParser', + dest='zippy', + default='zippy-default', + help='defined in TestCommand and used in TestArgumentParser', ) return parser @@ -141,10 +143,32 @@ class TestArgumentParser(base.TestBase): cmd = TestCommand(None, None) parser = cmd.get_parser('NAME') # We should have an exception registering an option with a - # name that already exists because we do not want commands to - # override global options. + # name that already exists because we configure the argument + # parser to ignore conflicts but this option has no other name + # to be used. self.assertRaises( argparse.ArgumentError, parser.add_argument, '-z', ) + + def test_option_name_collision_with_alias(self): + cmd = TestCommand(None, None) + parser = cmd.get_parser('NAME') + # We not should have an exception registering an option with a + # name that already exists because we configure the argument + # parser to ignore conflicts and this option can be added as + # --zero even if the -z is ignored. + parser.add_argument('-z', '--zero') + + def test_resolve_option_with_name_collision(self): + cmd = TestCommand(None, None) + parser = cmd.get_parser('NAME') + parser.add_argument( + '-z', '--zero', + dest='zero', + default='zero-default', + ) + args = parser.parse_args(['-z', 'foo', 'a', 'b']) + self.assertEqual(args.zippy, 'foo') + self.assertEqual(args.zero, 'zero-default') diff --git a/doc/source/install/index.rst b/doc/source/install/index.rst index 51e0250..406bea0 100644 --- a/doc/source/install/index.rst +++ b/doc/source/install/index.rst @@ -33,7 +33,7 @@ or:: Source Code =========== -The source is hosted on github: https://git.openstack.org/cgit/openstack/cliff +The source is hosted on OpenDev: https://opendev.org/openstack/cliff Reporting Bugs ============== @@ -2,7 +2,7 @@ name = cliff description-file = README.rst author = OpenStack -author-email = openstack-dev@lists.openstack.org +author-email = openstack-discuss@lists.openstack.org summary = Command Line Interface Formulation Framework home-page = https://docs.openstack.org/cliff/latest/ classifier = @@ -12,7 +12,7 @@ classifier = Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 - Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 Intended Audience :: Developers Environment :: Console @@ -1,6 +1,6 @@ [tox] minversion = 2.0 -envlist = py35,py27,pep8 +envlist = py36,py27,pep8 [testenv] setenv = @@ -16,7 +16,7 @@ commands = python setup.py test --coverage --coverage-package-name=cliff --slowest --testr-args='{posargs}' coverage report --show-missing deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} + -c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/upper-constraints.txt} -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt |