Source code for opengl_registry.reader

import logging
from io import StringIO
from opengl_registry.extensions import Extension
from typing import Dict, List
from xml.etree import ElementTree
import requests

# NOTE: Consider moving this to __init__ when finalized
from opengl_registry.registry import Registry
from opengl_registry.gltype import GlType
from opengl_registry.enums import Enum
from opengl_registry.commands import Command, CommandParam
from opengl_registry.features import Feature, FeatureDetails

# from opengl_registry.extensions import Extension

logger = logging.getLogger(__name__)


class RegistryReader:
    """Reads ``gl.xml`` file into a ``Registry`` structure
    that can easily be inspected.

    The reader instantiates data objects for every tag in the xml
    file. The created ``Registry`` is then responsible for making
    sense of the information.

    Example::

        # From a local file
        reader = RegistryReader.from_file('gl.xml')
        registry = reader.read()

        # From url. If no url parameter is passed in the last known url for this file is used.
        # Currently it resides on github in the KhronosGroup organization
        reader = RegistryReader.from_url()
        registry = reader.read()
    """

    #: The default URL for the ``gl.xml``` file
    DEFAULT_URL = "https://raw.githubusercontent.com/KhronosGroup/OpenGL-Registry/master/xml/gl.xml"

    #: The registry class. Can be replaced with a custom class
    registry_cls = Registry
    #: The GlType class. Can be replaced with a custom class
    type_cls = GlType
    #: The Enum class. Can be replaced with a custom class
    enum_cls = Enum

[docs] def __init__(self, tree: ElementTree): """Initialize the reader. Currently we use `xml.etree.ElementTree` for parsing the registry information. Args: tree (ElementTree): The `ElementTree` instance """ self._tree = tree
[docs] @classmethod def from_file(cls, path: str) -> "RegistryReader": """Create a RegistryReader with a local gl.xml file""" logger.info("Reading registry file: '%s'", path) tree = ElementTree.parse(path) return cls(tree)
[docs] @classmethod def from_url(cls, url: str = None) -> "RegistryReader": """Create a RegistryReader with a url to the gl.xml file""" url = url or cls.DEFAULT_URL logger.info("Reading registry file from url: '%s'", url) response = requests.get(url) if response.status_code != requests.codes.ok: response.raise_for_error() tree = ElementTree.parse(StringIO(response.text)) return cls(tree)
[docs] def read(self) -> Registry: """Reads the registry structure. Returns: Registry: The ``Registry`` instance """ return self.registry_cls( types=self.read_types(), enums=self.read_enums(), commands=self.read_commands(), features=self.read_features(), extensions=self.read_extensions(), )
[docs] def read_types(self) -> List[GlType]: """Read all GL type definitions Returns: List[GlType]: list of types """ types = [] for type_elem in self._tree.getroot().iter("type"): name = None try: name = next(type_elem.iter("name")).text except StopIteration: name = type_elem.get("name") types.append( self.type_cls( name=name, text="".join(type_elem.itertext()), comment=type_elem.get("comment"), requires=type_elem.get("requires"), ) ) return types
## Note: Khronos does not use the enum group information. Likely outdated. # def read_groups(self) -> List[Group]: # """Reads all group nodes. # Returns: # List[Group]: list of groups # """ # groups = [] # for groups_elem in self._tree.getroot().iter("group"): # groups.append( # self.group_cls( # groups_elem.attrib["name"], # entries={e.attrib["name"] for e in groups_elem.iter("enum")}, # ) # ) # return groups
[docs] def read_enums(self) -> Dict[str, Enum]: """Reads all enums groups. Returns: List[Enums]: list of enums groups """ enums = {} for enums_elem in self._tree.getroot().iter("enums"): enums.update({ el.get("name"): Enum( name=el.get("name"), value=el.get("value"), comment=el.get("comment"), alias=el.get("alias"), ) for el in enums_elem.iter("enum") }) return enums
[docs] def read_commands(self) -> Dict[str, Command]: """Reads all commands. Returns: List[Command]: list of commands """ entries = {} commands_elem = next(self._tree.getroot().iter("commands")) for comm_elem in commands_elem.iter("command"): command = Command() for child in comm_elem: # A command should only have one proto tag if child.tag == "proto": command.proto = "".join(child.itertext()) try: ptype = next(child.iter("ptype")) command.ptype = ptype.text except StopIteration: pass command.name = next(child.iter("name")).text elif child.tag == "param": name = next(child.iter("name")).text value = "".join(child.itertext()) group = child.get("group") length = child.get("len") ptype = None try: ptype = next(child.iter("ptype")).text except StopIteration: pass command.params.append( CommandParam( name=name, value=value, ptype=ptype, group=group, length=length, ) ) elif child.tag == "glx": command.glx = { "type": child.get("type"), "opcode": child.get("opcode"), "name": child.get("name"), "comment": child.get("comment"), } entries[command.name] = command return entries
[docs] def read_features(self) -> List[Feature]: """Reads all features. Returns: List[Feature]: list of features """ features = [] for feature_elem in self._tree.iter("feature"): feature = Feature( api=feature_elem.get("api"), name=feature_elem.get("name"), number=feature_elem.get("number"), ) features.append(feature) for details_elem in ( *feature_elem.iter("require"), *feature_elem.iter("remove"), ): mode = details_elem.tag details = FeatureDetails( mode, profile=details_elem.get("profile"), comment=details_elem.get("comment"), enums=[t.get("name") for t in details_elem.iter("enum")], commands=[t.get("name") for t in details_elem.iter("command")], types=[t.get("name") for t in details_elem.iter("type")], ) if mode == FeatureDetails.REQUIRE: feature.require.append(details) elif mode == FeatureDetails.REMOVE: feature.remove.append(details) else: logger.warning("Unsupported mode: '%s'", mode) return features
[docs] def read_extensions(self) -> Dict[str, Extension]: """Reads all extensions. Returns: List[Extension]: list of extensions """ extensions = {} # NOTE: Extensions done have <remove>. We can safely iter for ext_elem in self._tree.iter("extension"): name = ext_elem.get("name") extensions[name] = Extension( name=name, supported=ext_elem.get("supported"), enums=[e.get("name") for e in ext_elem.iter("enum")], commands=[e.get("name") for e in ext_elem.iter("command")], ) return extensions