rally/doc/ext/cli_reference.py

209 lines
7.9 KiB
Python

# Copyright 2016: Mirantis Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
import inspect
from docutils.parsers import rst
from . import utils
from rally.cli import cliutils
from rally.cli import main
class Parser(object):
"""A simplified interface of argparse.ArgumentParser"""
def __init__(self):
self.parsers = {}
self.subparser = None
self.defaults = {}
self.arguments = []
def add_parser(self, name, help=None, description=None,
formatter_class=None):
parser = Parser()
self.parsers[name] = {"description": description,
"help": help,
"fclass": formatter_class,
"parser": parser}
return parser
def set_defaults(self, command_object=None, action_fn=None,
action_kwargs=None):
if command_object:
self.defaults["command_object"] = command_object
if action_fn:
self.defaults["action_fn"] = action_fn
if action_kwargs:
self.defaults["action_kwargs"] = action_kwargs
def add_subparsers(self, dest):
# NOTE(andreykurilin): there is only one expected call
if self.subparser:
raise ValueError("Can't add one more subparser.")
self.subparser = Parser()
return self.subparser
def add_argument(self, *args, **kwargs):
if "action_args" in args:
return
self.arguments.append((args, kwargs))
DEFAULT_UUIDS_CMD = {
"deployment": ["rally deployment create"],
"task": ["rally task start"],
"verification": ["rally verify start", "rally verify import_results"]
}
def compose_note_about_default_uuids(argument, dest):
# TODO(andreykurilin): add references to commands
return utils.note(
"The default value for the ``%(arg)s`` argument is taken from "
"the Rally environment. Usually, the default value is equal to"
" the UUID of the last successful run of ``%(cmd)s``, if the "
"``--no-use`` argument was not used." % {
"arg": argument,
"cmd": "``, ``".join(DEFAULT_UUIDS_CMD[dest])})
def compose_use_cmd_hint_msg(cmd):
return utils.hint(
"You can set the default value by executing ``%(cmd)s <uuid>``"
" (ref__).\n\n __ #%(ref)s" % {"cmd": cmd,
"ref": cmd.replace(" ", "-")})
def make_arguments_section(category_name, cmd_name, arguments, defaults):
elements = [utils.paragraph("**Command arguments**:")]
for args, kwargs in arguments:
# for future changes...
# :param args: a single command argument which can represented by
# several names(for example, --uuid and --task-id) in cli.
# :type args: tuple
# :param kwargs: description of argument. Have next format:
# {"dest": "action_kwarg_<name of keyword argument in code>",
# "help": "just a description of argument"
# "metavar": "[optional] metavar of argument. Example:"
# "Example: argument '--file'; metavar 'path' ",
# "type": "[optional] class object of argument's type",
# "required": "[optional] boolean value"}
# :type kwargs: dict
dest = kwargs.get("dest").replace("action_kwarg_", "")
description = []
if cmd_name != "use":
# lets add notes about specific default values and hint about
# "use" command with reference
if dest in ("deployment", "task"):
description.append(compose_note_about_default_uuids(
args[0], dest))
description.append(
compose_use_cmd_hint_msg("rally %s use" % dest))
elif dest == "verification":
description.append(compose_note_about_default_uuids(
args[0], dest))
description.append(
compose_use_cmd_hint_msg("rally verify use"))
description.append(kwargs.get("help"))
action = kwargs.get("action")
if not action:
arg_type = kwargs.get("type")
if arg_type:
description.append("**Type**: %s" % arg_type.__name__)
skip_default = dest in ("deployment",
"task_id",
"verification")
if not skip_default and dest in defaults:
description.append("**Default**: %s" % defaults[dest])
metavar = kwargs.get("metavar")
ref = "%s_%s_%s" % (category_name, cmd_name, args[0].replace("-", ""))
if metavar:
args = ["%s %s" % (arg, metavar) for arg in args]
elements.extend(utils.make_definition(", ".join(args),
ref, description))
return elements
def get_defaults(func):
"""Return a map of argument:default_value for specified function."""
spec = inspect.getfullargspec(func)
if spec.defaults:
return dict(zip(spec.args[-len(spec.defaults):], spec.defaults))
return {}
def make_command_section(category_name, name, parser):
section = utils.subcategory("rally %s %s" % (category_name, name))
section.extend(utils.parse_text(parser["description"]))
if parser["parser"].arguments:
defaults = get_defaults(parser["parser"].defaults["action_fn"])
section.extend(make_arguments_section(
category_name, name, parser["parser"].arguments, defaults))
return section
def make_category_section(name, parser):
category_obj = utils.category("Category: %s" % name)
# NOTE(andreykurilin): we are re-using `_add_command_parsers` method from
# `rally.cli.cliutils`, but, since it was designed to print help message,
# generated description for categories contains specification for all
# sub-commands. We don't need information about sub-commands at this point,
# so let's skip "generated description" and take it directly from category
# class.
description = parser.defaults["command_object"].__doc__
# TODO(andreykurilin): write a decorator which will mark cli-class as
# deprecated without changing its docstring.
if description.startswith("[Deprecated"):
i = description.find("]")
msg = description[1:i]
description = description[i + 1:].strip()
category_obj.append(utils.warning(msg))
category_obj.extend(utils.parse_text(description))
for command in sorted(parser.subparser.parsers.keys()):
subparser = parser.subparser.parsers[command]
category_obj.append(make_command_section(name, command, subparser))
return category_obj
class CLIReferenceDirective(rst.Directive):
optional_arguments = 1
option_spec = {"group": str}
def run(self):
parser = Parser()
categories = copy.copy(main.categories)
if "group" in self.options:
categories = {k: v for k, v in categories.items()
if k == self.options["group"]}
cliutils._add_command_parsers(categories, parser)
content = []
for cg in sorted(categories.keys()):
content.append(make_category_section(
cg, parser.parsers[cg]["parser"]))
return content
def setup(app):
app.add_directive("make_cli_reference", CLIReferenceDirective)