summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitreview2
-rw-r--r--.travis.yml8
-rw-r--r--.zuul.yaml13
-rw-r--r--cliff/app.py9
-rw-r--r--cliff/command.py4
-rw-r--r--cliff/commandmanager.py5
-rw-r--r--cliff/help.py5
-rw-r--r--cliff/interactive.py40
-rw-r--r--cliff/sphinxext.py12
-rw-r--r--cliff/utils.py7
-rw-r--r--demoapp/cliffdemo/__main__.py6
-rw-r--r--doc/requirements.txt4
-rw-r--r--lower-constraints.txt37
-rw-r--r--requirements.txt7
-rw-r--r--setup.cfg2
-rw-r--r--test-requirements.txt2
-rw-r--r--tox.ini10
17 files changed, 140 insertions, 33 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/.travis.yml b/.travis.yml
deleted file mode 100644
index ea35c0e..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-language: python
-python:
- - 2.7
- - 3.2
- - 3.3
- - pypy
-install: pip install -r test-requirements.txt
-script: nosetests -d \ No newline at end of file
diff --git a/.zuul.yaml b/.zuul.yaml
index 6352396..e825123 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -17,10 +17,19 @@
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:
- name: openstack/cliff
+ templates:
+ - check-requirements
+ - lib-forward-testing
+ - 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:
- cliff-tox-py27-neutronclient-tip
diff --git a/cliff/app.py b/cliff/app.py
index 62f822e..94b3d76 100644
--- a/cliff/app.py
+++ b/cliff/app.py
@@ -14,7 +14,6 @@
"""
import codecs
-import inspect
import locale
import logging
import logging.handlers
@@ -55,6 +54,8 @@ class App(object):
"""
NAME = os.path.splitext(os.path.basename(sys.argv[0]))[0]
+ if NAME == '__main__':
+ NAME = os.path.split(os.path.dirname(sys.argv[0]))[-1]
LOG = logging.getLogger(NAME)
CONSOLE_MESSAGE_FORMAT = '%(message)s'
@@ -151,7 +152,7 @@ class App(object):
parser.add_argument(
'--version',
action='version',
- version='%(prog)s {0}'.format(version),
+ version='{0} {1}'.format(App.NAME, version),
)
verbose_group = parser.add_mutually_exclusive_group()
verbose_group.add_argument(
@@ -243,6 +244,7 @@ class App(object):
:param argv: input arguments and options
:paramtype argv: list of str
"""
+
try:
self.options, remainder = self.parser.parse_known_args(argv)
self.configure_logging()
@@ -384,10 +386,9 @@ class App(object):
return 2
cmd_factory, cmd_name, sub_argv = subcommand
kwargs = {}
- if 'cmd_name' in inspect.getargspec(cmd_factory.__init__).args:
+ if 'cmd_name' in utils.getargspec(cmd_factory.__init__).args:
kwargs['cmd_name'] = cmd_name
cmd = cmd_factory(self, self.options, **kwargs)
- err = None
result = 1
try:
self.prepare_to_run_command(cmd)
diff --git a/cliff/command.py b/cliff/command.py
index 0a63f88..760832b 100644
--- a/cliff/command.py
+++ b/cliff/command.py
@@ -192,7 +192,7 @@ class Command(object):
This method is intended to be called from the run() method before
take_action() is called.
- This method should only be overriden by developers creating new
+ This method should only be overridden by developers creating new
command base classes and only if it is necessary to have different
hook processing behavior.
"""
@@ -210,7 +210,7 @@ class Command(object):
This method is intended to be called from the run() method after
take_action() is called.
- This method should only be overriden by developers creating new
+ This method should only be overridden by developers creating new
command base classes and only if it is necessary to have different
hook processing behavior.
"""
diff --git a/cliff/commandmanager.py b/cliff/commandmanager.py
index a0a9bb3..20cf17f 100644
--- a/cliff/commandmanager.py
+++ b/cliff/commandmanager.py
@@ -13,11 +13,12 @@
"""Discover and lookup command plugins.
"""
-import inspect
import logging
import pkg_resources
+from . import utils
+
LOG = logging.getLogger(__name__)
@@ -103,7 +104,7 @@ class CommandManager(object):
else:
# NOTE(dhellmann): Some fake classes don't take
# require as an argument. Yay?
- arg_spec = inspect.getargspec(cmd_ep.load)
+ arg_spec = utils.getargspec(cmd_ep.load)
if 'require' in arg_spec[0]:
cmd_factory = cmd_ep.load(require=False)
else:
diff --git a/cliff/help.py b/cliff/help.py
index 971e9ad..92e4c2f 100644
--- a/cliff/help.py
+++ b/cliff/help.py
@@ -16,6 +16,7 @@ import sys
import traceback
from . import command
+from . import utils
class HelpAction(argparse.Action):
@@ -47,7 +48,7 @@ class HelpAction(argparse.Action):
continue
try:
kwargs = {}
- if 'cmd_name' in inspect.getargspec(factory.__init__).args:
+ if 'cmd_name' in utils.getargspec(factory.__init__).args:
kwargs['cmd_name'] = name
cmd = factory(app, None, **kwargs)
if cmd.deprecated:
@@ -100,7 +101,7 @@ class HelpCommand(command.Command):
return
self.app_args.cmd = search_args
kwargs = {}
- if 'cmd_name' in inspect.getargspec(cmd_factory.__init__).args:
+ if 'cmd_name' in utils.getargspec(cmd_factory.__init__).args:
kwargs['cmd_name'] = cmd_name
cmd = cmd_factory(self.app, self.app_args, **kwargs)
full_name = (cmd_name
diff --git a/cliff/interactive.py b/cliff/interactive.py
index 74b5f54..f5afa1b 100644
--- a/cliff/interactive.py
+++ b/cliff/interactive.py
@@ -26,7 +26,7 @@ class InteractiveApp(cmd2.Cmd):
Refer to the cmd2_ and cmd_ documentation for details
about subclassing and configuring this class.
- .. _cmd2: http://packages.python.org/cmd2/index.html
+ .. _cmd2: https://cmd2.readthedocs.io/en/latest/
.. _cmd: http://docs.python.org/library/cmd.html
:param parent_app: The calling application (expected to be derived
@@ -51,13 +51,24 @@ class InteractiveApp(cmd2.Cmd):
self.command_manager = command_manager
cmd2.Cmd.__init__(self, 'tab', stdin=stdin, stdout=stdout)
+ def _split_line(self, line):
+ try:
+ return shlex.split(line.parsed.raw)
+ except AttributeError:
+ # cmd2 >= 0.9.1 gives us a Statement not a PyParsing parse
+ # result.
+ parts = shlex.split(line)
+ if getattr(line, 'command', None):
+ parts.insert(0, line.command)
+ return parts
+
def default(self, line):
# Tie in the default command processor to
# dispatch commands known to the command manager.
# We send the message through our parent app,
# since it already has the logic for executing
# the subcommand.
- line_parts = shlex.split(line.parsed.raw)
+ line_parts = self._split_line(line)
self.parent_app.run_subcommand(line_parts)
def completenames(self, text, line, begidx, endidx):
@@ -111,7 +122,17 @@ class InteractiveApp(cmd2.Cmd):
# Dispatch to the underlying help command,
# which knows how to provide help for extension
# commands.
- self.default(self.parsed('help ' + arg))
+ try:
+ # NOTE(coreycb): This try path can be removed once
+ # requirements.txt has cmd2 >= 0.7.3.
+ parsed = self.parsed
+ except AttributeError:
+ try:
+ parsed = self.parser_manager.parsed
+ except AttributeError:
+ # cmd2 >= 0.9.1 does not have a parser manager
+ parsed = lambda x: x # noqa
+ self.default(parsed('help ' + arg))
else:
cmd2.Cmd.do_help(self, arg)
cmd_names = sorted([n for n, v in self.command_manager])
@@ -134,7 +155,7 @@ class InteractiveApp(cmd2.Cmd):
# Pre-process the parsed command in case it looks like one of
# our subcommands, since cmd2 does not handle multi-part
# command names by default.
- line_parts = shlex.split(statement.parsed.raw)
+ line_parts = self._split_line(statement)
try:
the_cmd = self.command_manager.find_command(line_parts)
cmd_factory, cmd_name, sub_argv = the_cmd
@@ -142,8 +163,15 @@ class InteractiveApp(cmd2.Cmd):
# Not a plugin command
pass
else:
- statement.parsed.command = cmd_name
- statement.parsed.args = ' '.join(sub_argv)
+ if hasattr(statement, 'parsed'):
+ # Older cmd2 uses PyParsing
+ statement.parsed.command = cmd_name
+ statement.parsed.args = ' '.join(sub_argv)
+ else:
+ # cmd2 >= 0.9.1 uses shlex and gives us a Statement.
+ statement.command = cmd_name
+ statement.argv = [cmd_name] + sub_argv
+ statement.args = ' '.join(statement.argv)
return statement
def cmdloop(self):
diff --git a/cliff/sphinxext.py b/cliff/sphinxext.py
index d8468b6..8a9a2e6 100644
--- a/cliff/sphinxext.py
+++ b/cliff/sphinxext.py
@@ -15,6 +15,7 @@
import argparse
import fnmatch
import importlib
+import inspect
import re
import sys
@@ -242,6 +243,8 @@ class AutoprogramCliffDirective(rst.Directive):
cliff_app_class = getattr(sys.modules[mod_str], class_str)
except AttributeError:
return
+ if not inspect.isclass(cliff_app_class):
+ return
if not issubclass(cliff_app_class, app.App):
return
app_arguments = self.options.get('arguments', '').split()
@@ -268,6 +271,15 @@ class AutoprogramCliffDirective(rst.Directive):
if fnmatch.fnmatch(x, command_pattern)]
else:
commands = manager.commands.keys()
+
+ if not commands:
+ msg = 'No commands found in the "{}" namespace'
+ if command_pattern:
+ msg += ' using the "{}" command name/pattern'
+ msg += ('. Are you sure this is correct and the application being '
+ 'documented is installed?')
+ raise self.warning(msg.format(self.arguments[0], command_pattern))
+
return dict((name, self._load_command(manager, name))
for name in commands)
diff --git a/cliff/utils.py b/cliff/utils.py
index a9ee975..0da9b42 100644
--- a/cliff/utils.py
+++ b/cliff/utils.py
@@ -13,6 +13,7 @@
import codecs
import ctypes
+import inspect
import os
import struct
import sys
@@ -28,6 +29,12 @@ import six
COST = {'w': 0, 's': 2, 'a': 1, 'd': 3}
+if hasattr(inspect, 'getfullargspec'):
+ getargspec = inspect.getfullargspec
+else:
+ getargspec = inspect.getargspec
+
+
def damerau_levenshtein(s1, s2, cost):
"""Calculates the Damerau-Levenshtein distance between two strings.
diff --git a/demoapp/cliffdemo/__main__.py b/demoapp/cliffdemo/__main__.py
new file mode 100644
index 0000000..e49cbfc
--- /dev/null
+++ b/demoapp/cliffdemo/__main__.py
@@ -0,0 +1,6 @@
+import sys
+from cliffdemo.main import main
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/doc/requirements.txt b/doc/requirements.txt
index f22cc9f..827758f 100644
--- a/doc/requirements.txt
+++ b/doc/requirements.txt
@@ -1,5 +1,5 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
-sphinx>=1.6.2 # BSD
-openstackdocstheme>=1.17.0 # Apache-2.0
+sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD
+openstackdocstheme>=1.18.1 # Apache-2.0
diff --git a/lower-constraints.txt b/lower-constraints.txt
new file mode 100644
index 0000000..1e0ab80
--- /dev/null
+++ b/lower-constraints.txt
@@ -0,0 +1,37 @@
+alabaster==0.7.10
+Babel==2.3.4
+bandit==1.1.0
+cmd2==0.8.0
+coverage==4.0
+docutils==0.11
+extras==1.0.0
+fixtures==3.0.0
+gitdb==0.6.4
+GitPython==1.0.1
+imagesize==0.7.1
+Jinja2==2.10
+linecache2==1.0.0
+MarkupSafe==1.0
+mock==2.0.0
+pbr==2.0.0
+prettytable==0.7.2
+Pygments==2.2.0
+pyparsing==2.1.0
+pyperclip==1.5.27
+python-mimeparse==1.6.0
+python-subunit==1.0.0
+pytz==2013.6
+PyYAML==3.12
+requests==2.14.2
+six==1.10.0
+smmap==0.9.0
+snowballstemmer==1.2.1
+Sphinx==1.6.2
+sphinxcontrib-websupport==1.0.1
+stevedore==1.20.0
+testrepository==0.0.18
+testscenarios==0.4
+testtools==2.2.0
+traceback2==1.4.0
+unicodecsv==0.8.0;python_version<'3.0'
+unittest2==1.1.0
diff --git a/requirements.txt b/requirements.txt
index 4f26b92..e83be76 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,10 +2,11 @@
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
pbr!=2.1.0,>=2.0.0 # Apache-2.0
-cmd2>=0.6.7 # MIT
-PrettyTable<0.8,>=0.7.1 # BSD
+cmd2!=0.8.3,<0.9.0;python_version<'3.0' # MIT
+cmd2!=0.8.3;python_version>='3.0' # MIT
+PrettyTable<0.8,>=0.7.2 # BSD
pyparsing>=2.1.0 # MIT
six>=1.10.0 # MIT
stevedore>=1.20.0 # Apache-2.0
unicodecsv>=0.8.0;python_version<'3.0' # BSD
-PyYAML>=3.10 # MIT
+PyYAML>=3.12 # MIT
diff --git a/setup.cfg b/setup.cfg
index 9aba8f8..eb03f01 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -54,6 +54,8 @@ cliff.demo =
cliff.demo.hooked =
sample-hook = cliffdemo.hook:Hook
+[bdist_wheel]
+universal = 1
[build_sphinx]
all-files = 1
diff --git a/test-requirements.txt b/test-requirements.txt
index dbb6402..4755809 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -12,7 +12,7 @@ coverage!=4.4,>=4.0 # Apache-2.0
# sphinx is required in test-requirements in addition to doc/requirements
# because there is a sphinx extension that has tests
-sphinx>=1.6.2 # BSD
+sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD
# Bandit security code scanner
bandit>=1.1.0 # Apache-2.0
diff --git a/tox.ini b/tox.ini
index 4090c8b..4680a6e 100644
--- a/tox.ini
+++ b/tox.ini
@@ -21,6 +21,7 @@ deps =
-r{toxinidir}/requirements.txt
[testenv:pep8]
+basepython = python3
deps =
-r{toxinidir}/test-requirements.txt
flake8
@@ -30,6 +31,7 @@ commands =
bandit -c bandit.yaml -r cliff -x tests -n5
[testenv:venv]
+basepython = python3
# TODO(modred) remove doc/requirements.txt once the openstack-build-sphinx-docs
# job is updated.
deps =
@@ -48,5 +50,13 @@ deps = os:openstack/python-openstackclient:python-openstackclient
commands = {toxinidir}/integration-tests/openstackclient-tip.sh {envdir}
[testenv:docs]
+basepython = python3
deps = -r{toxinidir}/doc/requirements.txt
commands = sphinx-build -b html doc/source doc/build/html
+
+[testenv:lower-constraints]
+basepython = python3
+deps =
+ -c{toxinidir}/lower-constraints.txt
+ -r{toxinidir}/test-requirements.txt
+ -r{toxinidir}/requirements.txt