diff --git a/docs/bin/plugin_formatter.py b/docs/bin/plugin_formatter.py index 85271bafe51..ea5b073adb5 100755 --- a/docs/bin/plugin_formatter.py +++ b/docs/bin/plugin_formatter.py @@ -206,6 +206,7 @@ def get_plugin_info(module_dir, limit_to=None, verbose=False): :aliases: set of aliases to this module name :metadata: The modules metadata (as recorded in the module) :doc: The documentation structure for the module + :seealso: The list of dictionaries with references to related subjects :examples: The module's examples :returndocs: The module's returndocs @@ -362,7 +363,7 @@ def jinja2_environment(template_dir, typ, plugin_type): templates = {} if typ == 'rst': - env.filters['convert_symbols_to_format'] = rst_ify + env.filters['rst_ify'] = rst_ify env.filters['html_ify'] = html_ify env.filters['fmt'] = rst_fmt env.filters['xline'] = rst_xline diff --git a/docs/docsite/rst/dev_guide/developing_modules_documenting.rst b/docs/docsite/rst/dev_guide/developing_modules_documenting.rst index 6c7755b068d..66e6606db26 100644 --- a/docs/docsite/rst/dev_guide/developing_modules_documenting.rst +++ b/docs/docsite/rst/dev_guide/developing_modules_documenting.rst @@ -224,7 +224,34 @@ All fields in the ``DOCUMENTATION`` block are lower-case. All fields are require :notes: * Details of any important information that doesn't fit in one of the above sections. - * For example, whether ``check_mode`` is or is not supported, or links to external documentation. + * For example, whether ``check_mode`` is or is not supported. + +:seealso: + + * A list of references to other modules, documentation or Internet resources + * A reference can be one of the following formats: + + + .. code-block:: yaml+jinja + + seealso:: + + # Reference by module name + - module: aci_tenant + + # Reference by module name, including description + - module: aci_tenant + description: ACI module to create tenants on a Cisco ACI fabric. + + # Reference by rST documentation anchor + - ref: aci_guide + description: Detailed information on how to manage your ACI infrastructure using Ansible. + + # Reference by Internet resource + - name: APIC Management Information Model reference + description: Complete reference of the APIC object model. + link: https://developer.cisco.com/docs/apic-mim-ref/ + Linking within module documentation ----------------------------------- @@ -239,7 +266,8 @@ You can link from your module documentation to other module docs, other resource .. note:: - To refer a collection of modules, use ``C(..)``, e.g. ``Refer to the C(win_*) modules.`` + - To refer a collection of modules, use ``C(..)``, e.g. ``Refer to the C(win_*) modules.`` + - Because it stands out better, using ``seealso`` is preferred for general references over the use of notes or adding links to the description. Documentation fragments ----------------------- @@ -248,10 +276,13 @@ If you're writing multiple related modules, they may share common documentation, For example, all AWS modules should include:: +.. code-block:: yaml+jinja + extends_documentation_fragment: - aws - ec2 + You can find more examples by searching for ``extends_documentation_fragment`` under the Ansible source tree. .. _examples_block: diff --git a/docs/templates/plugin.rst.j2 b/docs/templates/plugin.rst.j2 index 095b9aa3461..7e971a4c6ad 100644 --- a/docs/templates/plugin.rst.j2 +++ b/docs/templates/plugin.rst.j2 @@ -11,7 +11,7 @@ {% endfor %} {% if short_description %} -{% set title = module + ' - ' + short_description|convert_symbols_to_format %} +{% set title = module + ' - ' + short_description | rst_ify %} {% else %} {% set title = module %} {% endif %} @@ -39,9 +39,9 @@ DEPRECATED ---------- {# use unknown here? skip the fields? #} -:Removed in Ansible: version: @{ deprecated['removed_in'] | default('') | string | convert_symbols_to_format }@ -:Why: @{ deprecated['why'] | default('') | convert_symbols_to_format }@ -:Alternative: @{ deprecated['alternative'] | default('')| convert_symbols_to_format }@ +:Removed in Ansible: version: @{ deprecated['removed_in'] | default('') | string | rst_ify }@ +:Why: @{ deprecated['why'] | default('') | rst_ify }@ +:Alternative: @{ deprecated['alternative'] | default('') | rst_ify }@ {% endif %} @@ -51,10 +51,10 @@ Synopsis {% if description -%} {% if description is string -%} -- @{ description | convert_symbols_to_format }@ +- @{ description | rst_ify }@ {% else %} {% for desc in description %} -- @{ desc | convert_symbols_to_format }@ +- @{ desc | rst_ify }@ {% endfor %} {% endif %} @@ -75,7 +75,7 @@ The below requirements are needed on the local master node that executes this @{ {% endif %} {% for req in requirements %} -- @{ req | convert_symbols_to_format }@ +- @{ req | rst_ify }@ {% endfor %} {% endif %} @@ -206,17 +206,40 @@ Parameters {% endif %} {% if notes -%} - Notes ----- .. note:: {% for note in notes %} - - @{ note | convert_symbols_to_format }@ + - @{ note | rst_ify }@ {% endfor %} {% endif %} +{% if seealso -%} +See Also +-------- + +.. seealso:: + +{% for item in seealso %} +{% if item.module is defined and item.description is defined %} + :ref:`Module @{ item.module }@ <@{ item.module }@_module>` + @{ item.description | rst_ify }@ +{% elif item.module is defined %} + :ref:`@{ item.module }@_module` + The official documentation on the **@{ item.module }@** module. +{% elif item.name is defined and item.link is defined and item.description is defined %} + `@{ item.name }@ <@{ item.link }@>`_ + @{ item.description | rst_ify }@ +{% elif item.ref is defined and item.description is defined %} + :ref:`@{ item.ref }@` + @{ item.description | rst_ify }@ +{% endif %} +{% endfor %} + +{% endif %} + {% if examples or plainexamples -%} Examples @@ -421,7 +444,7 @@ please refer to this `Knowledge Base article -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . +# Copyright: (c) 2014, James Tanner +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import (absolute_import, division, print_function) __metaclass__ = type @@ -580,8 +568,45 @@ class DocCLI(CLI): for note in doc['notes']: text.append(textwrap.fill(CLI.tty_ify(note), limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent)) text.append('') + text.append('') del doc['notes'] + if 'seealso' in doc and doc['seealso']: + text.append("SEE ALSO:") + for item in doc['seealso']: + if 'module' in item and 'description' in item: + text.append(textwrap.fill(CLI.tty_ify('Module %s' % item['module']), + limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent)) + text.append(textwrap.fill(CLI.tty_ify(item['description']), + limit - 6, initial_indent=opt_indent, subsequent_indent=opt_indent)) + text.append(textwrap.fill(CLI.tty_ify('https://docs.ansible.com/ansible/latest/modules/%s_module.html' % item['module']), + limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent)) + elif 'module' in item: + text.append(textwrap.fill(CLI.tty_ify('Module %s' % item['module']), + limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent)) + text.append(textwrap.fill(CLI.tty_ify('The official documentation on the %s module.' % item['module']), + limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' ')) + text.append(textwrap.fill(CLI.tty_ify('https://docs.ansible.com/ansible/latest/modules/%s_module.html' % item['module']), + limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent)) + elif 'name' in item and 'link' in item and 'description' in item: + text.append(textwrap.fill(CLI.tty_ify(item['name']), + limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent)) + text.append(textwrap.fill(CLI.tty_ify(item['description']), + limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' ')) + text.append(textwrap.fill(CLI.tty_ify(item['link']), + limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' ')) + elif 'ref' in item and 'description' in item: + text.append(textwrap.fill(CLI.tty_ify('Ansible documentation [%s]' % item['ref']), + limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent)) + text.append(textwrap.fill(CLI.tty_ify(item['description']), + limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' ')) + text.append(textwrap.fill(CLI.tty_ify('https://docs.ansible.com/ansible/latest/#stq=%s&stp=1' % item['ref']), + limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' ')) + + text.append('') + text.append('') + del doc['seealso'] + if 'requirements' in doc and doc['requirements'] is not None and len(doc['requirements']) > 0: req = ", ".join(doc.pop('requirements')) text.append("REQUIREMENTS:%s\n" % textwrap.fill(CLI.tty_ify(req), limit - 16, initial_indent=" ", subsequent_indent=opt_indent)) @@ -601,14 +626,16 @@ class DocCLI(CLI): if 'plainexamples' in doc and doc['plainexamples'] is not None: text.append("EXAMPLES:") + text.append('') if isinstance(doc['plainexamples'], string_types): text.append(doc.pop('plainexamples').strip()) else: text.append(yaml.dump(doc.pop('plainexamples'), indent=2, default_flow_style=False)) text.append('') + text.append('') if 'returndocs' in doc and doc['returndocs'] is not None: - text.append("RETURN VALUES:\n") + text.append("RETURN VALUES:") if isinstance(doc['returndocs'], string_types): text.append(doc.pop('returndocs')) else: diff --git a/lib/ansible/modules/network/aci/aci_bd_subnet.py b/lib/ansible/modules/network/aci/aci_bd_subnet.py index 80daa147868..f4e743a1267 100644 --- a/lib/ansible/modules/network/aci/aci_bd_subnet.py +++ b/lib/ansible/modules/network/aci/aci_bd_subnet.py @@ -21,8 +21,9 @@ notes: is required when the state is C(absent) or C(present). - The C(tenant) and C(bd) used must exist before using this module in your playbook. The M(aci_tenant) module and M(aci_bd) can be used for these. -- More information about the internal APIC class B(fv:Subnet) from - L(the APIC Management Information Model reference,https://developer.cisco.com/docs/apic-mim-ref/). +seealso: +- module: aci_bd +- module: aci_tenant author: - Jacob McGill (@jmcgill298) version_added: '2.4' diff --git a/lib/ansible/parsing/plugin_docs.py b/lib/ansible/parsing/plugin_docs.py index d0fb658b004..81d7d818880 100644 --- a/lib/ansible/parsing/plugin_docs.py +++ b/lib/ansible/parsing/plugin_docs.py @@ -1,7 +1,6 @@ -# Copyright (c) 2017 Ansible Project +# Copyright: (c) 2017, Ansible Project # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -# Make coding more python3-ish from __future__ import (absolute_import, division, print_function) __metaclass__ = type @@ -29,7 +28,8 @@ def read_docstring(filename, verbose=True, ignore_errors=True): 'doc': None, 'plainexamples': None, 'returndocs': None, - 'metadata': None + 'metadata': None, + 'seealso': None, } string_to_vars = { diff --git a/lib/ansible/utils/module_docs_fragments/aci.py b/lib/ansible/utils/module_docs_fragments/aci.py index 71e3e48e4a9..24e927f9672 100644 --- a/lib/ansible/utils/module_docs_fragments/aci.py +++ b/lib/ansible/utils/module_docs_fragments/aci.py @@ -2,7 +2,6 @@ # Copyright: (c) 2017, Dag Wieers (@dagwieers) # Copyright: (c) 2017, Swetha Chunduri (@schunduri) - # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) @@ -70,6 +69,9 @@ options: - This should only set to C(no) when used on personally controlled sites using self-signed certificates. type: bool default: 'yes' -notes: -- Please read the :ref:`aci_guide` for more detailed information on how to manage your ACI infrastructure using Ansible. +seealso: +- ref: aci_guide + description: Detailed information on how to manage your ACI infrastructure using Ansible. +- ref: aci_dev_guide + description: Detailed guide on how to write your own Cisco ACI modules to contribute. ''' diff --git a/lib/ansible/utils/plugin_docs.py b/lib/ansible/utils/plugin_docs.py index 2e6d7d19ab9..e6a1dc5e7b5 100644 --- a/lib/ansible/utils/plugin_docs.py +++ b/lib/ansible/utils/plugin_docs.py @@ -89,6 +89,13 @@ def add_fragments(doc, filename, fragment_loader): doc['notes'] = [] doc['notes'].extend(notes) + if 'seealso' in fragment: + seealso = fragment.pop('seealso') + if seealso: + if 'seealso' not in doc: + doc['seealso'] = [] + doc['seealso'].extend(seealso) + if 'options' not in fragment: raise Exception("missing options in fragment (%s), possibly misformatted?: %s" % (fragment_name, filename)) diff --git a/test/sanity/validate-modules/schema.py b/test/sanity/validate-modules/schema.py index 1a44c1dd17c..5604e2f42bd 100644 --- a/test/sanity/validate-modules/schema.py +++ b/test/sanity/validate-modules/schema.py @@ -1,20 +1,8 @@ # -*- coding: utf-8 -*- -# -# Copyright (C) 2015 Matt Martz -# Copyright (C) 2015 Rackspace US, Inc. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . + +# Copyright: (c) 2015, Matt Martz +# Copyright: (c) 2015, Rackspace US, Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from voluptuous import PREVENT_EXTRA, All, Any, Length, Required, Schema, Self from ansible.module_utils.six import string_types @@ -36,6 +24,26 @@ def sequence_of_sequences(min=None, max=None): ) +seealso_schema = Schema( + [ + Any( + { + Required('module'): Any(*string_types), + 'description': Any(*string_types), + }, + { + Required('ref'): Any(*string_types), + Required('description'): Any(*string_types), + }, + { + Required('name'): Any(*string_types), + Required('link'): Any(*string_types), + Required('description'): Any(*string_types), + }, + ), + ] +) + ansible_module_kwargs_schema = Schema( { 'argument_spec': dict, @@ -147,6 +155,7 @@ def doc_schema(module_name): Required('version_added'): Any(float, *string_types), Required('author'): Any(None, list_string_types, *string_types), 'notes': Any(None, list_string_types), + 'seealso': Any(None, seealso_schema), 'requirements': list_string_types, 'todo': Any(None, list_string_types, *string_types), 'options': Any(None, *list_dict_option_schema),