summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitreview2
-rw-r--r--.zuul.yaml4
-rw-r--r--README.rst2
-rw-r--r--cliff/_argparse.py80
-rw-r--r--cliff/command.py1
-rw-r--r--cliff/tests/test_command.py30
-rw-r--r--doc/source/install/index.rst2
-rw-r--r--setup.cfg4
-rw-r--r--tox.ini4
9 files changed, 115 insertions, 14 deletions
diff --git a/.gitreview b/.gitreview
index 0207650..62b455a 100644
--- a/.gitreview
+++ b/.gitreview
@@ -1,4 +1,4 @@
[gerrit]
-host=review.openstack.org
+host=review.opendev.org
port=29418
project=openstack/cliff.git
diff --git a/.zuul.yaml b/.zuul.yaml
index 45543e0..739a4e4 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -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:
diff --git a/README.rst b/README.rst
index e72ba11..b3a0f18 100644
--- a/README.rst
+++ b/README.rst
@@ -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
==============
diff --git a/setup.cfg b/setup.cfg
index eb03f01..195f56f 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -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
diff --git a/tox.ini b/tox.ini
index 4680a6e..f642ee9 100644
--- a/tox.ini
+++ b/tox.ini
@@ -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