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