From d501af6eae8dda71c8c1c0d6513c3d851ca98603 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Wed, 21 Sep 2022 22:18:14 +0200 Subject: [PATCH 1/2] Allow to specify choices as dictionary instead of list. --- changelogs/fragments/36-choices-dict.yml | 2 + .../data/docsite/macros/parameters.rst.j2 | 88 ++++++++++++----- .../data/docsite/macros/returnvalues.rst.j2 | 96 +++++++++++++------ src/antsibull_docs/schemas/docs/base.py | 17 +++- src/antsibull_docs/schemas/docs/plugin.py | 12 ++- 5 files changed, 161 insertions(+), 54 deletions(-) create mode 100644 changelogs/fragments/36-choices-dict.yml diff --git a/changelogs/fragments/36-choices-dict.yml b/changelogs/fragments/36-choices-dict.yml new file mode 100644 index 00000000..622498b8 --- /dev/null +++ b/changelogs/fragments/36-choices-dict.yml @@ -0,0 +1,2 @@ +minor_changes: + - "Allow to specify choices as dictionary instead of list (https://github.com/ansible-community/antsibull-docs/pull/36)." diff --git a/src/antsibull_docs/data/docsite/macros/parameters.rst.j2 b/src/antsibull_docs/data/docsite/macros/parameters.rst.j2 index 67875565..9f7a3d94 100644 --- a/src/antsibull_docs/data/docsite/macros/parameters.rst.j2 +++ b/src/antsibull_docs/data/docsite/macros/parameters.rst.j2 @@ -72,7 +72,7 @@ {% for i in range(1, loop.depth) %}
{% endfor %}
-{% for desc in value['description'] %} +{% for desc in value['description'] %} @{ desc | replace('\n', '\n ') | rst_ify | indent(6) }@ {% endfor %} @@ -93,19 +93,39 @@ :ansible-option-choices:`Choices:` -{% for choice in value['choices'] %} -{# Turn boolean values in 'true' and 'false' values #} -{% if choice is sameas true %} -{% set choice = 'true' %} -{% elif choice is sameas false %} -{% set choice = 'false' %} -{% endif %} -{% if (value['default'] is not list and value['default'] == choice) or (value['default'] is list and choice in value['default']) %} +{% if value['choices'] is mapping %} +{% for choice, desc in value['choices'] | dictsort %} +{# Turn boolean values in 'true' and 'false' values #} +{% if choice is sameas true %} +{% set choice = 'true' %} +{% elif choice is sameas false %} +{% set choice = 'false' %} +{% endif %} +{% if (value['default'] is not list and value['default'] == choice) or (value['default'] is list and choice in value['default']) %} + - :ansible-option-default-bold:`@{ choice | rst_escape(escape_ending_whitespace=true) }@` :ansible-option-default:`(default)`\ : +{% else %} + - :ansible-option-choices-entry:`@{ choice | rst_escape(escape_ending_whitespace=true) }@`\ : +{% endif %} +{% for par in desc %} + @{ par | rst_ify | indent(8) }@ + +{% endfor %} +{% endfor %} +{% else %} +{% for choice in value['choices'] %} +{# Turn boolean values in 'true' and 'false' values #} +{% if choice is sameas true %} +{% set choice = 'true' %} +{% elif choice is sameas false %} +{% set choice = 'false' %} +{% endif %} +{% if (value['default'] is not list and value['default'] == choice) or (value['default'] is list and choice in value['default']) %} - :ansible-option-default-bold:`@{ choice | rst_escape(escape_ending_whitespace=true) }@` :ansible-option-default:`← (default)` -{% else %} +{% else %} - :ansible-option-choices-entry:`@{ choice | rst_escape(escape_ending_whitespace=true) }@` -{% endif %} -{% endfor %} +{% endif %} +{% endfor %} +{% endif %} {% endif %} {# Show default value, when multiple choice or no choices #} {% if value['default'] is not none and value['default'] not in value['choices'] %} @@ -240,19 +260,41 @@ {% if value['choices'] %}

Choices:

{% endif %} {# Show default value, when multiple choice or no choices #} diff --git a/src/antsibull_docs/data/docsite/macros/returnvalues.rst.j2 b/src/antsibull_docs/data/docsite/macros/returnvalues.rst.j2 index eac46cdb..c71ae931 100644 --- a/src/antsibull_docs/data/docsite/macros/returnvalues.rst.j2 +++ b/src/antsibull_docs/data/docsite/macros/returnvalues.rst.j2 @@ -58,16 +58,16 @@ {% for i in range(1, loop.depth) %}
{% endfor %}
-{% for desc in value['description'] %} +{% for desc in value['description'] %} @{ desc | rst_ify | indent(6) }@ {% endfor %} -{% if value['returned'] %} +{% if value['returned'] %} .. rst-class:: ansible-option-line :ansible-option-returned-bold:`Returned:` @{ value['returned'] | rst_ify | indent(6) }@ -{% endif %} +{% endif %} {# Show possible choices and highlight details #} {% if value['choices'] %} @@ -75,19 +75,39 @@ :ansible-option-choices:`Can only return:` -{% for choice in value['choices'] %} -{# Turn boolean values in 'true' and 'false' values #} -{% if choice is sameas true %} -{% set choice = 'true' %} -{% elif choice is sameas false %} -{% set choice = 'false' %} -{% endif %} -{% if (value['default'] is not list and value['default'] == choice) or (value['default'] is list and choice in value['default']) %} - - :ansible-option-default-bold:`@{ choice | rst_escape }@` :ansible-option-default:`← (default)` -{% else %} - - :ansible-option-choices-entry:`@{ choice | rst_escape }@` -{% endif %} -{% endfor %} +{% if value['choices'] is mapping %} +{% for choice, desc in value['choices'] | dictsort %} +{# Turn boolean values in 'true' and 'false' values #} +{% if choice is sameas true %} +{% set choice = 'true' %} +{% elif choice is sameas false %} +{% set choice = 'false' %} +{% endif %} +{% if (value['default'] is not list and value['default'] == choice) or (value['default'] is list and choice in value['default']) %} + - :ansible-option-default-bold:`@{ choice | rst_escape(escape_ending_whitespace=true) }@` :ansible-option-default:`(default)`\ : +{% else %} + - :ansible-option-choices-entry:`@{ choice | rst_escape(escape_ending_whitespace=true) }@`\ : +{% endif %} +{% for par in desc %} + @{ par | rst_ify | indent(8) }@ + +{% endfor %} +{% endfor %} +{% else %} +{% for choice in value['choices'] %} +{# Turn boolean values in 'true' and 'false' values #} +{% if choice is sameas true %} +{% set choice = 'true' %} +{% elif choice is sameas false %} +{% set choice = 'false' %} +{% endif %} +{% if (value['default'] is not list and value['default'] == choice) or (value['default'] is list and choice in value['default']) %} + - :ansible-option-default-bold:`@{ choice | rst_escape(escape_ending_whitespace=true) }@` :ansible-option-default:`← (default)` +{% else %} + - :ansible-option-choices-entry:`@{ choice | rst_escape(escape_ending_whitespace=true) }@` +{% endif %} +{% endfor %} +{% endif %} {% endif %} {% if value['sample'] is not none %} @@ -153,19 +173,41 @@ {% if value['choices'] %}

Can only return:

    -{% for choice in value['choices'] %} -{# Turn boolean values in 'true' and 'false' values #} -{% if choice is sameas true %} -{% set choice = 'true' %} -{% elif choice is sameas false %} -{% set choice = 'false' %} -{% endif %} -{% if (value['default'] is not list and value['default'] == choice) or (value['default'] is list and choice in value['default']) %} +{% if value['choices'] is mapping %} +{% for choice, desc in value['choices'] | dictsort %} +{# Turn boolean values in 'true' and 'false' values #} +{% if choice is sameas true %} +{% set choice = 'true' %} +{% elif choice is sameas false %} +{% set choice = 'false' %} +{% endif %} +
  • +{% if (value['default'] is not list and value['default'] == choice) or (value['default'] is list and choice in value['default']) %} +

    @{ choice | escape }@ (default): +{% else %} +

    @{ choice | escape }@: +{% endif %} + @{ desc | first | default('') | html_ify | indent(10, blank=true) }@

    +{% for line in desc[1:] %} +

    @{ line | html_ify | indent(10, blank=true) }@

    +{% endfor %} +
  • +{% endfor %} +{% else %} +{% for choice in value['choices'] %} +{# Turn boolean values in 'true' and 'false' values #} +{% if choice is sameas true %} +{% set choice = 'true' %} +{% elif choice is sameas false %} +{% set choice = 'false' %} +{% endif %} +{% if (value['default'] is not list and value['default'] == choice) or (value['default'] is list and choice in value['default']) %}
  • @{ choice | escape }@ ← (default)

  • -{% else %} +{% else %}
  • @{ choice | escape }@

  • -{% endif %} -{% endfor %} +{% endif %} +{% endfor %} +{% endif %}
{% endif %} {% if value['sample'] is not none %} diff --git a/src/antsibull_docs/schemas/docs/base.py b/src/antsibull_docs/schemas/docs/base.py index c890d505..d7a9bf5b 100644 --- a/src/antsibull_docs/schemas/docs/base.py +++ b/src/antsibull_docs/schemas/docs/base.py @@ -304,7 +304,7 @@ def normalize_return_type_names(obj): def normalize_value(values: t.Dict[str, t.Any], field: str, # noqa: C901 - is_list_of_values: bool = False) -> None: + is_list_of_values: bool = False, accept_dict: bool = False) -> None: if 'type' not in values or values.get(field) is None: return @@ -317,8 +317,12 @@ def normalize_value(values: t.Dict[str, t.Any], field: str, # noqa: C901 elements_name = normalize_option_type_names(values.get('elements')) elements_checker = TYPE_CHECKERS.get(elements_name) + descs = None if not is_list_of_values: value = [value] + elif accept_dict and isinstance(value, dict): + descs = list(value.values()) + value = list(value) elif not isinstance(value, list): return @@ -339,6 +343,9 @@ def normalize_value(values: t.Dict[str, t.Any], field: str, # noqa: C901 if not is_list_of_values: value = value[0] + elif descs is not None: + value = dict(zip(value, descs)) + values[field] = value @@ -444,7 +451,7 @@ def merge_typo_names(cls, values): class OptionsSchema(BaseModel): description: t.List[str] aliases: t.List[str] = [] - choices: t.List[t.Union[str, None]] = [] + choices: t.Union[t.List[t.Any], t.Dict[t.Any, t.List[str]]] = [] default: t.Any = None # JSON value elements: str = OPTION_TYPE_F required: bool = False @@ -508,8 +515,12 @@ def normalize_option_type(cls, obj): @p.root_validator(pre=True) # pylint:disable=no-self-argument,no-self-use def normalize_default_choices(cls, values): + if isinstance(values.get('choices'), dict): + for k, v in values['choices'].items(): + values['choices'][k] = list_from_scalars(v) normalize_value(values, 'default') - normalize_value(values, 'choices', is_list_of_values=values.get('type') != 'list') + normalize_value( + values, 'choices', is_list_of_values=values.get('type') != 'list', accept_dict=True) return values diff --git a/src/antsibull_docs/schemas/docs/plugin.py b/src/antsibull_docs/schemas/docs/plugin.py index e1c2b3cd..844ebffa 100644 --- a/src/antsibull_docs/schemas/docs/plugin.py +++ b/src/antsibull_docs/schemas/docs/plugin.py @@ -76,7 +76,7 @@ class ReturnSchema(BaseModel): """Schema of plugin return data docs.""" description: t.List[str] - choices: t.List[str] = [] + choices: t.Union[t.List[t.Any], t.Dict[t.Any, t.List[str]]] = [] elements: str = RETURN_TYPE_F returned: str = 'success' sample: t.Any = None # JSON value @@ -133,6 +133,16 @@ def normalize_sample(cls, values): pass return values + @p.root_validator(pre=True) + # pylint:disable=no-self-argument,no-self-use + def normalize_choices(cls, values): + if isinstance(values.get('choices'), dict): + for k, v in values['choices'].items(): + values['choices'][k] = list_from_scalars(v) + normalize_value( + values, 'choices', is_list_of_values=values.get('type') != 'list', accept_dict=True) + return values + class InnerReturnSchema(ReturnSchema): """Nested return schema which allows leaving out description.""" From 62f210113d25808623ba2d2bd618a0d4ca6be8f8 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Thu, 22 Sep 2022 06:15:23 +0200 Subject: [PATCH 2/2] Move common code into macro. --- .../data/docsite/macros/choiceslist.rst.j2 | 82 +++++++++++++++++++ .../data/docsite/macros/parameters.rst.j2 | 74 +---------------- .../data/docsite/macros/returnvalues.rst.j2 | 74 +---------------- 3 files changed, 90 insertions(+), 140 deletions(-) create mode 100644 src/antsibull_docs/data/docsite/macros/choiceslist.rst.j2 diff --git a/src/antsibull_docs/data/docsite/macros/choiceslist.rst.j2 b/src/antsibull_docs/data/docsite/macros/choiceslist.rst.j2 new file mode 100644 index 00000000..000463e5 --- /dev/null +++ b/src/antsibull_docs/data/docsite/macros/choiceslist.rst.j2 @@ -0,0 +1,82 @@ +{# + Copyright (c) Ansible Project + GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) + SPDX-License-Identifier: GPL-3.0-or-later +#} + +{% macro in_rst(choices, default_value=None, has_no_default=False) %} +{% if choices is mapping %} +{% for choice, desc in choices | dictsort %} +{# Turn boolean values in 'true' and 'false' values #} +{% if choice is sameas true %} +{% set choice = 'true' %} +{% elif choice is sameas false %} +{% set choice = 'false' %} +{% endif %} +{% if not has_no_default and ((default_value is not list and default_value == choice) or (default_value is list and choice in default_value)) %} + - :ansible-option-default-bold:`@{ choice | rst_escape(escape_ending_whitespace=true) }@` :ansible-option-default:`(default)`\ : +{% else %} + - :ansible-option-choices-entry:`@{ choice | rst_escape(escape_ending_whitespace=true) }@`\ : +{% endif %} +{% for par in desc %} + @{ par | rst_ify | indent(8) }@ + +{% endfor %} +{% endfor %} +{% else %} +{% for choice in choices %} +{# Turn boolean values in 'true' and 'false' values #} +{% if choice is sameas true %} +{% set choice = 'true' %} +{% elif choice is sameas false %} +{% set choice = 'false' %} +{% endif %} +{% if not has_no_default and ((default_value is not list and default_value == choice) or (default_value is list and choice in default_value)) %} + - :ansible-option-default-bold:`@{ choice | rst_escape(escape_ending_whitespace=true) }@` :ansible-option-default:`← (default)` +{% else %} + - :ansible-option-choices-entry:`@{ choice | rst_escape(escape_ending_whitespace=true) }@` +{% endif %} +{% endfor %} +{% endif %} +{% endmacro %} + + +{% macro in_html(choices, default_value=None, has_no_default=False) %} +
    +{% if choices is mapping %} +{% for choice, desc in choices | dictsort %} +{# Turn boolean values in 'true' and 'false' values #} +{% if choice is sameas true %} +{% set choice = 'true' %} +{% elif choice is sameas false %} +{% set choice = 'false' %} +{% endif %} +
  • +{% if not has_no_default and ((default_value is not list and default_value == choice) or (default_value is list and choice in default_value)) %} +

    @{ choice | escape }@ (default): +{% else %} +

    @{ choice | escape }@: +{% endif %} + @{ desc | first | default('') | html_ify | indent(10, blank=true) }@

    +{% for line in desc[1:] %} +

    @{ line | html_ify | indent(10, blank=true) }@

    +{% endfor %} +
  • +{% endfor %} +{% else %} +{% for choice in choices %} +{# Turn boolean values in 'true' and 'false' values #} +{% if choice is sameas true %} +{% set choice = 'true' %} +{% elif choice is sameas false %} +{% set choice = 'false' %} +{% endif %} +{% if not has_no_default and ((default_value is not list and default_value == choice) or (default_value is list and choice in default_value)) %} +
  • @{ choice | escape }@ ← (default)

  • +{% else %} +
  • @{ choice | escape }@

  • +{% endif %} +{% endfor %} +{% endif %} +
+{% endmacro %} diff --git a/src/antsibull_docs/data/docsite/macros/parameters.rst.j2 b/src/antsibull_docs/data/docsite/macros/parameters.rst.j2 index 9f7a3d94..c24ba1d6 100644 --- a/src/antsibull_docs/data/docsite/macros/parameters.rst.j2 +++ b/src/antsibull_docs/data/docsite/macros/parameters.rst.j2 @@ -4,6 +4,8 @@ SPDX-License-Identifier: GPL-3.0-or-later #} +{% from 'macros/choiceslist.rst.j2' import in_rst as choices_rst %} +{% from 'macros/choiceslist.rst.j2' import in_html as choices_html %} {% from 'macros/deprecates.rst.j2' import in_rst as deprecates_rst with context %} {% from 'macros/deprecates.rst.j2' import in_html as deprecates_html with context %} {% from 'macros/version_added.rst.j2' import version_added_rst, version_added_html %} @@ -93,39 +95,7 @@ :ansible-option-choices:`Choices:` -{% if value['choices'] is mapping %} -{% for choice, desc in value['choices'] | dictsort %} -{# Turn boolean values in 'true' and 'false' values #} -{% if choice is sameas true %} -{% set choice = 'true' %} -{% elif choice is sameas false %} -{% set choice = 'false' %} -{% endif %} -{% if (value['default'] is not list and value['default'] == choice) or (value['default'] is list and choice in value['default']) %} - - :ansible-option-default-bold:`@{ choice | rst_escape(escape_ending_whitespace=true) }@` :ansible-option-default:`(default)`\ : -{% else %} - - :ansible-option-choices-entry:`@{ choice | rst_escape(escape_ending_whitespace=true) }@`\ : -{% endif %} -{% for par in desc %} - @{ par | rst_ify | indent(8) }@ - -{% endfor %} -{% endfor %} -{% else %} -{% for choice in value['choices'] %} -{# Turn boolean values in 'true' and 'false' values #} -{% if choice is sameas true %} -{% set choice = 'true' %} -{% elif choice is sameas false %} -{% set choice = 'false' %} -{% endif %} -{% if (value['default'] is not list and value['default'] == choice) or (value['default'] is list and choice in value['default']) %} - - :ansible-option-default-bold:`@{ choice | rst_escape(escape_ending_whitespace=true) }@` :ansible-option-default:`← (default)` -{% else %} - - :ansible-option-choices-entry:`@{ choice | rst_escape(escape_ending_whitespace=true) }@` -{% endif %} -{% endfor %} -{% endif %} +@{ choices_rst(value['choices'], value['default']) }@ {% endif %} {# Show default value, when multiple choice or no choices #} {% if value['default'] is not none and value['default'] not in value['choices'] %} @@ -259,43 +229,7 @@ {# Show possible choices and highlight details #} {% if value['choices'] %}

Choices:

-
    -{% if value['choices'] is mapping %} -{% for choice, desc in value['choices'] | dictsort %} -{# Turn boolean values in 'true' and 'false' values #} -{% if choice is sameas true %} -{% set choice = 'true' %} -{% elif choice is sameas false %} -{% set choice = 'false' %} -{% endif %} -
  • -{% if (value['default'] is not list and value['default'] == choice) or (value['default'] is list and choice in value['default']) %} -

    @{ choice | escape }@ (default): -{% else %} -

    @{ choice | escape }@: -{% endif %} - @{ desc | first | default('') | html_ify | indent(10, blank=true) }@

    -{% for line in desc[1:] %} -

    @{ line | html_ify | indent(10, blank=true) }@

    -{% endfor %} -
  • -{% endfor %} -{% else %} -{% for choice in value['choices'] %} -{# Turn boolean values in 'true' and 'false' values #} -{% if choice is sameas true %} -{% set choice = 'true' %} -{% elif choice is sameas false %} -{% set choice = 'false' %} -{% endif %} -{% if (value['default'] is not list and value['default'] == choice) or (value['default'] is list and choice in value['default']) %} -
  • @{ choice | escape }@ ← (default)

  • -{% else %} -
  • @{ choice | escape }@

  • -{% endif %} -{% endfor %} -{% endif %} -
+@{ choices_html(value['choices'], value['default']) }@ {% endif %} {# Show default value, when multiple choice or no choices #} {% if value['default'] is not none and value['default'] not in value['choices'] %} diff --git a/src/antsibull_docs/data/docsite/macros/returnvalues.rst.j2 b/src/antsibull_docs/data/docsite/macros/returnvalues.rst.j2 index c71ae931..7b2c25a9 100644 --- a/src/antsibull_docs/data/docsite/macros/returnvalues.rst.j2 +++ b/src/antsibull_docs/data/docsite/macros/returnvalues.rst.j2 @@ -4,6 +4,8 @@ SPDX-License-Identifier: GPL-3.0-or-later #} +{% from 'macros/choiceslist.rst.j2' import in_rst as choices_rst %} +{% from 'macros/choiceslist.rst.j2' import in_html as choices_html %} {% from 'macros/version_added.rst.j2' import version_added_rst, version_added_html %} {% macro in_rst(elements) %} @@ -75,39 +77,7 @@ :ansible-option-choices:`Can only return:` -{% if value['choices'] is mapping %} -{% for choice, desc in value['choices'] | dictsort %} -{# Turn boolean values in 'true' and 'false' values #} -{% if choice is sameas true %} -{% set choice = 'true' %} -{% elif choice is sameas false %} -{% set choice = 'false' %} -{% endif %} -{% if (value['default'] is not list and value['default'] == choice) or (value['default'] is list and choice in value['default']) %} - - :ansible-option-default-bold:`@{ choice | rst_escape(escape_ending_whitespace=true) }@` :ansible-option-default:`(default)`\ : -{% else %} - - :ansible-option-choices-entry:`@{ choice | rst_escape(escape_ending_whitespace=true) }@`\ : -{% endif %} -{% for par in desc %} - @{ par | rst_ify | indent(8) }@ - -{% endfor %} -{% endfor %} -{% else %} -{% for choice in value['choices'] %} -{# Turn boolean values in 'true' and 'false' values #} -{% if choice is sameas true %} -{% set choice = 'true' %} -{% elif choice is sameas false %} -{% set choice = 'false' %} -{% endif %} -{% if (value['default'] is not list and value['default'] == choice) or (value['default'] is list and choice in value['default']) %} - - :ansible-option-default-bold:`@{ choice | rst_escape(escape_ending_whitespace=true) }@` :ansible-option-default:`← (default)` -{% else %} - - :ansible-option-choices-entry:`@{ choice | rst_escape(escape_ending_whitespace=true) }@` -{% endif %} -{% endfor %} -{% endif %} +@{ choices_rst(value['choices'], has_no_default=True) }@ {% endif %} {% if value['sample'] is not none %} @@ -172,43 +142,7 @@ {# Show possible choices and highlight details #} {% if value['choices'] %}

Can only return:

-
    -{% if value['choices'] is mapping %} -{% for choice, desc in value['choices'] | dictsort %} -{# Turn boolean values in 'true' and 'false' values #} -{% if choice is sameas true %} -{% set choice = 'true' %} -{% elif choice is sameas false %} -{% set choice = 'false' %} -{% endif %} -
  • -{% if (value['default'] is not list and value['default'] == choice) or (value['default'] is list and choice in value['default']) %} -

    @{ choice | escape }@ (default): -{% else %} -

    @{ choice | escape }@: -{% endif %} - @{ desc | first | default('') | html_ify | indent(10, blank=true) }@

    -{% for line in desc[1:] %} -

    @{ line | html_ify | indent(10, blank=true) }@

    -{% endfor %} -
  • -{% endfor %} -{% else %} -{% for choice in value['choices'] %} -{# Turn boolean values in 'true' and 'false' values #} -{% if choice is sameas true %} -{% set choice = 'true' %} -{% elif choice is sameas false %} -{% set choice = 'false' %} -{% endif %} -{% if (value['default'] is not list and value['default'] == choice) or (value['default'] is list and choice in value['default']) %} -
  • @{ choice | escape }@ ← (default)

  • -{% else %} -
  • @{ choice | escape }@

  • -{% endif %} -{% endfor %} -{% endif %} -
+@{ choices_html(value['choices'], has_no_default=True) }@ {% endif %} {% if value['sample'] is not none %}

Sample: @{ value['sample'] | antsibull_to_json | escape | indent(6, blank=true) }@