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
This commit is contained in:
parent
b226f80820
commit
c586091172
@ -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
|
||||
---
|
||||
|
303
os_doc_tools/commands.py
Normal file
303
os_doc_tools/commands.py
Normal file
@ -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 = """<?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
||||
<section xmlns=\"http://docbook.org/ns/docbook\"
|
||||
xmlns:xi=\"http://www.w3.org/2001/XInclude\"
|
||||
xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"5.0\"
|
||||
xml:id=\"{0}client_commands\">
|
||||
|
||||
<!-- This file is automatically generated, do not edit -->
|
||||
|
||||
<?dbhtml stop-chunking?>
|
||||
|
||||
<title>{0} commands</title>
|
||||
<para>The {0} client is the command-line interface (CLI) for the
|
||||
{1} and its extensions.</para>
|
||||
<para>For help on a specific <command>{0}</command>
|
||||
command, enter:
|
||||
</para>
|
||||
<screen><prompt>$</prompt> <userinput><command>{0}</command> \
|
||||
<option>help</option> <replaceable>COMMAND</replaceable></userinput></screen>
|
||||
|
||||
<section xml:id=\"{0}client_command_usage\">
|
||||
<title>{0} usage</title>\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 '<subcommand>' 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("</computeroutput></screen>\n")
|
||||
os_file.write(" </section>\n")
|
||||
os_file.write(" <section ")
|
||||
os_file.write("xml:id=\"%sclient_command_optional\">\n"
|
||||
% os_command)
|
||||
os_file.write(" <title>%s optional arguments</title>\n"
|
||||
% os_command)
|
||||
next_line_screen = True
|
||||
ignore_next_lines = False
|
||||
continue
|
||||
# swift
|
||||
if line.startswith('Examples:'):
|
||||
os_file.write("</computeroutput></screen>\n")
|
||||
os_file.write(" </section>\n")
|
||||
os_file.write(" <section ")
|
||||
os_file.write("xml:id=\"%sclient_command_examples\">\n"
|
||||
% os_command)
|
||||
os_file.write(" <title>%s examples</title>\n"
|
||||
% os_command)
|
||||
next_line_screen = True
|
||||
continue
|
||||
continue
|
||||
if '<subcommand> ...' in line:
|
||||
os_file.write("%s</computeroutput></screen>\n" % xline)
|
||||
os_file.write(" </section>\n")
|
||||
os_file.write(" <section xml:id=\"%sclient_command_pos\">\n"
|
||||
% os_command)
|
||||
os_file.write(" <title>%s positional arguments</title>\n"
|
||||
% os_command)
|
||||
ignore_next_lines = True
|
||||
continue
|
||||
if not ignore_next_lines:
|
||||
if next_line_screen:
|
||||
os_file.write(" <screen><computeroutput>%s\n" % xline)
|
||||
next_line_screen = False
|
||||
elif len(line) > 0:
|
||||
os_file.write("%s\n" % (xline))
|
||||
|
||||
os_file.write("</computeroutput></screen>\n")
|
||||
os_file.write(" </section>\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(" <section xml:id=\"%sclient_subcommand_%s\">\n"
|
||||
% (os_command, os_subcommand))
|
||||
os_file.write(" <title>%s %s command</title>\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(" <screen><computeroutput>%s\n" % xline)
|
||||
next_line_screen = False
|
||||
else:
|
||||
os_file.write("%s\n" % (xline))
|
||||
|
||||
os_file.write("</computeroutput></screen>\n")
|
||||
os_file.write(" </section>\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("</section>\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())
|
Loading…
Reference in New Issue
Block a user