diff --git a/changelogs/fragments/6-tests-filters.yml b/changelogs/fragments/6-tests-filters.yml new file mode 100644 index 00000000..58344fa2 --- /dev/null +++ b/changelogs/fragments/6-tests-filters.yml @@ -0,0 +1,6 @@ +minor_changes: + - "Support test and filter plugins when ansible-core 2.14+ is used. This works with the current ``devel`` branch of ansible-core (https://github.com/ansible-community/antsibull-docs/pull/6)." + - "Support parameter type ``any``, and show ``raw`` as ``any`` (https://github.com/ansible-community/antsibull-docs/pull/6)." + - "More robust handling of parsing errors when ansible-doc was unable to extract documentation (https://github.com/ansible-community/antsibull-docs/pull/6)." + - "If lookup plugins have an option called ``_terms``, it is now shown in its own section ``Terms``, and not in the regular ``Parameters`` section (https://github.com/ansible-community/antsibull-docs/pull/6)." + - "If lookup plugins have a single return value starting with ``_``, that return value is now labelled ``Return value`` (https://github.com/ansible-community/antsibull-docs/pull/6)." diff --git a/src/antsibull_docs/cli/doc_commands/stable.py b/src/antsibull_docs/cli/doc_commands/stable.py index 65a50bb4..50f29483 100644 --- a/src/antsibull_docs/cli/doc_commands/stable.py +++ b/src/antsibull_docs/cli/doc_commands/stable.py @@ -136,6 +136,9 @@ def normalize_plugin_info(plugin_type: str, # If you wonder why this code isn't showing up in code coverage: that's because it's executed # in a subprocess. See normalize_all_plugin_info below. + if 'error' in plugin_info: + return ({}, [plugin_info['error']]) + errors = [] if plugin_type == 'role': try: @@ -251,10 +254,12 @@ def get_plugin_contents(plugin_info: t.Mapping[str, t.Mapping[str, t.Any]], namespace, collection, short_name = get_fqcn_parts(plugin_name) if plugin_type == 'role': desc = '' - if 'main' in plugin_desc['entry_points']: - desc = plugin_desc['entry_points']['main']['short_description'] + if 'entry_points' in plugin_desc and 'main' in plugin_desc['entry_points']: + desc = plugin_desc['entry_points']['main'].get('short_description') or '' + elif 'doc' in plugin_desc: + desc = plugin_desc['doc'].get('short_description') or '' else: - desc = plugin_desc['doc']['short_description'] + desc = '' plugin_contents[plugin_type]['.'.join((namespace, collection))][short_name] = desc return plugin_contents diff --git a/src/antsibull_docs/constants.py b/src/antsibull_docs/constants.py index 2e99f894..8cf0c433 100644 --- a/src/antsibull_docs/constants.py +++ b/src/antsibull_docs/constants.py @@ -16,9 +16,11 @@ DOCUMENTABLE_PLUGINS: FrozenSet[str] = frozenset(('become', 'cache', 'callback', 'cliconf', 'connection', 'httpapi', 'inventory', 'lookup', 'netconf', 'shell', 'vars', 'module', - 'strategy', 'role',)) + 'strategy', 'role', 'filter', 'test')) DOCUMENTABLE_PLUGINS_MIN_VERSION: Dict[str, str] = { + 'filter': '2.14.0', 'role': '2.11.0', + 'test': '2.14.0', } diff --git a/src/antsibull_docs/data/docsite/macros/parameters.rst.j2 b/src/antsibull_docs/data/docsite/macros/parameters.rst.j2 index c46252c1..ec4398cf 100644 --- a/src/antsibull_docs/data/docsite/macros/parameters.rst.j2 +++ b/src/antsibull_docs/data/docsite/macros/parameters.rst.j2 @@ -11,7 +11,7 @@ * - Parameter - Comments -{% for key, value in elements | dictsort recursive %} +{% for key, value in elements recursive %} {# parameter name with required and/or introduced label #} * - .. raw:: html @@ -186,7 +186,7 @@ {% set row_class = cycler('even', 'odd') %} -{% for key, value in elements | dictsort recursive %} +{% for key, value in elements recursive %} {# parameter name with required and/or introduced label #} {% for i in range(1, loop.depth) %}
{% endfor %}
diff --git a/src/antsibull_docs/data/docsite/macros/returnvalues.rst.j2 b/src/antsibull_docs/data/docsite/macros/returnvalues.rst.j2 index e4625e87..641151a7 100644 --- a/src/antsibull_docs/data/docsite/macros/returnvalues.rst.j2 +++ b/src/antsibull_docs/data/docsite/macros/returnvalues.rst.j2 @@ -8,7 +8,7 @@ * - Key - Description -{% for key, value in elements | dictsort recursive %} +{% for key, value in elements recursive %} {# return value name #} * - .. raw:: html @@ -114,7 +114,7 @@ {% set row_class = cycler('even', 'odd') %} -{% for key, value in elements | dictsort recursive %} +{% for key, value in elements recursive %} {# return value name #} {% for i in range(1, loop.depth) %}
{% endfor %}
diff --git a/src/antsibull_docs/data/docsite/plugin.rst.j2 b/src/antsibull_docs/data/docsite/plugin.rst.j2 index 373c632c..5b9719b9 100644 --- a/src/antsibull_docs/data/docsite/plugin.rst.j2 +++ b/src/antsibull_docs/data/docsite/plugin.rst.j2 @@ -162,17 +162,90 @@ The below requirements are needed on the local controller node that executes thi {% endif %} +{% set options_to_skip = [] %} + +{% if doc['options']['_terms'] and plugin_type in ['lookup'] %} +{% set options_to_skip = options_to_skip + ['_terms'] %} + +.. Terms + +Terms +----- + +{% if use_html_blobs %} +@{ parameters_html([['Terms', doc['options']['_terms']]], suboption_key='suboptions') }@ +{% else %} +@{ parameters_rst([['Terms', doc['options']['_terms']]], suboption_key='suboptions') }@ +{% endif %} + +{% endif %} + +{% if doc['options']['_input'] and plugin_type in ['filter', 'test'] %} +{% set options_to_skip = options_to_skip + ['_input'] %} + +.. Input + +Input +----- + +{% if plugin_type == 'filter' %} +This describes the input of the filter, the value before ``| @{plugin_name}@``. +{% else %} +This describes the input of the test, the value before ``is @{plugin_name}@`` or ``is not @{plugin_name}@``. +{% endif %} + +{% if use_html_blobs %} +@{ parameters_html([['Input', doc['options']['_input']]], suboption_key='suboptions') }@ +{% else %} +@{ parameters_rst([['Input', doc['options']['_input']]], suboption_key='suboptions') }@ +{% endif %} + +{% endif %} + +{% if doc['positional'] and plugin_type in ['lookup', 'filter', 'test'] %} +{% set options_to_skip = options_to_skip + doc['positional'] %} + +.. Positional + +Positional parameters +--------------------- + +{% if plugin_type == 'filter' %} +This describes positional parameters of the filter. These are the values ``positional1``, ``positional2`` and so on in the following example: ``input | @{plugin_name}@(positional1, positional2, ...)``. +{% elif plugin_type == 'test' %} +This describes positional parameters of the test. These are the values ``positional1``, ``positional2`` and so on in the following examples: ``input is @{plugin_name}@(positional1, positional2, ...)`` and ``input is not @{plugin_name}@(positional1, positional2, ...)``. +{% endif %} + +{% if use_html_blobs %} +@{ parameters_html(doc['options'] | extract_options_from_list(doc['positional']), suboption_key='suboptions') }@ +{% else %} +@{ parameters_rst(doc['options'] | extract_options_from_list(doc['positional']), suboption_key='suboptions') }@ +{% endif %} + +{% endif %} + .. Options -{% if doc['options'] -%} +{% if doc['options'] | remove_options_from_list(options_to_skip) -%} +{% if plugin_type in ['filter', 'test'] %} +Keyword parameters +------------------ +{% else %} Parameters ---------- +{% endif %} + +{% if plugin_type == 'filter' %} +This describes keyword parameters of the filter. These are the values ``key1=value1``, ``key2=value2`` and so on in the following example: ``input | @{plugin_name}@(key1=value1, key2=value2, ...)``. +{% elif plugin_type == 'test' %} +This describes keyword parameters of the test. These are the values ``key1=value1``, ``key2=value2`` and so on in the following examples: ``input is @{plugin_name}@(key1=value1, key2=value2, ...)`` and ``input is not @{plugin_name}@(key1=value1, key2=value2, ...)``. +{% endif %} {% if use_html_blobs %} -@{ parameters_html(doc['options'], suboption_key='suboptions') }@ +@{ parameters_html(doc['options'] | remove_options_from_list(options_to_skip) | dictsort, suboption_key='suboptions') }@ {% else %} -@{ parameters_rst(doc['options'], suboption_key='suboptions') }@ +@{ parameters_rst(doc['options'] | remove_options_from_list(options_to_skip) | dictsort, suboption_key='suboptions') }@ {% endif %} {% endif %} @@ -235,7 +308,7 @@ Examples {% endif %} -{% if 'ansible_facts' in returndocs %} +{% if 'ansible_facts' in returndocs and plugin_type == 'module' %} {% set returnfacts = returndocs['ansible_facts']['contains'] %} {% set _x = returndocs.pop('ansible_facts', None) %} {% endif %} @@ -248,23 +321,37 @@ Returned Facts Facts returned by this module are added/updated in the ``hostvars`` host facts and can be referenced by name just like any other host fact. They do not need to be registered in order to use them. {% if use_html_blobs %} -@{ return_docs_html(returnfacts) }@ +@{ return_docs_html(returnfacts | dictsort) }@ {% else %} -@{ return_docs_rst(returnfacts) }@ +@{ return_docs_rst(returnfacts | dictsort) }@ {% endif %} {% endif %} .. Return values {% if returndocs -%} +{% if plugin_type not in ['lookup', 'filter', 'test'] %} +{# This only makes sense for plugins which can return more than one value. #} Return Values ------------- Common return values are documented :ref:`here `, the following are the fields unique to this @{ plugin_type }@: +{% else %} +Return Value +------------ +{% endif %} -{% if use_html_blobs %} -@{ return_docs_html(returndocs) }@ +{% if plugin_type in ['lookup', 'filter', 'test'] and returndocs | length == 1 and (returndocs | first).startswith('_') %} +{% if use_html_blobs %} +@{ return_docs_html([['Return value', returndocs.values() | first]]) }@ +{% else %} +@{ return_docs_rst([['Return value', returndocs.values() | first]]) }@ +{% endif %} {% else %} -@{ return_docs_rst(returndocs) }@ +{% if use_html_blobs %} +@{ return_docs_html(returndocs | dictsort) }@ +{% else %} +@{ return_docs_rst(returndocs | dictsort) }@ +{% endif %} {% endif %} {% endif %} diff --git a/src/antsibull_docs/data/docsite/role.rst.j2 b/src/antsibull_docs/data/docsite/role.rst.j2 index e302a413..a5aa8616 100644 --- a/src/antsibull_docs/data/docsite/role.rst.j2 +++ b/src/antsibull_docs/data/docsite/role.rst.j2 @@ -128,9 +128,9 @@ Parameters ^^^^^^^^^^ {% if use_html_blobs %} -@{ parameters_html(ep_doc['options'], suboption_key='options', parameter_html_prefix=entry_point ~ '--', parameter_rst_prefix=entry_point ~ '__') }@ +@{ parameters_html(ep_doc['options'] | dictsort, suboption_key='options', parameter_html_prefix=entry_point ~ '--', parameter_rst_prefix=entry_point ~ '__') }@ {% else %} -@{ parameters_rst(ep_doc['options'], suboption_key='options', parameter_html_prefix=entry_point ~ '--', parameter_rst_prefix=entry_point ~ '__') }@ +@{ parameters_rst(ep_doc['options'] | dictsort, suboption_key='options', parameter_html_prefix=entry_point ~ '--', parameter_rst_prefix=entry_point ~ '__') }@ {% endif %} {% endif %} diff --git a/src/antsibull_docs/jinja2/environment.py b/src/antsibull_docs/jinja2/environment.py index 43e3e650..1901218b 100644 --- a/src/antsibull_docs/jinja2/environment.py +++ b/src/antsibull_docs/jinja2/environment.py @@ -9,7 +9,7 @@ from .filters import ( do_max, documented_type, html_ify, rst_ify, rst_escape, rst_fmt, rst_xline, move_first, - massage_author_name, + massage_author_name, extract_options_from_list, remove_options_from_list, ) from .tests import still_relevant, test_list @@ -70,6 +70,8 @@ def doc_environment(template_location): env.filters['documented_type'] = documented_type env.filters['move_first'] = move_first env.filters['massage_author_name'] = massage_author_name + env.filters['extract_options_from_list'] = extract_options_from_list + env.filters['remove_options_from_list'] = remove_options_from_list env.tests['list'] = test_list env.tests['still_relevant'] = still_relevant diff --git a/src/antsibull_docs/jinja2/filters.py b/src/antsibull_docs/jinja2/filters.py index eac5eade..168d5a5a 100644 --- a/src/antsibull_docs/jinja2/filters.py +++ b/src/antsibull_docs/jinja2/filters.py @@ -194,3 +194,18 @@ def massage_author_name(value): value = _EMAIL_ADDRESS.sub('', value) value = value.replace('(!UNKNOWN)', '') return value + + +def extract_options_from_list(options: t.Dict[str, t.Any], + options_to_extract: t.List[str]) -> t.List[t.Tuple[str, t.Any]]: + ''' return list of tuples (option, option_data) with option from options_to_extract ''' + return [(option, options[option]) for option in options_to_extract if option in options] + + +def remove_options_from_list(options: t.Dict[str, t.Any], + options_to_remove: t.List[str]) -> t.Dict[str, t.Any]: + ''' return copy of dictionary with the options from options_to_remove removed ''' + result = options.copy() + for option in options_to_remove: + result.pop(option, None) + return result diff --git a/src/antsibull_docs/schemas/ansible_doc.py b/src/antsibull_docs/schemas/ansible_doc.py index eb968833..4792adb2 100644 --- a/src/antsibull_docs/schemas/ansible_doc.py +++ b/src/antsibull_docs/schemas/ansible_doc.py @@ -13,5 +13,5 @@ warnings.warn('antsibull.schemas.ansible_doc is deprecated.' - ' Use antsibull.schemas.docs.ansible_doc instead.', + ' Use antsibull_docs.schemas.docs.ansible_doc instead.', DeprecationWarning, stacklevel=2) diff --git a/src/antsibull_docs/schemas/docs/__init__.py b/src/antsibull_docs/schemas/docs/__init__.py index d3819796..d1fe63e7 100644 --- a/src/antsibull_docs/schemas/docs/__init__.py +++ b/src/antsibull_docs/schemas/docs/__init__.py @@ -10,6 +10,7 @@ """ from .callback import CallbackDocSchema, CallbackSchema +from .positional import PositionalDocSchema, PositionalSchema from .module import ModuleDocSchema, ModuleSchema from .plugin import (PluginDocSchema, PluginExamplesSchema, PluginMetadataSchema, PluginReturnSchema, PluginSchema) @@ -37,6 +38,14 @@ 'return': PluginReturnSchema, } +_POSITIONAL_PLUGIN_SCHEMA_RECORD = { + 'top': PositionalSchema, + 'doc': PositionalDocSchema, + 'examples': PluginExamplesSchema, + 'metadata': PluginMetadataSchema, + 'return': PluginReturnSchema, +} + #: Mapping of plugin_types to the schemas which validate and normalize their documentation. #: The structure of this mapping is a two level nested dict. The outer key is the plugin_type. @@ -54,9 +63,10 @@ }, 'cliconf': _PLUGIN_SCHEMA_RECORD, 'connection': _PLUGIN_SCHEMA_RECORD, + 'filter': _POSITIONAL_PLUGIN_SCHEMA_RECORD, 'httpapi': _PLUGIN_SCHEMA_RECORD, 'inventory': _PLUGIN_SCHEMA_RECORD, - 'lookup': _PLUGIN_SCHEMA_RECORD, + 'lookup': _POSITIONAL_PLUGIN_SCHEMA_RECORD, 'module': { 'top': ModuleSchema, 'doc': ModuleDocSchema, @@ -67,6 +77,7 @@ 'netconf': _PLUGIN_SCHEMA_RECORD, 'shell': _PLUGIN_SCHEMA_RECORD, 'strategy': _PLUGIN_SCHEMA_RECORD, + 'test': _POSITIONAL_PLUGIN_SCHEMA_RECORD, 'vars': _PLUGIN_SCHEMA_RECORD, 'role': RoleSchema, } diff --git a/src/antsibull_docs/schemas/docs/ansible_doc.py b/src/antsibull_docs/schemas/docs/ansible_doc.py index 786bbdee..7b168d38 100644 --- a/src/antsibull_docs/schemas/docs/ansible_doc.py +++ b/src/antsibull_docs/schemas/docs/ansible_doc.py @@ -17,6 +17,7 @@ from .base import BaseModel from .callback import CallbackSchema +from .positional import PositionalSchema from .module import ModuleSchema from .plugin import PluginSchema from .role import RoleSchema @@ -26,7 +27,7 @@ 'CallbackPluginSchema', 'CliConfPluginSchema', 'ConnectionPluginSchema', 'HttpApiPluginSchema', 'InventoryPluginSchema', 'LookupPluginSchema', 'ModulePluginSchema', 'NetConfPluginSchema', 'ShellPluginSchema', 'StrategyPluginSchema', - 'VarsPluginSchema',) + 'VarsPluginSchema', 'TestPluginSchema', 'FilterPluginSchema',) class AnsibleDocSchema(BaseModel): @@ -54,13 +55,15 @@ class AnsibleDocSchema(BaseModel): callback: t.Dict[str, CallbackSchema] cliconf: t.Dict[str, PluginSchema] connection: t.Dict[str, PluginSchema] + filter: t.Dict[str, PositionalSchema] httpapi: t.Dict[str, PluginSchema] inventory: t.Dict[str, PluginSchema] - lookup: t.Dict[str, PluginSchema] + lookup: t.Dict[str, PositionalSchema] module: t.Dict[str, ModuleSchema] netconf: t.Dict[str, PluginSchema] shell: t.Dict[str, PluginSchema] strategy: t.Dict[str, PluginSchema] + test: t.Dict[str, PositionalSchema] vars: t.Dict[str, PluginSchema] role: t.Dict[str, RoleSchema] @@ -89,6 +92,18 @@ class CallbackPluginSchema(BaseModel): __root__: t.Dict[str, CallbackSchema] +class PositionalPluginSchema(BaseModel): + """ + Document the output of ``ansible-doc -t lookup CALLBACK_NAME``, or ``-t filter``, ``-t test``. + + .. note:: Both the model and the dict will be wrapped in an outer dict with your data mapped + to the ``__root__`` key. This happens because the toplevel key of ansible-doc's output is + a dynamic key which we can't automatically map to an attribute name. + """ + + __root__: t.Dict[str, PositionalSchema] + + class ModulePluginSchema(BaseModel): """ Document the output of ``ansible-doc -t module MODULE_NAME``. @@ -121,11 +136,13 @@ class RolePluginSchema(BaseModel): ConnectionPluginSchema = GenericPluginSchema HttpApiPluginSchema = GenericPluginSchema InventoryPluginSchema = GenericPluginSchema -LookupPluginSchema = GenericPluginSchema +LookupPluginSchema = PositionalPluginSchema NetConfPluginSchema = GenericPluginSchema ShellPluginSchema = GenericPluginSchema StrategyPluginSchema = GenericPluginSchema VarsPluginSchema = GenericPluginSchema +TestPluginSchema = PositionalPluginSchema +FilterPluginSchema = PositionalPluginSchema #: A mapping from plugin type to the Schema to use for them. Use this to more easily get @@ -136,6 +153,7 @@ class RolePluginSchema(BaseModel): 'callback': CallbackPluginSchema, 'cliconf': CliConfPluginSchema, 'connection': ConnectionPluginSchema, + 'filter': FilterPluginSchema, 'httpapi': HttpApiPluginSchema, 'inventory': InventoryPluginSchema, 'lookup': LookupPluginSchema, @@ -144,5 +162,6 @@ class RolePluginSchema(BaseModel): 'role': RolePluginSchema, 'shell': ShellPluginSchema, 'strategy': StrategyPluginSchema, + 'test': TestPluginSchema, 'vars': VarsPluginSchema, } diff --git a/src/antsibull_docs/schemas/docs/base.py b/src/antsibull_docs/schemas/docs/base.py index 8d579629..4b93790c 100644 --- a/src/antsibull_docs/schemas/docs/base.py +++ b/src/antsibull_docs/schemas/docs/base.py @@ -139,7 +139,7 @@ def handle_renamed_attribute(cls, values): REQUIRED_ENV_VAR_F = p.Field(..., regex='[A-Z_]+') #: option types are a set of strings that represent the types handled by argspec. -OPTION_TYPE_F = p.Field('str', regex='^(bits|bool|bytes|dict|float|int|json|jsonarg|list' +OPTION_TYPE_F = p.Field('str', regex='^(any|bits|bool|bytes|dict|float|int|json|jsonarg|list' '|path|raw|sid|str|tmppath|pathspec|pathlist)$') #: Constrained string type for version numbers @@ -251,12 +251,15 @@ def normalize_option_type_names(obj): if obj == 'dictionary': return 'dict' - if obj == 'lists': + if obj in ('lists', 'tuple'): return 'list' if obj in ('tmp', 'temppath'): return 'tmppath' + if obj == 'raw': + return 'any' + return obj @@ -264,9 +267,6 @@ def normalize_return_type_names(obj): """Normalize common mispellings of return type names.""" obj = normalize_option_type_names(obj) - if obj == 'raw': - return 'any' - return obj diff --git a/src/antsibull_docs/schemas/docs/positional.py b/src/antsibull_docs/schemas/docs/positional.py new file mode 100644 index 00000000..bdf01136 --- /dev/null +++ b/src/antsibull_docs/schemas/docs/positional.py @@ -0,0 +1,50 @@ +# coding: utf-8 +# Author: Toshio Kuratomi +# Author: Felix Fontein +# License: GPLv3+ +# Copyright: Ansible Project, 2022 +"""Schemas for the plugin DOCUMENTATION data.""" + +import typing as t + +import pydantic as p + +from .base import BaseModel +from .plugin import InnerDocSchema, PluginExamplesSchema, PluginMetadataSchema, PluginReturnSchema + + +class InnerPositionalDocSchema(InnerDocSchema): + """ + Schema describing the structure of documentation for plugins with positional parameters. + """ + + positional: t.List[str] = [] + + @p.root_validator(pre=True) + # pylint:disable=no-self-argument,no-self-use + def add_default_positional(cls, values): + """ + Remove example in favor of sample. + + Having both sample and example is redundant. Many more plugins are using sample so + standardize on that. + """ + positional = values.get('positional', []) + + if isinstance(positional, str): + positional = [part.strip() for part in positional.split(',')] if positional else [] + + values['positional'] = positional + return values + + +# Ignore Uninitialized attribute error as BaseModel works some magic to initialize the +# attributes when data is loaded into them. +# pyre-ignore[13] +class PositionalDocSchema(BaseModel): + doc: InnerPositionalDocSchema + + +class PositionalSchema(PositionalDocSchema, PluginExamplesSchema, PluginMetadataSchema, + PluginReturnSchema, BaseModel): + """Documentation of plugins with positional parameters.""" diff --git a/src/antsibull_docs/write_docs.py b/src/antsibull_docs/write_docs.py index e9f984ae..801cea53 100644 --- a/src/antsibull_docs/write_docs.py +++ b/src/antsibull_docs/write_docs.py @@ -142,7 +142,7 @@ async def write_plugin_rst(collection_name: str, # Guess path inside collection tree gh_path = f"{gh_plugin_dir}/{plugin_short_name}.py" # If we have more precise information, use that! - if 'doc' in plugin_record and 'filename' in plugin_record['doc']: + if plugin_record and 'doc' in plugin_record and 'filename' in plugin_record['doc']: filename = follow_relative_links(plugin_record['doc']['filename']) gh_path = os.path.relpath(filename, collection_meta.path) # Compose path diff --git a/tests/functional/schema/good_data/one_filter.json b/tests/functional/schema/good_data/one_filter.json new file mode 100644 index 00000000..69f5e0e2 --- /dev/null +++ b/tests/functional/schema/good_data/one_filter.json @@ -0,0 +1,50 @@ +{ + "ternary": { + "doc": { + "author": "Brian Coca (@bcoca)", + "collection": "ansible.builtin", + "description": [ + "Return the first value if the input is C(True), the second if C(False)." + ], + "filename": "/home/felix/projects/code/github-cloned/ansible/lib/ansible/plugins/filter/ternary.yml", + "name": "ternary", + "notes": [ + "vars as values are evaluated even if not returned. This is due to them being evaluated before being passed into the filter." + ], + "options": { + "_input": { + "description": "A boolean expression, must evaluate to C(True) or C(False).", + "required": true, + "type": "bool" + }, + "false_val": { + "description": "Value to return if the input is C(False).", + "type": "any" + }, + "none_val": { + "description": "Value to return if the input is C(None). If not set, C(None) will be treated as C(False).", + "type": "any", + "version_added": "2.8", + "version_added_collection": "ansible.builtin" + }, + "true_val": { + "description": "Value to return if the input is C(True).", + "required": true, + "type": "any" + } + }, + "positional": "true_val, false_val", + "short_description": "Ternary operation filter", + "version_added": "1.9", + "version_added_collection": "ansible.builtin" + }, + "examples": "# set first 10 volumes rw, rest as dp\nvolume_mode: \"{{ (item|int < 11)|ternary('rw', 'dp') }}\"\n\n# choose correct vpc subnet id, note that vars as values are evaluated even if not returned\nvpc_subnet_id: \"{{ (ec2_subnet_type == 'public') | ternary(ec2_vpc_public_subnet_id, ec2_vpc_private_subnet_id) }}\"\n\n- name: service-foo, use systemd module unless upstart is present, then use old service module\n service:\n state: restarted\n enabled: yes\n use: \"{{ (ansible_service_mgr == 'upstart') | ternary('service', 'systemd') }}\"\n", + "metadata": null, + "return": { + "_value": { + "description": "The value indicated by the input.", + "type": "any" + } + } + } +} diff --git a/tests/functional/schema/good_data/one_filter_results.json b/tests/functional/schema/good_data/one_filter_results.json new file mode 100644 index 00000000..6be103f4 --- /dev/null +++ b/tests/functional/schema/good_data/one_filter_results.json @@ -0,0 +1,133 @@ +{ + "__root__": { + "ternary": { + "doc": { + "aliases": [], + "attributes": {}, + "author": [ + "Brian Coca (@bcoca)" + ], + "collection": "ansible.builtin", + "deprecated": {}, + "description": [ + "Return the first value if the input is C(True), the second if C(False)." + ], + "extends_documentation_fragment": [], + "filename": "/home/felix/projects/code/github-cloned/ansible/lib/ansible/plugins/filter/ternary.yml", + "name": "ternary", + "notes": [ + "vars as values are evaluated even if not returned. This is due to them being evaluated before being passed into the filter." + ], + "options": { + "_input": { + "aliases": [], + "choices": [], + "cli": [], + "default": null, + "deprecated": {}, + "description": [ + "A boolean expression, must evaluate to C(True) or C(False)." + ], + "elements": "str", + "env": [], + "ini": [], + "keyword": [], + "required": true, + "suboptions": {}, + "type": "bool", + "vars": [], + "version_added": "historical", + "version_added_collection": "" + }, + "false_val": { + "aliases": [], + "choices": [], + "cli": [], + "default": null, + "deprecated": {}, + "description": [ + "Value to return if the input is C(False)." + ], + "elements": "str", + "env": [], + "ini": [], + "keyword": [], + "required": false, + "suboptions": {}, + "type": "any", + "vars": [], + "version_added": "historical", + "version_added_collection": "" + }, + "none_val": { + "aliases": [], + "choices": [], + "cli": [], + "default": null, + "deprecated": {}, + "description": [ + "Value to return if the input is C(None). If not set, C(None) will be treated as C(False)." + ], + "elements": "str", + "env": [], + "ini": [], + "keyword": [], + "required": false, + "suboptions": {}, + "type": "any", + "vars": [], + "version_added": "2.8", + "version_added_collection": "ansible.builtin" + }, + "true_val": { + "aliases": [], + "choices": [], + "cli": [], + "default": null, + "deprecated": {}, + "description": [ + "Value to return if the input is C(True)." + ], + "elements": "str", + "env": [], + "ini": [], + "keyword": [], + "required": true, + "suboptions": {}, + "type": "any", + "vars": [], + "version_added": "historical", + "version_added_collection": "" + } + }, + "positional": [ + "true_val", + "false_val" + ], + "requirements": [], + "seealso": [], + "short_description": "Ternary operation filter", + "todo": [], + "version_added": "1.9", + "version_added_collection": "ansible.builtin" + }, + "examples": "# set first 10 volumes rw, rest as dp\nvolume_mode: \"{{ (item|int < 11)|ternary('rw', 'dp') }}\"\n\n# choose correct vpc subnet id, note that vars as values are evaluated even if not returned\nvpc_subnet_id: \"{{ (ec2_subnet_type == 'public') | ternary(ec2_vpc_public_subnet_id, ec2_vpc_private_subnet_id) }}\"\n\n- name: service-foo, use systemd module unless upstart is present, then use old service module\n service:\n state: restarted\n enabled: yes\n use: \"{{ (ansible_service_mgr == 'upstart') | ternary('service', 'systemd') }}\"\n", + "metadata": null, + "return_": { + "_value": { + "choices": [], + "contains": {}, + "description": [ + "The value indicated by the input." + ], + "elements": "str", + "returned": "success", + "sample": null, + "type": "any", + "version_added": "historical", + "version_added_collection": "" + } + } + } + } +} \ No newline at end of file diff --git a/tests/functional/schema/good_data/one_lookup_results.json b/tests/functional/schema/good_data/one_lookup_results.json index 3430a812..831e46c2 100644 --- a/tests/functional/schema/good_data/one_lookup_results.json +++ b/tests/functional/schema/good_data/one_lookup_results.json @@ -265,6 +265,7 @@ "version_added_collection": "" } }, + "positional": [], "requirements": [ "python-consul python library U(https://python-consul.readthedocs.io/en/latest/#installation)" ], diff --git a/tests/functional/schema/good_data/one_test.json b/tests/functional/schema/good_data/one_test.json new file mode 100644 index 00000000..ce07e8b4 --- /dev/null +++ b/tests/functional/schema/good_data/one_test.json @@ -0,0 +1,32 @@ +{ + "changed": { + "doc": { + "author": "Ansible Core", + "collection": "ansible.builtin", + "description": [ + "Tests if task required changes to complete", + "This test checks for the existance of a C(changed) key in the input dictionary and that it is C(True) if present" + ], + "filename": "/home/felix/projects/code/github-cloned/ansible/lib/ansible/plugins/test/changed.yml", + "name": "changed", + "options": { + "_input": { + "description": "registered result from an Ansible task", + "required": true, + "type": "dictionary" + } + }, + "short_description": "check if task required changes", + "version_added": "1.9", + "version_added_collection": "ansible.builtin" + }, + "examples": "# test 'status' to know how to respond\n{{ (taskresults is changed }}\n", + "metadata": null, + "return": { + "_value": { + "description": "Returns C(True) if the task was required changes, C(False) otherwise.", + "type": "boolean" + } + } + } +} diff --git a/tests/functional/schema/good_data/one_test_results.json b/tests/functional/schema/good_data/one_test_results.json new file mode 100644 index 00000000..3f6615db --- /dev/null +++ b/tests/functional/schema/good_data/one_test_results.json @@ -0,0 +1,69 @@ +{ + "__root__": { + "changed": { + "doc": { + "aliases": [], + "attributes": {}, + "author": [ + "Ansible Core" + ], + "collection": "ansible.builtin", + "deprecated": {}, + "description": [ + "Tests if task required changes to complete", + "This test checks for the existance of a C(changed) key in the input dictionary and that it is C(True) if present" + ], + "extends_documentation_fragment": [], + "filename": "/home/felix/projects/code/github-cloned/ansible/lib/ansible/plugins/test/changed.yml", + "name": "changed", + "notes": [], + "options": { + "_input": { + "aliases": [], + "choices": [], + "cli": [], + "default": null, + "deprecated": {}, + "description": [ + "registered result from an Ansible task" + ], + "elements": "str", + "env": [], + "ini": [], + "keyword": [], + "required": true, + "suboptions": {}, + "type": "dict", + "vars": [], + "version_added": "historical", + "version_added_collection": "" + } + }, + "positional": [], + "requirements": [], + "seealso": [], + "short_description": "check if task required changes", + "todo": [], + "version_added": "1.9", + "version_added_collection": "ansible.builtin" + }, + "examples": "# test 'status' to know how to respond\n{{ (taskresults is changed }}\n", + "metadata": null, + "return_": { + "_value": { + "choices": [], + "contains": {}, + "description": [ + "Returns C(True) if the task was required changes, C(False) otherwise." + ], + "elements": "str", + "returned": "success", + "sample": null, + "type": "bool", + "version_added": "historical", + "version_added_collection": "" + } + } + } + } +} \ No newline at end of file diff --git a/tests/functional/schema/test_schema.py b/tests/functional/schema/test_schema.py index 884f3dc1..ed0d28d7 100644 --- a/tests/functional/schema/test_schema.py +++ b/tests/functional/schema/test_schema.py @@ -21,7 +21,7 @@ # dumping the file:: # # import json -# from antsibull.schemas.docs.ansible_doc import ConnectionPluginSchema +# from antsibull_docs.schemas.docs.ansible_doc import ConnectionPluginSchema # raw = open('one_connection.json').read() # normalized = ConnectionPluginSchema.parse_raw(raw) # out = json.dumps(normalized.dict(), indent=4, sort_keys=True) @@ -32,6 +32,7 @@ 'one_callback.json': ad.CallbackPluginSchema, 'one_cliconf.json': ad.CliConfPluginSchema, 'one_connection.json': ad.ConnectionPluginSchema, + 'one_filter.json': ad.FilterPluginSchema, 'one_httpapi.json': ad.HttpApiPluginSchema, 'one_inventory.json': ad.InventoryPluginSchema, 'one_lookup.json': ad.LookupPluginSchema, @@ -39,6 +40,7 @@ 'one_netconf.json': ad.NetConfPluginSchema, 'one_shell.json': ad.ShellPluginSchema, 'one_strategy.json': ad.StrategyPluginSchema, + 'one_test.json': ad.TestPluginSchema, 'one_vars.json': ad.VarsPluginSchema, }