From c586091172c97021763cfc2d2fb3d1880371e097 Mon Sep 17 00:00:00 2001 From: Andreas Jaeger Date: Tue, 21 Jan 2014 19:41:10 +0100 Subject: [PATCH] Add tool to document cli tools Call "os_doc_tools/commands.py" nova to generate a file section_cli_nova_commands.xml with the command line options. Allows generation of all supported clients with --all command. The tool gets installed as: openstack-auto-commands blueprint os-user-doc Change-Id: Ie4b200818bd585d3fce0b27f0639ad03069b3b7b --- README.rst | 2 + os_doc_tools/commands.py | 303 +++++++++++++++++++++++++++++++++++++++ setup.cfg | 1 + 3 files changed, 306 insertions(+) create mode 100644 os_doc_tools/commands.py diff --git a/README.rst b/README.rst index 1e905883..55a14b23 100644 --- a/README.rst +++ b/README.rst @@ -71,6 +71,8 @@ Release notes * New option --exceptions-file to pass list of files to ignore completely * Major improvements for automatic generation of option tables + * New tool openstack-auto-commands to document python + command line clients 0.3 --- diff --git a/os_doc_tools/commands.py b/os_doc_tools/commands.py new file mode 100644 index 00000000..eb9e8a1a --- /dev/null +++ b/os_doc_tools/commands.py @@ -0,0 +1,303 @@ +#!/usr/bin/env python + +# 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 argparse +import subprocess +import sys + +import os_doc_tools + + +# NOTE(berendt): check_output as provided in Python 2.7.5 to make script +# usable with Python < 2.7 +def check_output(*popenargs, **kwargs): + """Run command with arguments and return its output as a byte string. + + If the exit code was non-zero it raises a CalledProcessError. The + CalledProcessError object will have the return code in the returncode + attribute and output in the output attribute. + """ + if 'stdout' in kwargs: + raise ValueError('stdout argument not allowed, it will be overridden.') + process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs) + output, unused_err = process.communicate() + retcode = process.poll() + if retcode: + cmd = kwargs.get("args") + if cmd is None: + cmd = popenargs[0] + raise subprocess.CalledProcessError(retcode, cmd, output=output) + return output + + +def quote_xml(line): + """Convert special characters for XML output.""" + + return line.replace('&', '&').replace('<', '<') + + +def generate_heading(os_command, api_name, os_file): + """Write DocBook file header. + + :param os_command: client command to document + :param api_name: string description of the API of os_command + :param os_file: open filehandle for output of DocBook file + """ + + print("Documenting '%s help'" % os_command) + + header = """ +
+ + + + + + {0} commands + The {0} client is the command-line interface (CLI) for the + {1} and its extensions. + For help on a specific {0} + command, enter: + + $ {0} \ + COMMAND + +
+ {0} usage\n""" + + os_file.write(header.format(os_command, api_name)) + + +def generate_command(os_command, os_file): + """Convert os_command --help to DocBook. + + :param os_command: client command to document + :param os_file: open filehandle for output of DocBook file + """ + + help_lines = check_output([os_command, "--help"]).split('\n') + + ignore_next_lines = False + next_line_screen = True + for line in help_lines: + xline = quote_xml(line) + if len(line) > 0 and line[0] != ' ': + if '' in line: + ignore_next_lines = False + continue + if 'Positional arguments' in line: + ignore_next_lines = False + next_line_screen = True + continue + if line.startswith(('Optional arguments:', 'Optional:', + 'Options:')): + os_file.write("\n") + os_file.write("
\n") + os_file.write("
\n" + % os_command) + os_file.write(" %s optional arguments\n" + % os_command) + next_line_screen = True + ignore_next_lines = False + continue + # swift + if line.startswith('Examples:'): + os_file.write("\n") + os_file.write("
\n") + os_file.write("
\n" + % os_command) + os_file.write(" %s examples\n" + % os_command) + next_line_screen = True + continue + continue + if ' ...' in line: + os_file.write("%s\n" % xline) + os_file.write("
\n") + os_file.write("
\n" + % os_command) + os_file.write(" %s positional arguments\n" + % os_command) + ignore_next_lines = True + continue + if not ignore_next_lines: + if next_line_screen: + os_file.write(" %s\n" % xline) + next_line_screen = False + elif len(line) > 0: + os_file.write("%s\n" % (xline)) + + os_file.write("\n") + os_file.write("
\n") + + +def generate_subcommand(os_command, os_subcommand, os_file): + """Convert os_command help os_subcommand to DocBook. + + :param os_command: client command to document + :param os_subcommand: client subcommand to document + :param os_file: open filehandle for output of DocBook file + """ + + if os_command == "swift": + help_lines = check_output([os_command, os_subcommand, + "--help"]).split('\n') + else: + help_lines = check_output([os_command, "help", + os_subcommand]).split('\n') + + os_file.write("
\n" + % (os_command, os_subcommand)) + os_file.write(" %s %s command\n" + % (os_command, os_subcommand)) + + next_line_screen = True + for line in help_lines: + xline = quote_xml(line) + if next_line_screen: + os_file.write(" %s\n" % xline) + next_line_screen = False + else: + os_file.write("%s\n" % (xline)) + + os_file.write("\n") + os_file.write("
\n") + + +def generate_subcommands(os_command, os_file, blacklist, only_subcommands): + """Convert os_command help subcommands for all subcommands to DocBook. + + :param os_command: client command to document + :param os_file: open filehandle for output of DocBook file + :param blacklist: list of elements that will not be documented + :param only_subcommands: if not empty, list of subcommands to document + """ + + print("Documenting '%s' subcommands..." % os_command) + blacklist.append("bash-completion") + blacklist.append("complete") + blacklist.append("help") + if not only_subcommands: + all_options = check_output([os_command, + "bash-completion"]).strip().split() + else: + all_options = only_subcommands + + subcommands = [o for o in all_options if not + (o.startswith('-') or o in blacklist)] + for subcommand in sorted(subcommands): + generate_subcommand(os_command, subcommand, os_file) + print ("%d subcommands documented." % len(subcommands)) + + +def generate_end(os_file): + """Finish writing file. + + :param os_file: open filehandle for output of DocBook file + """ + + print("Finished.\n") + os_file.write("
\n") + + +def document_single_project(os_command): + """Create documenation for os_command.""" + + print ("Documenting '%s'" % os_command) + + blacklist = [] + subcommands = [] + if os_command == 'ceilometer': + api_name = "OpenStack Telemetry API" + blacklist = ["alarm-create"] + elif os_command == 'cinder': + api_name = "OpenStack Block Storage API" + elif os_command == 'glance': + api_name = 'OpenStack Image Service API' + # Does not know about bash-completion yet, need to specify + # subcommands manually + subcommands = ["image-create", "image-delete", "image-list", + "image-show", "image-update", "member-create", + "member-delete", "member-list"] + elif os_command == 'heat': + api_name = "OpenStack Orchestration API" + blacklist = ["create", "delete", "describe", "event", + "gettemplate", "list", "resource", + "update", "validate"] + elif os_command == 'keystone': + api_name = "OpenStack Identity API" + elif os_command == 'neutron': + api_name = "OpenStack Networking API" + elif os_command == 'nova': + api_name = "OpenStack Compute API" + blacklist = ["add-floating-ip", "remove-floating-ip"] + elif os_command == 'swift': + api_name = "OpenStack Object Storage API" + # Does not know about bash-completion yet, need to specify + # subcommands manually + subcommands = ["delete", "download", "list", "post", + "stat", "upload"] + elif os_command == 'trove': + api_name = "OpenStack Database API" + else: + print("Not yet handled command") + sys.exit(-1) + + os_file = open("section_cli_" + os_command + "_commands.xml", + 'w') + generate_heading(os_command, api_name, os_file) + generate_command(os_command, os_file) + generate_subcommands(os_command, os_file, blacklist, + subcommands) + generate_end(os_file) + os_file.close() + + +def main(): + print("OpenStack Auto Documenting of Commands (using " + "openstack-doc-tools version %s)\n" + % os_doc_tools.__version__) + + parser = argparse.ArgumentParser(description="Generate DocBook XML files " + "to document python-PROJECTclients") + parser.add_argument('client', nargs='?', + help="OpenStack command to document") + parser.add_argument("--all", help="Document all clients ", + action="store_true") + prog_args = parser.parse_args() + + if prog_args.all: + document_single_project("ceilometer") + document_single_project("cinder") + document_single_project("glance") + document_single_project("heat") + document_single_project("keystone") + document_single_project("nova") + document_single_project("neutron") + document_single_project("swift") + document_single_project("trove") + elif prog_args.client is None: + print("Pass the name of the client to document as argument.") + sys.exit(1) + else: + document_single_project(prog_args.client) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/setup.cfg b/setup.cfg index fcef84ce..08fa2cb2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -38,6 +38,7 @@ setup-hooks = console_scripts = openstack-doc-test = os_doc_tools.doctest:main openstack-autohelp = autogenerate_config_docs.autohelp:main + openstack-auto-commands = os_doc_tools.commands:main [build_sphinx] source-dir = doc/source