""" QAPI domain extension. """ from __future__ import annotations from typing import ( TYPE_CHECKING, AbstractSet, Any, Dict, Iterable, List, NamedTuple, Optional, Tuple, ) from sphinx.domains import ( Domain, Index, IndexEntry, ObjType, ) from sphinx.locale import _, __ from sphinx.util import logging if TYPE_CHECKING: from sphinx.application import Sphinx logger = logging.getLogger(__name__) class ObjectEntry(NamedTuple): docname: str node_id: str objtype: str aliased: bool class QAPIIndex(Index): """ Index subclass to provide the QAPI definition index. """ # pylint: disable=too-few-public-methods name = "index" localname = _("QAPI Index") shortname = _("QAPI Index") def generate( self, docnames: Optional[Iterable[str]] = None, ) -> Tuple[List[Tuple[str, List[IndexEntry]]], bool]: assert isinstance(self.domain, QAPIDomain) content: Dict[str, List[IndexEntry]] = {} collapse = False # list of all object (name, ObjectEntry) pairs, sorted by name # (ignoring the module) objects = sorted( self.domain.objects.items(), key=lambda x: x[0].split(".")[-1].lower(), ) for objname, obj in objects: if docnames and obj.docname not in docnames: continue # Strip the module name out: objname = objname.split(".")[-1] # Add an alphabetical entry: entries = content.setdefault(objname[0].upper(), []) entries.append( IndexEntry( objname, 0, obj.docname, obj.node_id, obj.objtype, "", "" ) ) # Add a categorical entry: category = obj.objtype.title() + "s" entries = content.setdefault(category, []) entries.append( IndexEntry(objname, 0, obj.docname, obj.node_id, "", "", "") ) # alphabetically sort categories; type names first, ABC entries last. sorted_content = sorted( content.items(), key=lambda x: (len(x[0]) == 1, x[0]), ) return sorted_content, collapse class QAPIDomain(Domain): """QAPI language domain.""" name = "qapi" label = "QAPI" # This table associates cross-reference object types (key) with an # ObjType instance, which defines the valid cross-reference roles # for each object type. # Actual table entries for module, command, event, etc will come in # forthcoming commits. object_types: Dict[str, ObjType] = {} directives = {} roles = {} # Moved into the data property at runtime; # this is the internal index of reference-able objects. initial_data: Dict[str, Dict[str, Tuple[Any]]] = { "objects": {}, # fullname -> ObjectEntry } # Index pages to generate; each entry is an Index class. indices = [ QAPIIndex, ] @property def objects(self) -> Dict[str, ObjectEntry]: ret = self.data.setdefault("objects", {}) return ret # type: ignore[no-any-return] def note_object( self, name: str, objtype: str, node_id: str, aliased: bool = False, location: Any = None, ) -> None: """Note a QAPI object for cross reference.""" if name in self.objects: other = self.objects[name] if other.aliased and aliased is False: # The original definition found. Override it! pass elif other.aliased is False and aliased: # The original definition is already registered. return else: # duplicated logger.warning( __( "duplicate object description of %s, " "other instance in %s, use :no-index: for one of them" ), name, other.docname, location=location, ) self.objects[name] = ObjectEntry( self.env.docname, node_id, objtype, aliased ) def clear_doc(self, docname: str) -> None: for fullname, obj in list(self.objects.items()): if obj.docname == docname: del self.objects[fullname] def merge_domaindata( self, docnames: AbstractSet[str], otherdata: Dict[str, Any] ) -> None: for fullname, obj in otherdata["objects"].items(): if obj.docname in docnames: # Sphinx's own python domain doesn't appear to bother to # check for collisions. Assert they don't happen and # we'll fix it if/when the case arises. assert fullname not in self.objects, ( "bug - collision on merge?" f" {fullname=} {obj=} {self.objects[fullname]=}" ) self.objects[fullname] = obj def resolve_any_xref(self, *args: Any, **kwargs: Any) -> Any: # pylint: disable=unused-argument return [] def setup(app: Sphinx) -> Dict[str, Any]: app.setup_extension("sphinx.directives") app.add_domain(QAPIDomain) return { "version": "1.0", "env_version": 1, "parallel_read_safe": True, "parallel_write_safe": True, }