diff --git a/bin/doc-tools-update-cli-reference b/bin/doc-tools-update-cli-reference index 48e09297..14de9f77 100755 --- a/bin/doc-tools-update-cli-reference +++ b/bin/doc-tools-update-cli-reference @@ -89,6 +89,6 @@ git pull branch=update_client_$project git branch --list $branch && git branch -D $branch git checkout -b $branch -mv ../output/ch_cli_${project}_commands.xml doc/cli-reference/generated +mv ../output/${project}.rst "doc/cli-reference/source" version=$($project --version 2>&1) git commit -a -m "Update CLI reference for python-${project}client ${version##* }" diff --git a/os_doc_tools/commands.py b/os_doc_tools/commands.py index 0005ed96..b5db4561 100644 --- a/os_doc_tools/commands.py +++ b/os_doc_tools/commands.py @@ -14,6 +14,7 @@ import argparse import os +import re import subprocess import sys import yaml @@ -21,6 +22,7 @@ import yaml import os_doc_tools DEVNULL = open(os.devnull, 'wb') +MAXLINELENGTH = 78 def use_help_flag(os_command): @@ -33,28 +35,35 @@ def use_help_flag(os_command): return os_command == "swift" or "-manage" in os_command -def quote_xml(line): - """Convert special characters for XML output.""" +def quote_rst(line): + """Convert special characters for RST output.""" - line = line.replace('&', '&').replace('<', '<').replace('>', '>') + line = line.replace('\\', '\\\\').replace('`', '\\`').replace('*', '\\*') + + if '--' in line: + line = re.sub(r'(--[^ .\'\\]*)', r":option:`\1`", line) + # work around for "`--`" at murano + line = line.replace('\\`:option:`--`\\`', '```--```') if 'DEPRECATED!' in line: - line = line.replace('DEPRECATED!', 'DEPRECATED!') + line = line.replace('DEPRECATED!', '**DEPRECATED!**') elif 'DEPRECATED' in line: - line = line.replace('DEPRECATED', 'DEPRECATED') + line = line.replace('DEPRECATED', '**DEPRECATED**') if 'env[' in line: - line = line.replace('env[', 'env[').replace(']', ']') + line = line.replace('env[', '``env[').replace(']', ']``') + # work around for "Default=env[...]" at cinder + line = line.replace('=``', '= ``') return line def generate_heading(os_command, api_name, title, os_file): - """Write DocBook file header. + """Write RST 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 + :param os_file: open filehandle for output of RST file """ try: @@ -70,72 +79,41 @@ def generate_heading(os_command, api_name, title, os_file): print("Documenting '%s help (version %s)'" % (os_command, version)) - if use_help_flag(os_command): - help_str = "COMMAND " - else: - help_str = " COMMAND" - - header1 = """ - - - - - - - %(title)s\n""" - if os_command == "openstack": - header2 = """ - The %(os_command)s client is a common - OpenStack command-line interface (CLI).\n""" - else: - header2 = """ - The %(os_command)s client is the command-line - interface (CLI) for the %(api_name)s and its extensions.\n""" - - header3 = """ - This chapter documents %(os_command)s version - %(version)s. - - - For help on a specific %(os_command)s - command, enter: - - $ %(os_command)s \ -%(help_str)s - -
- %(os_command)s usage\n""" + os_file.write(".. ## WARNING ######################################\n") + os_file.write(".. This file is automatically generated, do not edit\n") + os_file.write(".. #################################################\n\n") + format_heading(title, 1, os_file) if os_command == "keystone": - header_deprecation = """ - - The %(os_command)s CLI is deprecated in favor of - python-openstackclient. For more information on - python-openstackclient, please see - and - . - For a Python library, continue - using python-%(os_command)sclient. - \n""" - else: - header_deprecation = None + os_file.write(".. warning::\n\n") + os_file.write(" The " + os_command + " CLI is deprecated\n") + os_file.write(" in favor of python-openstackclient.\n") + os_file.write(" For more information, see :doc:`openstack`.\n") + os_file.write(" For a Python library, continue using\n") + os_file.write(" python-" + os_command + "client.\n\n") - format_dict = { - "os_command": os_command, - "api_name": api_name, - "title": title, - "version": version, - "help_str": help_str - } - os_file.write(header1 % format_dict) - if header_deprecation: - os_file.write(header_deprecation % format_dict) - os_file.write(header2 % format_dict) - os_file.write(header3 % format_dict) + if os_command == "openstack": + os_file.write("The openstack client is a common OpenStack") + os_file.write("command-line interface (CLI).\n\n") + else: + os_file.write("The " + os_command + " client is the command-line ") + os_file.write("interface (CLI) for\n") + os_file.write("the " + api_name + " and its extensions.\n\n") + + os_file.write("This chapter documents :command:`" + os_command + "` ") + os_file.write("version ``" + version + "``.\n\n") + + os_file.write("For help on a specific :command:`" + os_command + "` ") + os_file.write("command, enter:\n\n") + + os_file.write(".. code-block:: console\n\n") + if use_help_flag(os_command): + os_file.write(" $ " + os_command + " COMMAND --help\n\n") + else: + os_file.write(" $ " + os_command + " help COMMAND\n\n") + + os_file.write(".. _" + os_command + "_command_usage:\n\n") + format_heading(os_command + " usage", 2, os_file) def is_option(string): @@ -233,13 +211,42 @@ def extract_options(line): return split_line -def format_table(title, lines, os_file): - """Nicely print section of lines.""" +def format_heading(heading, level, os_file): + """Nicely print heading. + + :param heading: heading strings + :param level: heading level + :param os_file: open filehandle for output of RST file + """ + + if level == 1: + os_file.write("=" * len(heading) + "\n") + + os_file.write(heading + "\n") + + if level == 1: + os_file.write("=" * len(heading) + "\n\n") + elif level == 2: + os_file.write("~" * len(heading) + "\n\n") + elif level == 3: + os_file.write("-" * len(heading) + "\n\n") + else: + os_file.write("\n") + + return + + +def format_help(title, lines, os_file): + """Nicely print section of lines. + + :param title: help title, if exist + :param lines: strings to format + :param os_file: open filehandle for output of RST file + """ close_entry = False - os_file.write(" \n") if title: - os_file.write(" %s\n" % title) + format_heading(title, 3, os_file) for line in lines: if not line or line[0] != ' ': @@ -252,9 +259,13 @@ def format_table(title, lines, os_file): # on next line # If there are more than 8 spaces, let's treat it as # explanation. - if line.startswith(' '): + if line.startswith(' '): # Explanation - os_file.write(" %s\n" % quote_xml(line.lstrip(' '))) + xline = quote_rst(line.lstrip(' ')) + if len(xline) > (MAXLINELENGTH - 2): + # check niceness + xline = xline.replace(' ', '\n ') + os_file.write(" " + xline + "\n") continue # Now we have a command or parameter to handle split_line = extract_options(line) @@ -262,31 +273,41 @@ def format_table(title, lines, os_file): if not close_entry: close_entry = True else: - os_file.write(" \n") - os_file.write(" \n") - os_file.write(" \n") + os_file.write("\n") + + xline = split_line[0] + + # check niceness work around for long option name + if len(xline) > (MAXLINELENGTH - 4): + xline = xline.replace(', -', ',``\n\n``-') + + # check niceness work around for long option name, openstack + xline = xline.replace('--nic \n") - os_file.write(" %s\n" - % quote_xml(split_line[0])) - os_file.write(" \n") - os_file.write(" \n") if len(split_line) > 1: - os_file.write(" %s\n" % quote_xml(split_line[1])) + xline = quote_rst(split_line[1]) + if len(xline) > (MAXLINELENGTH - 2): + # check niceness + xline = xline.replace(' ', '\n ') + os_file.write(" " + xline + "\n") - os_file.write(" \n") - os_file.write(" \n") - os_file.write(" \n") - os_file.write(" \n") + os_file.write("\n") return def generate_command(os_command, os_file): - """Convert os_command --help to DocBook. + """Convert os_command --help to RST. :param os_command: client command to document - :param os_file: open filehandle for output of DocBook file + :param os_file: open filehandle for output of RST file """ if os_command == "glance": @@ -300,13 +321,11 @@ def generate_command(os_command, os_file): ignore_next_lines = False next_line_screen = True - next_line_screen = True line_index = -1 in_screen = False subcommands = 'complete' for line in help_lines: line_index += 1 - xline = quote_xml(line) if line and line[0] != ' ': # XXX: Might have whitespace before!! if '' in line: @@ -315,69 +334,52 @@ def generate_command(os_command, os_file): if 'Positional arguments' in line: ignore_next_lines = True next_line_screen = True - os_file.write("\n") + os_file.write("\n\n") in_screen = False if os_command != "glance": - format_table('Subcommands', - help_lines[line_index + 2:], os_file) + format_help('Subcommands', + help_lines[line_index + 2:], os_file) continue if line.startswith(('Optional arguments:', 'Optional:', 'Options:', 'optional arguments')): if in_screen: - os_file.write("\n") + os_file.write("\n\n") in_screen = False - os_file.write("
\n") - os_file.write("
\n" - % os_command) - os_file.write(" %s optional arguments\n" - % os_command) - format_table('', help_lines[line_index + 1:], - os_file) + os_file.write(".. _" + os_command + "_command_options:\n\n") + format_heading(os_command + " optional arguments", 2, os_file) + format_help('', help_lines[line_index + 1:], os_file) next_line_screen = True ignore_next_lines = True continue - # sahara + # magnum and sahara if line.startswith('Common auth options'): if in_screen: - os_file.write("\n") + os_file.write("\n\n") in_screen = False - os_file.write("
\n") - os_file.write("
\n" - % os_command) - os_file.write(" %s common authentication " - "arguments\n" - % os_command) - format_table('', help_lines[line_index + 1:], - os_file) + os_file.write("\n") + os_file.write(os_command) + os_file.write(".. _" + os_command + "_common_auth:\n\n") + format_heading(os_command + " common authentication arguments", + 2, os_file) + format_help('', help_lines[line_index + 1:], os_file) next_line_screen = True ignore_next_lines = True continue # neutron if line.startswith('Commands for API v2.0:'): if in_screen: - os_file.write("\n") + os_file.write("\n\n") in_screen = False - os_file.write("
\n") - os_file.write("
\n" - % os_command) - os_file.write(" %s API v2.0 commands\n" - % os_command) - format_table('', help_lines[line_index + 1:], - os_file) + os_file.write(".. _" + os_command + "_common_api_v2:\n\n") + format_heading(os_command + " API v2.0 commands", 2, os_file) + format_help('', help_lines[line_index + 1:], os_file) next_line_screen = True ignore_next_lines = True continue # swift if line.startswith('Examples:'): - os_file.write("
\n") - os_file.write("
\n" - % os_command) - os_file.write(" %s examples\n" - % os_command) + os_file.write(".. _" + os_command + "_examples:\n\n") + format_heading(os_command + " examples", 2, os_file) next_line_screen = True ignore_next_lines = False continue @@ -385,31 +387,31 @@ def generate_command(os_command, os_file): continue if not ignore_next_lines: if next_line_screen: - os_file.write(" %s" % xline) + os_file.write(".. code-block:: console\n\n") + os_file.write(" " + line) next_line_screen = False in_screen = True elif line: - os_file.write("\n%s" % xline.rstrip()) + os_file.write("\n " + line.rstrip()) # subcommands (select bash-completion, complete for bash-completion) if 'bash-completion' in line: subcommands = 'bash-completion' if in_screen: - os_file.write("\n") + os_file.write("\n\n") - os_file.write("
\n") return subcommands def generate_subcommand(os_command, os_subcommand, os_file, extra_params, suffix, title_suffix): - """Convert os_command help os_subcommand to DocBook. + """Convert os_command help os_subcommand to RST. :param os_command: client command to document :param os_subcommand: client subcommand to document - :param os_file: open filehandle for output of DocBook file + :param os_file: open filehandle for output of RST file :param extra_params: Extra parameter to pass to os_command - :param suffix: Extra suffix to add to xml:id + :param suffix: Extra suffix to add to link ID :param title_suffix: Extra suffix for title """ @@ -440,17 +442,14 @@ def generate_subcommand(os_command, os_subcommand, os_file, extra_params, help_lines = help_lines.split('\n') os_subcommandid = os_subcommand.replace(' ', '_') - os_file.write("
\n" - % (os_command, os_subcommandid, suffix)) - os_file.write(" %s %s%s\n" - % (os_command, os_subcommand, title_suffix)) + os_file.write(".. _" + os_command + "_" + os_subcommandid + suffix) + os_file.write(":\n\n") + format_heading(os_command + " " + os_subcommand + title_suffix, 2, os_file) if os_command == "swift": next_line_screen = False - os_file.write("\n Usage: swift %s" - "" - % (os_subcommand)) - os_file.write("\n ") + os_file.write(".. code-block:: console\n\n") + os_file.write("Usage: swift " + os_subcommand + "\n\n") in_para = True else: next_line_screen = True @@ -477,39 +476,47 @@ def generate_subcommand(os_command, os_subcommand, os_file, extra_params, 'optional arguments')): if in_para: in_para = False - os_file.write("\n ") + os_file.write("\n") if line.startswith(('Positional arguments', 'positional arguments')): - format_table('Positional arguments', - help_lines[line_index + 1:], os_file) + format_help('Positional arguments', + help_lines[line_index + 1:], os_file) skip_lines = True continue elif line.startswith(('Optional arguments:', 'optional arguments')): - format_table('Optional arguments', - help_lines[line_index + 1:], os_file) + format_help('Optional arguments', + help_lines[line_index + 1:], os_file) break else: - format_table('Arguments', help_lines[line_index + 1:], os_file) + format_help('Arguments', help_lines[line_index + 1:], os_file) break if skip_lines: continue if not line: if not in_para: - os_file.write("") - os_file.write("\n ") + os_file.write("\n") in_para = True continue - xline = quote_xml(line) if next_line_screen: - os_file.write(" %s" % xline) + os_file.write(".. code-block:: console\n\n") + os_file.write(" " + line + "\n") next_line_screen = False + elif line.startswith(' '): + # ceilometer alarm-gnocchi-aggregation-by-metrics-threshold-create + # has 7 white space indentation + if not line.isspace(): + # skip blank line, such as "trove help cluster-grow" command. + os_file.write(" " + line + "\n") else: - os_file.write("\n%s" % (xline)) + xline = quote_rst(line) + if (len(xline) > MAXLINELENGTH): + # check niceness + xline = xline.replace(' ', '\n') + os_file.write(xline + "\n") if in_para: - os_file.write("\n \n") - os_file.write("
\n") + os_file.write("\n") def discover_subcommands(os_command, subcommands, extra_params): @@ -558,14 +565,14 @@ def discover_subcommands(os_command, subcommands, extra_params): def generate_subcommands(os_command, os_file, subcommands, extra_params, suffix, title_suffix): - """Convert os_command help subcommands for all subcommands to DocBook. + """Convert os_command help subcommands for all subcommands to RST. :param os_command: client command to document - :param os_file: open filehandle for output of DocBook file + :param os_file: open filehandle for output of RST file :param subcommands: list or type ('complete' or 'bash-completion') of subcommands to document :param extra_params: Extra parameter to pass to os_command. - :param suffix: Extra suffix to add to xml:id + :param suffix: Extra suffix to add to link ID :param title_suffix: Extra suffix for title """ for subcommand in subcommands: @@ -576,14 +583,14 @@ def generate_subcommands(os_command, os_file, subcommands, extra_params, def discover_and_generate_subcommands(os_command, os_file, subcommands, extra_params, suffix, title_suffix): - """Convert os_command help subcommands for all subcommands to DocBook. + """Convert os_command help subcommands for all subcommands to RST. :param os_command: client command to document - :param os_file: open filehandle for output of DocBook file + :param os_file: open filehandle for output of RST file :param subcommands: list or type ('complete' or 'bash-completion') of subcommands to document :param extra_params: Extra parameter to pass to os_command. - :param suffix: Extra suffix to add to xml:id + :param suffix: Extra suffix to add to link ID :param title_suffix: Extra suffix for title """ subcommands = discover_subcommands(os_command, subcommands, extra_params) @@ -591,16 +598,6 @@ def discover_and_generate_subcommands(os_command, os_file, subcommands, suffix, title_suffix) -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 get_clients(): """Load client definitions from the resource file.""" fname = os.path.join(os.path.dirname(__file__), @@ -628,21 +625,18 @@ def document_single_project(os_command, output_dir): api_name = '' title = data.get('title', '') - out_filename = "ch_cli_" + os_command + "_commands.xml" + out_filename = os_command + ".rst" out_file = open(os.path.join(output_dir, out_filename), 'w') generate_heading(os_command, api_name, title, out_file) subcommands = generate_command(os_command, out_file) if os_command == 'cinder': - out_file.write(""" -
- Block Storage API v1 commands (DEPRECATED)\n""") + format_heading("Block Storage API v1 commands (DEPRECATED)", + 2, out_file) discover_and_generate_subcommands(os_command, out_file, subcommands, None, "", "") elif os_command == 'openstack': - out_file.write(""" -
- OpenStack with Identity API v2 commands\n""") + format_heading("OpenStack with Identity API v2 commands", 2, out_file) auth_type_token = ["--os-auth-type", "token"] identity_api_v2 = ["--os-identity-api-version", "2"] extra_params = auth_type_token + identity_api_v2 @@ -651,9 +645,7 @@ def document_single_project(os_command, output_dir): generate_subcommands(os_command, out_file, subcommands_v2, extra_params, "_with_identity_api_v2", "") elif os_command == 'glance': - out_file.write(""" -
- Image service API v1 commands\n""") + format_heading("Image service API v1 commands", 2, out_file) discover_and_generate_subcommands(os_command, out_file, subcommands, ["--os-image-api-version", "1"], "_v1", " (v1)") @@ -663,40 +655,37 @@ def document_single_project(os_command, output_dir): # Print subcommands for different API versions if os_command == 'cinder': - out_file.write("
\n") - out_file.write(""" -
- Block Storage API v2 commands - - You can select an API version to use by adding the - --os-volume-api-version parameter or by setting - the corresponding environment variable:\n""") - out_file.write("$ " - "export OS_VOLUME_API_VERSION=2\n" - "\n") + out_file.write("\n") + format_heading("Block Storage API v2 commands", 2, out_file) + + out_file.write("You can select an API version to use by adding the\n") + out_file.write(":option:`--os-volume-api-version` parameter or by\n") + out_file.write("setting the corresponding environment variable:\n\n") + + out_file.write(".. code-block:: console\n\n") + out_file.write(" export OS_VOLUME_API_VERSION=2\n\n") discover_and_generate_subcommands(os_command, out_file, subcommands, ["--os-volume-api-version", "2"], "_v2", " (v2)") - out_file.write("
\n") if os_command == 'openstack': # Print the additional subcommands possible by using v3 of identity API - out_file.write(""" -
\n -
- OpenStack with Identity API v3 commands (diff) - - You can select the Identity API version to use by adding the - --os-identity-api-version parameter or by setting - the corresponding environment variable:\n""") - out_file.write("$ " - "export OS_IDENTITY_API_VERSION=3" - "\n\n") - out_file.write("\n" - "This section documents only the difference in" - " subcommands available for the openstack client when" - " the identity API version is changed from v2 to v3.\n" - "\n") + out_file.write("\n") + format_heading("OpenStack with Identity API v3 commands (diff)", + 2, out_file) + + out_file.write("You can select the Identity API version to use by\n") + out_file.write("adding the :option:`--os-identity-api-version`\n") + out_file.write("parameter or by setting the corresponding\n") + out_file.write("environment variable:\n\n") + + out_file.write(".. code-block:: console\n\n") + out_file.write(" export OS_IDENTITY_API_VERSION=3\n\n") + + out_file.write("This section documents only the difference in\n") + out_file.write("subcommands available for the openstack client\n") + out_file.write("when the Identity API version is changed from\n") + out_file.write("v2 to v3.\n\n") identity_api_v3 = ["--os-identity-api-version", "3"] extra_params = auth_type_token + identity_api_v3 @@ -707,30 +696,24 @@ def document_single_project(os_command, output_dir): generate_subcommands(os_command, out_file, subcommands_delta, extra_params, "_with_identity_api_v3", " (Identity API v3)") - out_file.write("
\n") if os_command == 'glance': - out_file.write(""" -
\n -
- Image service API v2 commands - - You can select an API version to use by adding the - --os-image-api-version parameter or by setting - the corresponding environment variable:\n""") - out_file.write("$ " - "export OS_IMAGE_API_VERSION=2\n" - "\n") + out_file.write("\n") + format_heading("Image service API v2 commands", 2, out_file) + out_file.write("You can select an API version to use by adding the\n") + out_file.write(":option:`--os-image-api-version` parameter or by\n") + out_file.write("setting the corresponding environment variable:\n\n") + + out_file.write(".. code-block:: console\n\n") + out_file.write(" export OS_IMAGE_API_VERSION=2\n\n") discover_and_generate_subcommands(os_command, out_file, subcommands, ["--os-image-api-version", "2"], "_v2", " (v2)") - out_file.write("
\n") if os_command == 'glance': - out_file.write(" \n") + out_file.write(".. include:: glance_property_keys.rst\n") - generate_end(out_file) + print("Finished.\n") out_file.close() @@ -744,7 +727,7 @@ def main(): manage_clients = sorted([x for x in clients if x.endswith('-manage')]) all_clients = api_clients + manage_clients - parser = argparse.ArgumentParser(description="Generate DocBook XML files " + parser = argparse.ArgumentParser(description="Generate RST files " "to document python-PROJECTclients.") parser.add_argument('client', nargs='?', help="OpenStack command to document. One of: " + diff --git a/releasenotes/notes/cli-ref-rst-20365acfdba086f2.yaml b/releasenotes/notes/cli-ref-rst-20365acfdba086f2.yaml new file mode 100644 index 00000000..3d22ff62 --- /dev/null +++ b/releasenotes/notes/cli-ref-rst-20365acfdba086f2.yaml @@ -0,0 +1,6 @@ +--- +features: + - Update CLI Reference generation tool for RST. + To migrate CLI Reference from DocBook to RST, + output the documentation in RST format, + with a few work arounds for RST/Sphinx specific issues.