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.