From d9b3af523b0eb7be3bf71cd1d9ff9e22d3596f6c Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Tue, 13 Aug 2019 13:00:13 -0700 Subject: [PATCH] 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 --- docs/docsite/_static/ansible.css | 58 ++++++++++++++-- docs/templates/collections_galaxy_meta.rst.j2 | 68 ++++++++++--------- docs/templates/plugin.rst.j2 | 18 ++--- .../command_plugins/collection_meta.py | 14 +++- .../command_plugins/plugin_formatter.py | 23 +++++-- .../build_ansible/jinja2/filters.py | 2 +- 6 files changed, 127 insertions(+), 56 deletions(-) diff --git a/docs/docsite/_static/ansible.css b/docs/docsite/_static/ansible.css index 3fea13034ad..336d35efd2c 100644 --- a/docs/docsite/_static/ansible.css +++ b/docs/docsite/_static/ansible.css @@ -4,13 +4,63 @@ /* override table width restrictions */ @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 { - /* !important prevents the common CSS stylesheets from overriding - this as on RTD they are loaded after this stylesheet */ - white-space: normal !important; + white-space: normal; } .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; +} diff --git a/docs/templates/collections_galaxy_meta.rst.j2 b/docs/templates/collections_galaxy_meta.rst.j2 index 87c530e0760..cb3568b32ad 100644 --- a/docs/templates/collections_galaxy_meta.rst.j2 +++ b/docs/templates/collections_galaxy_meta.rst.j2 @@ -16,38 +16,44 @@ Structure The ``galaxy.yml`` file must contain the following keys in valid YAML: -.. raw:: html - - {# Header of the documentation -#} - - - - - {% for entry in options %} - - {# key name with required or type label #} - - {# Comments #} - - - {% endfor %} -
KeyComments
- @{ entry.key }@ -
- @{ entry.type | documented_type }@ - {% if entry.get('required', False) %} / required{% endif %} -
-
- {% if entry.description is string %} -
@{ entry.description | replace('\n', '\n ') | html_ify }@
- {% else %} - {% for desc in entry.description %} -
@{ desc | replace('\n', '\n ') | html_ify }@
- {% endfor %} - {% endif %} -
-
+.. rst-class:: documentation-table + +.. list-table:: + :header-rows: 1 + :widths: auto + + * - Key + - Comment + +{%- for entry in options %} + + + * - .. rst-class:: value-name + + @{ entry.key }@ |br| + + .. rst-class:: value-type + + @{ entry.type | documented_type }@ |_| + + {% if entry.get('required', False) -%} + .. rst-class:: value-separator + + / |_| + + .. rst-class:: value-required + + required + {%- endif %} + + + - {% for desc in entry.description -%} + @{ desc | trim | rst_ify }@ + + {% endfor -%} +{%- endfor %} + Examples ======== diff --git a/docs/templates/plugin.rst.j2 b/docs/templates/plugin.rst.j2 index 65acbb2e220..2edbad34d0d 100644 --- a/docs/templates/plugin.rst.j2 +++ b/docs/templates/plugin.rst.j2 @@ -50,13 +50,9 @@ Synopsis -------- {% if description -%} -{% if description is string -%} -- @{ description | rst_ify }@ -{% else %} -{% for desc in description %} +{% for desc in description %} - @{ desc | rst_ify }@ -{% endfor %} -{% endif %} +{% endfor %} {% endif %} @@ -183,13 +179,9 @@ Parameters {% endif %} {# description #} - {% if value.description is string %} -
@{ value.description | replace('\n', '\n ') | html_ify }@
- {% else %} - {% for desc in value.description %} -
@{ desc | replace('\n', '\n ') | html_ify }@
- {% endfor %} - {% endif %} + {% for desc in value.description %} +
@{ desc | replace('\n', '\n ') | html_ify }@
+ {% endfor %} {% if 'aliases' in value and value.aliases %}

aliases: @{ value.aliases|join(', ') }@
{% endif %} diff --git a/hacking/build_library/build_ansible/command_plugins/collection_meta.py b/hacking/build_library/build_ansible/command_plugins/collection_meta.py index b28797473f2..7b7112e8f9a 100644 --- a/hacking/build_library/build_ansible/command_plugins/collection_meta.py +++ b/hacking/build_library/build_ansible/command_plugins/collection_meta.py @@ -12,18 +12,26 @@ import pathlib import yaml from jinja2 import Environment, FileSystemLoader +from ansible.module_utils.six import string_types from ansible.module_utils._text import to_bytes # Pylint doesn't understand Python3 namespace modules. 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 ..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_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): name = 'collection-meta' @@ -51,12 +59,14 @@ class DocumentCollectionMeta(Command): with open(args.collection_defs) as f: options = yaml.safe_load(f) + normalize_options(options) + env = Environment(loader=FileSystemLoader(template_dir), variable_start_string="@{", variable_end_string="}@", trim_blocks=True) env.filters['documented_type'] = documented_type - env.filters['html_ify'] = html_ify + env.filters['rst_ify'] = rst_ify template = env.get_template(template_file) output_name = os.path.join(output_dir, template_file.replace('.j2', '')) diff --git a/hacking/build_library/build_ansible/command_plugins/plugin_formatter.py b/hacking/build_library/build_ansible/command_plugins/plugin_formatter.py index 7407c97f997..1d2ed459f8a 100644 --- a/hacking/build_library/build_ansible/command_plugins/plugin_formatter.py +++ b/hacking/build_library/build_ansible/command_plugins/plugin_formatter.py @@ -386,8 +386,17 @@ def process_plugins(module_map, templates, outputname, output_dir, ansible_versi 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])) + 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: - 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 @@ -416,6 +425,14 @@ def process_plugins(module_map, templates, outputname, output_dir, ansible_versi if 'description' not in doc['options'][k]: 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 # information on when something is required in here. Those need # 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']): 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.sort() diff --git a/hacking/build_library/build_ansible/jinja2/filters.py b/hacking/build_library/build_ansible/jinja2/filters.py index 735d07b7ba7..21436eda907 100644 --- a/hacking/build_library/build_ansible/jinja2/filters.py +++ b/hacking/build_library/build_ansible/jinja2/filters.py @@ -27,7 +27,7 @@ _ITALIC = re.compile(r"I\(([^)]+)\)") _BOLD = re.compile(r"B\(([^)]+)\)") _MODULE = re.compile(r"M\(([^)]+)\)") _URL = re.compile(r"U\(([^)]+)\)") -_LINK = re.compile(r"L\(([^)]+),([^)]+)\)") +_LINK = re.compile(r"L\(([^)]+), *([^)]+)\)") _CONST = re.compile(r"C\(([^)]+)\)") _RULER = re.compile(r"HORIZONTALLINE")