Galaxy meta docs table (#60171)

* Use an rst table instead of a raw html table

* Rst is easier to read so we want to use it wherever possible
* Fix the jinja2 filters which create links so that they do not include
  extraneous whitespace in the URL

* Normalize description data before sending them to the templates
This commit is contained in:
Toshio Kuratomi 2019-08-13 13:00:13 -07:00 committed by Alicia Cozine
parent 60f8483603
commit d9b3af523b
6 changed files with 127 additions and 56 deletions

View file

@ -4,13 +4,63 @@
/* override table width restrictions */ /* override table width restrictions */
@media screen and (min-width: 767px) { @media screen and (min-width: 767px) {
/* If we ever publish to read the docs, we need to use !important for these
* two styles as read the docs itself loads their theme in a way that we
* can't otherwise override it.
*/
.wy-table-responsive table td { .wy-table-responsive table td {
/* !important prevents the common CSS stylesheets from overriding white-space: normal;
this as on RTD they are loaded after this stylesheet */
white-space: normal !important;
} }
.wy-table-responsive { .wy-table-responsive {
overflow: visible !important; overflow: visible;
} }
} }
/*
* We use the class documentation-table for attribute tables where the first
* column is the name of an attribute and the second column is the description.
*/
/* These tables look like this:
*
* Attribute Name Description
* -------------- -----------
* **NAME** This is a multi-line description
* str/required that can span multiple lines
*
* With multiple paragraphs
* -------------- -----------
*
* **NAME** is given the class .value-name
* str is given the class .value-type
* / is given the class .value-separator
* required is given the class .value-required
*/
table.documentation-table td:first-child {
white-space: nowrap;
vertical-align: top;
}
table.documentation-table .value-name {
font-weight: bold;
display: inline;
}
table.documentation-table .value-type {
font-size: x-small;
color: purple;
display: inline;
}
table.documentation-table .value-separator {
font-size: x-small;
display: inline;
}
table.documentation-table .value-required {
font-size: x-small;
color: red;
display: inline;
}

View file

@ -16,38 +16,44 @@ Structure
The ``galaxy.yml`` file must contain the following keys in valid YAML: The ``galaxy.yml`` file must contain the following keys in valid YAML:
.. raw:: html
<table border=0 cellpadding=0 class="documentation-table"> .. rst-class:: documentation-table
{# Header of the documentation -#}
<tr> .. list-table::
<th>Key</th> :header-rows: 1
<th width="100%">Comments</th> :widths: auto
</tr>
{% for entry in options %} * - Key
<tr> - Comment
{# key name with required or type label #}
<td> {%- for entry in options %}
<b>@{ entry.key }@</b>
<div style="font-size: small">
<span style="color: purple">@{ entry.type | documented_type }@</span> * - .. rst-class:: value-name
{% if entry.get('required', False) %} / <span style="color: red">required</span>{% endif %}
</div> @{ entry.key }@ |br|
</td>
{# Comments #} .. rst-class:: value-type
<td>
{% if entry.description is string %} @{ entry.type | documented_type }@ |_|
<div>@{ entry.description | replace('\n', '\n ') | html_ify }@</div>
{% else %} {% if entry.get('required', False) -%}
{% for desc in entry.description %} .. rst-class:: value-separator
<div>@{ desc | replace('\n', '\n ') | html_ify }@</div>
{% endfor %} / |_|
{% endif %}
</td> .. rst-class:: value-required
</tr>
{% endfor %} required
</table> {%- endif %}
<br/>
- {% for desc in entry.description -%}
@{ desc | trim | rst_ify }@
{% endfor -%}
{%- endfor %}
Examples Examples
======== ========

View file

@ -50,13 +50,9 @@ Synopsis
-------- --------
{% if description -%} {% if description -%}
{% if description is string -%}
- @{ description | rst_ify }@
{% else %}
{% for desc in description %} {% for desc in description %}
- @{ desc | rst_ify }@ - @{ desc | rst_ify }@
{% endfor %} {% endfor %}
{% endif %}
{% endif %} {% endif %}
@ -183,13 +179,9 @@ Parameters
{% endif %} {% endif %}
{# description #} {# description #}
<td> <td>
{% if value.description is string %}
<div>@{ value.description | replace('\n', '\n ') | html_ify }@</div>
{% else %}
{% for desc in value.description %} {% for desc in value.description %}
<div>@{ desc | replace('\n', '\n ') | html_ify }@</div> <div>@{ desc | replace('\n', '\n ') | html_ify }@</div>
{% endfor %} {% endfor %}
{% endif %}
{% if 'aliases' in value and value.aliases %} {% if 'aliases' in value and value.aliases %}
<div style="font-size: small; color: darkgreen"><br/>aliases: @{ value.aliases|join(', ') }@</div> <div style="font-size: small; color: darkgreen"><br/>aliases: @{ value.aliases|join(', ') }@</div>
{% endif %} {% endif %}

View file

@ -12,18 +12,26 @@ import pathlib
import yaml import yaml
from jinja2 import Environment, FileSystemLoader from jinja2 import Environment, FileSystemLoader
from ansible.module_utils.six import string_types
from ansible.module_utils._text import to_bytes from ansible.module_utils._text import to_bytes
# Pylint doesn't understand Python3 namespace modules. # Pylint doesn't understand Python3 namespace modules.
from ..change_detection import update_file_if_different # pylint: disable=relative-beyond-top-level from ..change_detection import update_file_if_different # pylint: disable=relative-beyond-top-level
from ..commands import Command # pylint: disable=relative-beyond-top-level from ..commands import Command # pylint: disable=relative-beyond-top-level
from ..jinja2.filters import documented_type, html_ify # pylint: disable=relative-beyond-top-level from ..jinja2.filters import documented_type, rst_ify # pylint: disable=relative-beyond-top-level
DEFAULT_TEMPLATE_FILE = 'collections_galaxy_meta.rst.j2' DEFAULT_TEMPLATE_FILE = 'collections_galaxy_meta.rst.j2'
DEFAULT_TEMPLATE_DIR = pathlib.Path(__file__).parents[4] / 'docs/templates' DEFAULT_TEMPLATE_DIR = pathlib.Path(__file__).parents[4] / 'docs/templates'
def normalize_options(options):
"""Normalize the options to make for easy templating"""
for opt in options:
if isinstance(opt['description'], string_types):
opt['description'] = [opt['description']]
class DocumentCollectionMeta(Command): class DocumentCollectionMeta(Command):
name = 'collection-meta' name = 'collection-meta'
@ -51,12 +59,14 @@ class DocumentCollectionMeta(Command):
with open(args.collection_defs) as f: with open(args.collection_defs) as f:
options = yaml.safe_load(f) options = yaml.safe_load(f)
normalize_options(options)
env = Environment(loader=FileSystemLoader(template_dir), env = Environment(loader=FileSystemLoader(template_dir),
variable_start_string="@{", variable_start_string="@{",
variable_end_string="}@", variable_end_string="}@",
trim_blocks=True) trim_blocks=True)
env.filters['documented_type'] = documented_type env.filters['documented_type'] = documented_type
env.filters['html_ify'] = html_ify env.filters['rst_ify'] = rst_ify
template = env.get_template(template_file) template = env.get_template(template_file)
output_name = os.path.join(output_dir, template_file.replace('.j2', '')) output_name = os.path.join(output_dir, template_file.replace('.j2', ''))

View file

@ -386,8 +386,17 @@ def process_plugins(module_map, templates, outputname, output_dir, ansible_versi
if field in doc and doc[field] in (None, ''): if field in doc and doc[field] in (None, ''):
print("%s: WARNING: MODULE field '%s' DOCUMENTATION is null/empty value=%s" % (fname, field, doc[field])) print("%s: WARNING: MODULE field '%s' DOCUMENTATION is null/empty value=%s" % (fname, field, doc[field]))
if 'description' in doc:
if isinstance(doc['description'], string_types):
doc['description'] = [doc['description']]
elif not isinstance(doc['description'], (list, tuple)):
raise AnsibleError("Description must be a string or list of strings. Got %s"
% type(doc['description']))
else:
doc['description'] = []
if 'version_added' not in doc: if 'version_added' not in doc:
display.error("*** ERROR: missing version_added in: %s ***\n" % module) raise AnsibleError("*** ERROR: missing version_added in: %s ***\n" % module)
# #
# The present template gets everything from doc so we spend most of this # The present template gets everything from doc so we spend most of this
@ -416,6 +425,14 @@ def process_plugins(module_map, templates, outputname, output_dir, ansible_versi
if 'description' not in doc['options'][k]: if 'description' not in doc['options'][k]:
raise AnsibleError("Missing required description for parameter '%s' in '%s' " % (k, module)) raise AnsibleError("Missing required description for parameter '%s' in '%s' " % (k, module))
# Make sure description is a list of lines for later formatting
if isinstance(doc['options'][k]['description'], string_types):
doc['options'][k]['description'] = [doc['options'][k]['description']]
elif not isinstance(doc['options'][k]['description'], (list, tuple)):
raise AnsibleError("Invalid type for options['%s']['description']."
" Must be string or list of strings. Got %s" %
(k, type(doc['options'][k]['description'])))
# Error out if required isn't a boolean (people have been putting # Error out if required isn't a boolean (people have been putting
# information on when something is required in here. Those need # information on when something is required in here. Those need
# to go in the description instead). # to go in the description instead).
@ -427,10 +444,6 @@ def process_plugins(module_map, templates, outputname, output_dir, ansible_versi
if 'version_added' in doc['options'][k] and too_old(doc['options'][k]['version_added']): if 'version_added' in doc['options'][k] and too_old(doc['options'][k]['version_added']):
del doc['options'][k]['version_added'] del doc['options'][k]['version_added']
# Make sure description is a list of lines for later formatting
if not isinstance(doc['options'][k]['description'], list):
doc['options'][k]['description'] = [doc['options'][k]['description']]
option_names.append(k) option_names.append(k)
option_names.sort() option_names.sort()

View file

@ -27,7 +27,7 @@ _ITALIC = re.compile(r"I\(([^)]+)\)")
_BOLD = re.compile(r"B\(([^)]+)\)") _BOLD = re.compile(r"B\(([^)]+)\)")
_MODULE = re.compile(r"M\(([^)]+)\)") _MODULE = re.compile(r"M\(([^)]+)\)")
_URL = re.compile(r"U\(([^)]+)\)") _URL = re.compile(r"U\(([^)]+)\)")
_LINK = re.compile(r"L\(([^)]+),([^)]+)\)") _LINK = re.compile(r"L\(([^)]+), *([^)]+)\)")
_CONST = re.compile(r"C\(([^)]+)\)") _CONST = re.compile(r"C\(([^)]+)\)")
_RULER = re.compile(r"HORIZONTALLINE") _RULER = re.compile(r"HORIZONTALLINE")