Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix redirect finding and alias reporting #183

Merged
merged 4 commits into from
Jun 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions changelogs/fragments/183-routing.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
bugfixes:
- "When looking for redirects, the ``aliases`` field and filesystem redirects in ansible-core were not properly considered.
This ensures that all redirect stubs are created, and that no duplicates show up, not depending on whether ansible-core is installed in editable mode or not
(https://github.com/ansible-community/antsibull-docs/pull/183)."
- "Make sure that all aliases are actually listed for plugins (https://github.com/ansible-community/antsibull-docs/pull/183)."
38 changes: 34 additions & 4 deletions src/antsibull_docs/augment_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
from __future__ import annotations

import typing as t
from collections import defaultdict
from collections.abc import Mapping, MutableMapping

from antsibull_docs.utils.rst import massage_rst_label
from .docs_parsing.routing import CollectionRoutingT
from .utils.rst import massage_rst_label


def add_full_key(
Expand Down Expand Up @@ -99,7 +101,25 @@ def _add_seealso(
entry["description"] = desc


def augment_docs(plugin_info: MutableMapping[str, MutableMapping[str, t.Any]]) -> None:
def _add_aliases(
plugin_name: str,
plugin_record: MutableMapping[str, t.Any],
routing_sources: list[str],
) -> None:
collection_prefix = ".".join(plugin_name.split(".", 2)[:2]) + "."
for redirect in routing_sources:
if redirect.startswith(collection_prefix):
alias = redirect[len(collection_prefix) :]
if "aliases" not in plugin_record:
plugin_record["aliases"] = []
if alias not in plugin_record["aliases"]:
plugin_record["aliases"].append(alias)


def augment_docs(
plugin_info: MutableMapping[str, MutableMapping[str, t.Any]],
collection_routing: CollectionRoutingT,
) -> None:
"""
Add additional data to the data extracted from the plugins.

Expand All @@ -115,14 +135,24 @@ def augment_docs(plugin_info: MutableMapping[str, MutableMapping[str, t.Any]]) -
.. warning:: This function operates by side-effect. The plugin_info dictionay is modified
directly.
"""
for _, plugin_map in plugin_info.items():
for _, plugin_record in plugin_map.items():
for plugin_type, plugin_map in plugin_info.items():
# First collect all routing targets
routing_sources: defaultdict[str, list[str]] = defaultdict(list)
for plugin_name, plugin_meta in collection_routing.get(plugin_type, {}).items():
redirect = plugin_meta.get("redirect")
if isinstance(redirect, str):
routing_sources[redirect].append(plugin_name)
# Now augment all plugin records
for plugin_name, plugin_record in plugin_map.items():
if plugin_record.get("return"):
add_full_key(plugin_record["return"], "contains")
if plugin_record.get("doc"):
add_full_key(plugin_record["doc"]["options"], "suboptions")
if plugin_record["doc"].get("seealso"):
_add_seealso(plugin_record["doc"]["seealso"], plugin_info)
_add_aliases(
plugin_name, plugin_record["doc"], routing_sources[plugin_name]
)
if plugin_record.get("entry_points"):
for entry_point in plugin_record["entry_points"].values():
add_full_key(entry_point["options"], "options")
4 changes: 3 additions & 1 deletion src/antsibull_docs/cli/doc_commands/_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,15 @@ def generate_docs_for_all_collections(

remove_redirect_duplicates(plugin_info, collection_routing)
stubs_info = find_stubs(plugin_info, collection_routing)
if collection_names is not None and "ansible.builtin" not in collection_names:
stubs_info.pop("ansible.builtin", None)
# flog.fields(stubs_info=stubs_info).debug('Stubs info')

new_plugin_info, nonfatal_errors = asyncio.run(
normalize_all_plugin_info(plugin_info)
)
flog.fields(errors=len(nonfatal_errors)).notice("Finished data validation")
augment_docs(new_plugin_info)
augment_docs(new_plugin_info, collection_routing)
flog.notice("Finished calculating new data")

# Load collection extra docs data
Expand Down
3 changes: 2 additions & 1 deletion src/antsibull_docs/cli/doc_commands/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ def generate_plugin_docs(
t.cast(
MutableMapping[str, MutableMapping[str, t.Any]],
{plugin_type: {plugin_name: plugin_info}},
)
),
{},
)

# Setup the jinja environment
Expand Down
12 changes: 1 addition & 11 deletions src/antsibull_docs/data/docsite/ansible-docsite/plugin.rst.j2
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,6 @@
.. _@{ doc['name'] }@_@{ plugin_type }@:
{% endif -%}

.. Anchors: aliases

{# TODO: This assumes that aliases will be short names.
If they're FQCN, then we need to change this
#}

{% for alias in doc['aliases'] -%}
.. _ansible_collections.@{collection}@.@{ alias }@_@{ plugin_type }@:
{% endfor %}

.. Title

{% if doc['short_description'] -%}
Expand Down Expand Up @@ -172,7 +162,7 @@ Synopsis
.. Aliases

{% if doc['aliases'] -%}
Aliases: @{ ','.join(aliases) }@
Aliases: @{ ', '.join(doc['aliases'] | sort) }@
{% endif %}

.. Requirements
Expand Down
6 changes: 0 additions & 6 deletions src/antsibull_docs/data/docsite/ansible-docsite/role.rst.j2
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,6 @@

.. _ansible_collections.@{plugin_name}@_@{plugin_type}@:

.. Anchors: aliases

{# TODO: This assumes that aliases will be short names.
If they're FQCN, then we need to change this
#}

.. Title

{% if entry_points.main and entry_points.main.short_description -%}
Expand Down
116 changes: 84 additions & 32 deletions src/antsibull_docs/docs_parsing/routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import os
import typing as t
from collections import defaultdict
from collections.abc import Mapping, MutableMapping
from collections.abc import Mapping, MutableMapping, Sequence

import asyncio_pool # type: ignore[import]
from antsibull_core import app_context, yaml
Expand Down Expand Up @@ -260,6 +260,29 @@ def _add_symlink_redirects(
plugin_type_routing[redirect_name]["redirect_is_symlink"] = True


def _add_core_symlink_redirects(
collection_metadata: AnsibleCollectionMetadata,
plugin_routing_out: dict[str, dict[str, dict[str, t.Any]]],
) -> None:
for plugin_type in DOCUMENTABLE_PLUGINS:
directory_name = (
"modules"
if plugin_type == "module"
else os.path.join("plugins", plugin_type)
)
directory_path = os.path.join(collection_metadata.path, directory_name)
plugin_type_routing = plugin_routing_out[plugin_type]

symlink_redirects = find_symlink_redirects(
"ansible.builtin", plugin_type, directory_path
)
for redirect_name, redirect_dst in symlink_redirects.items():
if redirect_name not in plugin_type_routing:
plugin_type_routing[redirect_name] = {}
if "redirect" not in plugin_type_routing[redirect_name]:
plugin_type_routing[redirect_name]["redirect"] = redirect_dst


async def load_collection_routing(
collection_name: str, collection_metadata: AnsibleCollectionMetadata
) -> dict[str, dict[str, dict[str, t.Any]]]:
Expand All @@ -280,6 +303,7 @@ async def load_collection_routing(
if collection_name == "ansible.builtin":
# ansible-core has a special directory structure we currently do not want
# (or need) to handle
_add_core_symlink_redirects(collection_metadata, plugin_routing_out)
return plugin_routing_out

_add_symlink_redirects(collection_name, collection_metadata, plugin_routing_out)
Expand Down Expand Up @@ -316,6 +340,62 @@ async def load_all_collection_routing(
return global_plugin_routing


def _add_meta_redirect_for_aliases(
plugin_map: MutableMapping[str, t.Any],
plugin_routing: MutableMapping[str, MutableMapping[str, t.Any]],
) -> None:
for plugin_name, plugin_record in plugin_map.items():
if isinstance(plugin_record.get("doc"), Mapping) and isinstance(
plugin_record["doc"].get("aliases"), Sequence
):
for alias in plugin_record["doc"]["aliases"]:
alias_fqcn = f"ansible.builtin.{alias}"
if alias_fqcn not in plugin_routing:
plugin_routing[alias_fqcn] = {}
if "redirect" not in plugin_routing[alias_fqcn]:
plugin_routing[alias_fqcn]["redirect"] = plugin_name


def _remove_redirect_duplicates(
plugin_map: MutableMapping[str, t.Any],
plugin_routing: MutableMapping[str, MutableMapping[str, t.Any]],
) -> None:
for plugin_name, plugin_record in list(plugin_map.items()):
if plugin_name in plugin_routing and "redirect" in plugin_routing[plugin_name]:
destination = plugin_routing[plugin_name]["redirect"]
if destination in plugin_map and destination != plugin_name:
# Heuristic: if we have a redirect, and docs for both this plugin and the
# redirected one are generated from the same plugin filename, then we can
# remove this plugin's docs and generate a redirect stub instead.
a = plugin_record.get("doc")
b = plugin_map[destination].get("doc")
if a and b and compare_all_but(a, b, ["filename"]):
del plugin_map[plugin_name]


def _remove_other_duplicates(
plugin_type: str,
plugin_map: MutableMapping[str, t.Any],
plugin_routing: MutableMapping[str, MutableMapping[str, t.Any]],
) -> None:
# In some cases, both the plugin and its aliases will be listed.
# Basically look for plugins whose name is wrong, a plugin with that
# name exists, and whose docs are identical.
for plugin_name, plugin_record in list(plugin_map.items()):
doc = plugin_record.get("doc") or {}
name = doc.get("module" if plugin_type == "module" else "name")
collection_name = ".".join(plugin_name.split(".")[:2])
full_name = f"{collection_name}.{name}"
if full_name and full_name != plugin_name and full_name in plugin_map:
a = plugin_record.get("doc")
b = plugin_map[full_name].get("doc")
if a and b and compare_all_but(a, b, ["name", "filename"]):
del plugin_map[plugin_name]
if plugin_name not in plugin_routing:
plugin_routing[plugin_name] = {}
plugin_routing[plugin_name]["redirect"] = full_name


def remove_redirect_duplicates(
plugin_info: MutableMapping[str, MutableMapping[str, t.Any]],
collection_routing: MutableCollectionRoutingT,
Expand All @@ -326,37 +406,9 @@ def remove_redirect_duplicates(
"""
for plugin_type, plugin_map in plugin_info.items():
plugin_routing = collection_routing[plugin_type]
for plugin_name, plugin_record in list(plugin_map.items()):
# Check redirect
if (
plugin_name in plugin_routing
and "redirect" in plugin_routing[plugin_name]
):
destination = plugin_routing[plugin_name]["redirect"]
if destination in plugin_map and destination != plugin_name:
# Heuristic: if we have a redirect, and docs for both this plugin and the
# redirected one are generated from the same plugin filename, then we can
# remove this plugin's docs and generate a redirect stub instead.
a = plugin_record.get("doc")
b = plugin_map[destination].get("doc")
if a and b and compare_all_but(a, b, ["filename"]):
del plugin_map[plugin_name]

# For filters and tests, both the plugin and its aliases will be listed. Basically
# look for plugins whose name is wrong, a plugin with that name exists, and whose
# docs are identical
for plugin_name, plugin_record in list(plugin_map.items()):
name = (plugin_record.get("doc") or {}).get("name")
collection_name = ".".join(plugin_name.split(".")[:2])
full_name = f"{collection_name}.{name}"
if full_name and full_name != plugin_name and full_name in plugin_map:
a = plugin_record.get("doc")
b = plugin_map[full_name].get("doc")
if a and b and compare_all_but(a, b, ["name", "filename"]):
del plugin_map[plugin_name]
if plugin_name not in plugin_routing:
plugin_routing[plugin_name] = {}
plugin_routing[plugin_name]["redirect"] = full_name
_add_meta_redirect_for_aliases(plugin_map, plugin_routing)
_remove_redirect_duplicates(plugin_map, plugin_routing)
_remove_other_duplicates(plugin_type, plugin_map, plugin_routing)


def find_stubs(
Expand Down
2 changes: 1 addition & 1 deletion src/antsibull_docs/lint_plugin_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,7 @@ def _lint_collection_plugin_docs(
new_plugin_info, nonfatal_errors = asyncio.run(
normalize_all_plugin_info(plugin_info)
)
augment_docs(new_plugin_info)
augment_docs(new_plugin_info, collection_routing)
# Load link data
link_data = asyncio.run(
load_collections_links(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@

.. _ansible_collections.ns.col2.bar_role:

.. Anchors: aliases


.. Title

ns.col2.bar role -- Bar role
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@

.. Anchors: short name for ansible.builtin

.. Anchors: aliases



.. Title

ns.col2.foo2 module -- Foo two
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@

.. Anchors: short name for ansible.builtin

.. Anchors: aliases



.. Title

ns.col2.foo3 module -- Foo III
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@

.. Anchors: short name for ansible.builtin

.. Anchors: aliases



.. Title

ns.col2.foo4 module -- Markup reference linting test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@

.. Anchors: short name for ansible.builtin

.. Anchors: aliases



.. Title

ns2.col.bar filter -- The bar filter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,6 @@

.. Anchors: short name for ansible.builtin

.. Anchors: aliases


.. _ansible_collections.ns2.col.is_bar_test:

.. Title

ns2.col.bar test -- Is something a bar
Expand Down Expand Up @@ -70,7 +65,7 @@ Synopsis

.. Aliases

Aliases:
Aliases: is_bar

.. Requirements

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@

.. Anchors: short name for ansible.builtin

.. Anchors: aliases



.. Title

ns2.col.foo2 module -- Another foo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@

.. Anchors: short name for ansible.builtin

.. Anchors: aliases



.. Title

ns2.col.foo become -- Use foo \ :ansopt:`ns2.col.foo#become:bar`\
Expand Down
Loading