1"""
2Manifest Revisions Table
3========================
4
5This extension allows to render a table containing the revisions of the projects
6present in a manifest file.
7
8Usage
9*****
10
11This extension introduces a new directive: ``manifest-projects-table``. It can
12be used in the code as::
13
14    .. manifest-projects-table::
15        :filter: active
16
17where the ``:filter:`` option can have the following values: active, inactive, all.
18
19Options
20*******
21
22- ``manifest_projects_table_manifest``: Path to the manifest file.
23
24Copyright (c) Nordic Semiconductor ASA 2022
25Copyright (c) Intel Corp 2023
26SPDX-License-Identifier: Apache-2.0
27"""
28
29import re
30from typing import Any
31
32from docutils import nodes
33from docutils.parsers.rst import directives
34from sphinx.application import Sphinx
35from sphinx.util.docutils import SphinxDirective
36from west.manifest import Manifest
37
38__version__ = "0.1.0"
39
40
41class ManifestProjectsTable(SphinxDirective):
42    """Manifest revisions table."""
43
44    option_spec = {
45        "filter": directives.unchanged,
46    }
47
48    @staticmethod
49    def rev_url(base_url: str, rev: str) -> str:
50        """Return URL for a revision.
51
52        Notes:
53            Revision format is assumed to be a git hash or a tag. URL is
54            formatted assuming a GitHub base URL.
55
56        Args:
57            base_url: Base URL of the repository.
58            rev: Revision.
59
60        Returns:
61            URL for the revision.
62        """
63
64        if re.match(r"^[0-9a-f]{40}$", rev):
65            return f"{base_url}/commit/{rev}"
66
67        return f"{base_url}/releases/tag/{rev}"
68
69    def run(self) -> list[nodes.Element]:
70        active_filter = self.options.get("filter", None)
71
72        manifest = Manifest.from_file(self.env.config.manifest_projects_table_manifest)
73        projects = []
74        for project in manifest.projects:
75            if project.name == "manifest":
76                continue
77            if (
78                active_filter == "active"
79                and manifest.is_active(project)
80                or active_filter == "inactive"
81                and not manifest.is_active(project)
82                or active_filter == "all"
83                or active_filter is None
84            ):
85                projects.append(project)
86
87        # build table
88        table = nodes.table()
89
90        tgroup = nodes.tgroup(cols=2)
91        tgroup += nodes.colspec(colwidth=1)
92        tgroup += nodes.colspec(colwidth=1)
93        table += tgroup
94
95        thead = nodes.thead()
96        tgroup += thead
97
98        row = nodes.row()
99        thead.append(row)
100
101        entry = nodes.entry()
102        entry += nodes.paragraph(text="Project")
103        row += entry
104        entry = nodes.entry()
105        entry += nodes.paragraph(text="Revision")
106        row += entry
107
108        rows = []
109        for project in projects:
110            row = nodes.row()
111            rows.append(row)
112
113            entry = nodes.entry()
114            entry += nodes.paragraph(text=project.name)
115            row += entry
116            entry = nodes.entry()
117            par = nodes.paragraph()
118            par += nodes.reference(
119                project.revision,
120                project.revision,
121                internal=False,
122                refuri=ManifestProjectsTable.rev_url(project.url, project.revision),
123            )
124            entry += par
125            row += entry
126
127        tbody = nodes.tbody()
128        tbody.extend(rows)
129        tgroup += tbody
130
131        return [table]
132
133
134def setup(app: Sphinx) -> dict[str, Any]:
135    app.add_config_value("manifest_projects_table_manifest", None, "env")
136
137    directives.register_directive("manifest-projects-table", ManifestProjectsTable)
138
139    return {
140        "version": __version__,
141        "parallel_read_safe": True,
142        "parallel_write_safe": True,
143    }
144