summaryrefslogtreecommitdiffstats
path: root/sphinx/environment/collectors/metadata.py
blob: 5f737a94428384690035c43b0ec1c2d7e32ef119 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
"""The metadata collector components for sphinx.environment."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any, cast

from docutils import nodes

from sphinx.environment.collectors import EnvironmentCollector

if TYPE_CHECKING:
    from sphinx.application import Sphinx
    from sphinx.environment import BuildEnvironment


class MetadataCollector(EnvironmentCollector):
    """metadata collector for sphinx.environment."""

    def clear_doc(self, app: Sphinx, env: BuildEnvironment, docname: str) -> None:
        env.metadata.pop(docname, None)

    def merge_other(self, app: Sphinx, env: BuildEnvironment,
                    docnames: set[str], other: BuildEnvironment) -> None:
        for docname in docnames:
            env.metadata[docname] = other.metadata[docname]

    def process_doc(self, app: Sphinx, doctree: nodes.document) -> None:
        """Process the docinfo part of the doctree as metadata.

        Keep processing minimal -- just return what docutils says.
        """
        index = doctree.first_child_not_matching_class(nodes.PreBibliographic)
        if index is None:
            return
        elif isinstance(doctree[index], nodes.docinfo):
            md = app.env.metadata[app.env.docname]
            for node in doctree[index]:  # type: ignore[attr-defined]
                # nodes are multiply inherited...
                if isinstance(node, nodes.authors):
                    authors = cast(list[nodes.author], node)
                    md['authors'] = [author.astext() for author in authors]
                elif isinstance(node, nodes.field):
                    assert len(node) == 2
                    field_name = cast(nodes.field_name, node[0])
                    field_body = cast(nodes.field_body, node[1])
                    md[field_name.astext()] = field_body.astext()
                elif isinstance(node, nodes.TextElement):
                    # other children must be TextElement
                    # see: https://docutils.sourceforge.io/docs/ref/doctree.html#bibliographic-elements  # noqa: E501
                    md[node.__class__.__name__] = node.astext()

            for name, value in md.items():
                if name in ('tocdepth',):
                    try:
                        value = int(value)
                    except ValueError:
                        value = 0
                    md[name] = value

            doctree.pop(index)


def setup(app: Sphinx) -> dict[str, Any]:
    app.add_env_collector(MetadataCollector)

    return {
        'version': 'builtin',
        'parallel_read_safe': True,
        'parallel_write_safe': True,
    }