xref: /openbmc/qemu/docs/sphinx/qapi_domain.py (revision 36ceafad9e4e61138b08dc371c42248dc5289a57)
1"""
2QAPI domain extension.
3"""
4
5from __future__ import annotations
6
7from typing import (
8    TYPE_CHECKING,
9    AbstractSet,
10    Any,
11    Dict,
12    NamedTuple,
13    Tuple,
14)
15
16from sphinx.domains import Domain, ObjType
17from sphinx.locale import __
18from sphinx.util import logging
19
20
21if TYPE_CHECKING:
22    from sphinx.application import Sphinx
23
24logger = logging.getLogger(__name__)
25
26
27class ObjectEntry(NamedTuple):
28    docname: str
29    node_id: str
30    objtype: str
31    aliased: bool
32
33
34class QAPIDomain(Domain):
35    """QAPI language domain."""
36
37    name = "qapi"
38    label = "QAPI"
39
40    # This table associates cross-reference object types (key) with an
41    # ObjType instance, which defines the valid cross-reference roles
42    # for each object type.
43
44    # Actual table entries for module, command, event, etc will come in
45    # forthcoming commits.
46    object_types: Dict[str, ObjType] = {}
47
48    directives = {}
49    roles = {}
50
51    # Moved into the data property at runtime;
52    # this is the internal index of reference-able objects.
53    initial_data: Dict[str, Dict[str, Tuple[Any]]] = {
54        "objects": {},  # fullname -> ObjectEntry
55    }
56
57    indices = []
58
59    @property
60    def objects(self) -> Dict[str, ObjectEntry]:
61        ret = self.data.setdefault("objects", {})
62        return ret  # type: ignore[no-any-return]
63
64    def note_object(
65        self,
66        name: str,
67        objtype: str,
68        node_id: str,
69        aliased: bool = False,
70        location: Any = None,
71    ) -> None:
72        """Note a QAPI object for cross reference."""
73        if name in self.objects:
74            other = self.objects[name]
75            if other.aliased and aliased is False:
76                # The original definition found. Override it!
77                pass
78            elif other.aliased is False and aliased:
79                # The original definition is already registered.
80                return
81            else:
82                # duplicated
83                logger.warning(
84                    __(
85                        "duplicate object description of %s, "
86                        "other instance in %s, use :no-index: for one of them"
87                    ),
88                    name,
89                    other.docname,
90                    location=location,
91                )
92        self.objects[name] = ObjectEntry(
93            self.env.docname, node_id, objtype, aliased
94        )
95
96    def clear_doc(self, docname: str) -> None:
97        for fullname, obj in list(self.objects.items()):
98            if obj.docname == docname:
99                del self.objects[fullname]
100
101    def merge_domaindata(
102        self, docnames: AbstractSet[str], otherdata: Dict[str, Any]
103    ) -> None:
104        for fullname, obj in otherdata["objects"].items():
105            if obj.docname in docnames:
106                # Sphinx's own python domain doesn't appear to bother to
107                # check for collisions. Assert they don't happen and
108                # we'll fix it if/when the case arises.
109                assert fullname not in self.objects, (
110                    "bug - collision on merge?"
111                    f" {fullname=} {obj=} {self.objects[fullname]=}"
112                )
113                self.objects[fullname] = obj
114
115    def resolve_any_xref(self, *args: Any, **kwargs: Any) -> Any:
116        # pylint: disable=unused-argument
117        return []
118
119
120def setup(app: Sphinx) -> Dict[str, Any]:
121    app.setup_extension("sphinx.directives")
122    app.add_domain(QAPIDomain)
123
124    return {
125        "version": "1.0",
126        "env_version": 1,
127        "parallel_read_safe": True,
128        "parallel_write_safe": True,
129    }
130