xref: /aosp_15_r20/external/pigweed/pw_docgen/py/pw_docgen/sphinx/modules_index.py (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1# Copyright 2024 The Pigweed Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4# use this file except in compliance with the License. You may obtain a copy of
5# the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations under
13# the License.
14"""Generates the modules index table on //docs/modules.rst."""
15
16import json
17import os
18import sys
19
20from sphinx.application import Sphinx
21
22
23try:  # Bazel location for the data
24    from python.runfiles import runfiles  # type: ignore
25
26    r = runfiles.Create()
27    modules_file = r.Rlocation('pigweed/PIGWEED_MODULES')
28    r = runfiles.Create()
29    metadata_file = r.Rlocation('pigweed/docs/module_metadata.json')
30except ImportError:  # GN location for the data
31    modules_file = f'{os.environ["PW_ROOT"]}/PIGWEED_MODULES'
32    metadata_file = f'{os.environ["PW_ROOT"]}/docs/module_metadata.json'
33with open(modules_file, 'r') as f:
34    # The complete, authoritative list of modules.
35    complete_pigweed_modules_list = f.read().splitlines()
36with open(metadata_file, 'r') as f:
37    # Module metadata such as supported languages and status.
38    metadata = json.load(f)
39
40
41def build_status_badge(status):
42    """Styles the module status as a clickable badge."""
43    # This default value should never get used but it's a valid
44    # neutral styling in case something goes wrong and it leaks through.
45    badge_type = 'info'
46    if status == 'stable':
47        badge_type = 'primary'
48    elif status == 'unstable':
49        badge_type = 'secondary'
50    elif status == 'experimental':
51        badge_type = 'warning'
52    elif status == 'deprecated':
53        badge_type = 'danger'
54    else:
55        msg = f'[modules_index.py] error: invalid module status ("{status}")'
56        sys.exit(msg)
57    # Use bdg-ref-* to make the badge link to the glossary definition for
58    # "stable", "experimental", etc.
59    # https://sphinx-design.readthedocs.io/en/latest/badges_buttons.html
60    return f':bdg-{badge_type}:`{status}`'
61
62
63def build_row(module_name: str):
64    """Builds a row of data for the table. Each module gets a row."""
65    ref = f':ref:`module-{module_name}`'
66    if module_name not in metadata:
67        return f'   "{ref}", "", "", ""\n'
68    tagline = metadata[module_name]['tagline']
69    status = build_status_badge(metadata[module_name]['status'])
70    if 'languages' in metadata[module_name]:
71        languages = ', '.join(metadata[module_name]['languages'])
72    else:
73        languages = ''
74    return f'   "{ref}", "{status}", "{tagline}", "{languages}"\n'
75
76
77def generate_modules_index(_, docname: str, source: list[str]) -> None:
78    """Inserts the metadata table into //docs/modules.rst."""
79    if docname != 'modules':  # Only run this logic on //docs/modules.rst.
80        return
81    skip = ['docker']  # Items in //PIGWEED_MODULES that should be skipped.
82    # Transform //docs/module_metadata.json into a csv-table reST directive.
83    # https://docutils.sourceforge.io/docs/ref/rst/directives.html#csv-table-1
84    content = '\n\n.. csv-table::\n'
85    content += '   :header: "Name", "Status", "Description", "Languages"\n\n'
86    # Loop through the complete, authoritative list (as opposed to the metadata)
87    # to guarantee that every module is listed on the modules index page.
88    for module in complete_pigweed_modules_list:
89        if module in skip:
90            continue
91        content += build_row(module)
92    # Modify the reST of //docs/modules.rst in-place. The auto-generated table
93    # is just appended to the reST source text.
94    source[0] += content
95
96
97def setup(app: Sphinx) -> dict[str, bool]:
98    """Hooks this extension into Sphinx."""
99    app.connect('source-read', generate_modules_index)
100    return {
101        'parallel_read_safe': True,
102        'parallel_write_safe': True,
103    }
104