diff options
-rw-r--r-- | .gitreview | 2 | ||||
-rw-r--r-- | .travis.yml | 8 | ||||
-rw-r--r-- | .zuul.yaml | 13 | ||||
-rw-r--r-- | cliff/app.py | 9 | ||||
-rw-r--r-- | cliff/command.py | 4 | ||||
-rw-r--r-- | cliff/commandmanager.py | 5 | ||||
-rw-r--r-- | cliff/help.py | 5 | ||||
-rw-r--r-- | cliff/interactive.py | 40 | ||||
-rw-r--r-- | cliff/sphinxext.py | 12 | ||||
-rw-r--r-- | cliff/utils.py | 7 | ||||
-rw-r--r-- | demoapp/cliffdemo/__main__.py | 6 | ||||
-rw-r--r-- | doc/requirements.txt | 4 | ||||
-rw-r--r-- | lower-constraints.txt | 37 | ||||
-rw-r--r-- | requirements.txt | 7 | ||||
-rw-r--r-- | setup.cfg | 2 | ||||
-rw-r--r-- | test-requirements.txt | 2 | ||||
-rw-r--r-- | tox.ini | 10 |
17 files changed, 140 insertions, 33 deletions
@@ -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 @@ -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 @@ -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 @@ -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 |