| # Copyright 2020 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import dataclasses |
| import enum |
| import os.path |
| from typing import Any, Dict, Iterable, List, Mapping, Optional, Tuple |
| import xml.etree.ElementTree |
| |
| # Short aliases for typing |
| Element = xml.etree.ElementTree.Element |
| |
| |
| @dataclasses.dataclass(frozen=True) |
| class Description: |
| """Holds the data from a Wayland protocol <description> subtree.""" |
| |
| # The value of the name summary of the <description>. |
| summary: str |
| |
| # The text content of the <description>. |
| description: str |
| |
| @staticmethod |
| def parse_xml(e: Element) -> Optional['Description']: |
| return Description(summary=e.get('summary'), |
| description=e.text.strip() |
| if e.text else '') if e is not None else None |
| |
| |
| class MessageArgType(enum.Enum): |
| """The valid types of an <arg>.""" |
| INT = 'int' |
| UINT = 'uint' |
| FIXED = 'fixed' |
| STRING = 'string' |
| FD = 'fd' # File descriptor |
| ARRAY = 'array' # untyped Array |
| NEW_ID = 'new_id' # Special type for constructing |
| OBJECT = 'object' # An interface |
| |
| |
| @dataclasses.dataclass(frozen=True) |
| class MessageArg: |
| """Holds the data from a Wayland protocol <arg> subtree.""" |
| |
| # The containing Message, for context. |
| message: 'Message' = dataclasses.field(repr=False, hash=False) |
| |
| # The value of the name attribute of the <arg>. |
| name: str |
| |
| # The value of the type attribute of the <arg>. |
| type: MessageArgType |
| |
| # The value of the optional summary attribute of the <arg>. |
| summary: Optional[str] |
| |
| # The value of the optional interface attribute of the <arg>. |
| interface: Optional[str] |
| |
| # The value of the optional nullable attribute of the <arg>. |
| nullable: Optional[bool] |
| |
| # The value of the optional enum attribute of the <arg>. |
| enum: Optional[str] |
| |
| # The optional <description> child element of the <arg>. |
| description: Optional[Description] |
| |
| @staticmethod |
| def parse_xml(message: 'Message', e: Element) -> 'MessageArg': |
| return MessageArg(message=message, |
| name=e.get('name'), |
| type=e.get('type'), |
| summary=e.get('summary'), |
| interface=e.get('interface'), |
| nullable=e.get('allow-null') == 'true' |
| if e.get('allow-null') else None, |
| enum=e.get('enum'), |
| description=Description.parse_xml( |
| e.find('description'))) |
| |
| |
| class RequestType(enum.Enum): |
| """The valid types of a <request> message.""" |
| DESTRUCTOR = 'destructor' |
| |
| |
| @dataclasses.dataclass(frozen=True) |
| class Message: |
| """Holds the data from a Wayland protocol <request> OR <event> subtree.""" |
| |
| # The containing Interface, for context. |
| interface: 'Interface' = dataclasses.field(repr=False, hash=False) |
| |
| # If true, this message is an <event> in the containing interface. |
| # Otherwise it is a <request>. |
| is_event: bool |
| |
| # The value of the name attribute of the <request> or <event>. |
| name: str |
| |
| # The value of the optional type attribute of the <request> Always empty |
| # for <events>. |
| request_type: Optional[RequestType] |
| |
| # The value of the optional since attribute of the <request> or <event>. |
| since: Optional[int] |
| |
| # The optional <description> child element of the <request> or <event>. |
| description: Optional[Description] |
| |
| # The child <arg> elements of the <request> or <event> |
| args: Tuple[MessageArg, ...] = dataclasses.field(init=False) |
| |
| @staticmethod |
| def parse_xml(interface: 'Interface', is_event: bool, |
| e: Element) -> 'Message': |
| message = Message( |
| interface=interface, |
| is_event=is_event, |
| name=e.get('name'), |
| request_type=e.get('type'), |
| since=int(e.get('since')) if e.get('since') else None, |
| description=Description.parse_xml(e.find('description'))) |
| |
| # Note: This is needed to finish up since the instance is frozen. |
| object.__setattr__( |
| message, 'args', |
| tuple(MessageArg.parse_xml(message, c) for c in e.findall('arg'))) |
| |
| return message |
| |
| |
| @dataclasses.dataclass(frozen=True) |
| class EnumEntry: |
| """Holds the data from a Wayland protocol <entry> subtree.""" |
| |
| # The containing Enum, for context. |
| enum: 'Enum' = dataclasses.field(repr=False, hash=False) |
| |
| # The value of the name attribute of the <entry>. |
| name: str |
| |
| # The value of the value attribute of the <entry>. |
| value: int |
| |
| # The value of the optional summary attribute of the <entry>. |
| summary: Optional[str] |
| |
| # The value of the optional since attribute of the <entry>. |
| since: Optional[int] |
| |
| # The optional <description> child element of the <request> or <event>. |
| description: Optional[Description] |
| |
| @staticmethod |
| def parse_xml(enum: 'Enum', e: Element) -> 'EnumEntry': |
| return EnumEntry( |
| enum=enum, |
| name=e.get('name'), |
| value=int(e.get('value'), 0), |
| summary=e.get('summary'), |
| since=int(e.get('since'), 0) if e.get('since') else None, |
| description=Description.parse_xml(e.find('description'))) |
| |
| |
| @dataclasses.dataclass(frozen=True) |
| class Enum: |
| """Holds the data from a Wayland protocol <enum> subtree.""" |
| |
| # The containing Interface, for context. |
| interface: 'Interface' = dataclasses.field(repr=False, hash=False) |
| |
| # The value of the name attribute of the <enum>. |
| name: str |
| |
| # The value of the optional since attribute of the <enum>. |
| since: Optional[int] |
| |
| # The value of the optional bitfield attribute of the <enum>. |
| bitfield: Optional[bool] |
| |
| # The optional <description> child element of the <request> or <event>. |
| description: Optional[Description] |
| |
| # The child <entry> elements for the <enum>. |
| entries: Tuple[EnumEntry, ...] = dataclasses.field(init=False) |
| |
| @staticmethod |
| def parse_xml(interface: 'Interface', e: Element) -> 'Enum': |
| enum = Enum(interface=interface, |
| name=e.get('name'), |
| since=int(e.get('since'), 0) if e.get('since') else None, |
| bitfield=e.get('bitfield') == 'true' |
| if e.get('bitfield') else None, |
| description=Description.parse_xml(e.find('description'))) |
| |
| # Note: This is needed to finish up since the instance is frozen. |
| object.__setattr__( |
| enum, 'entries', |
| tuple(EnumEntry.parse_xml(enum, c) for c in e.findall('entry'))) |
| |
| return enum |
| |
| |
| @dataclasses.dataclass(frozen=True) |
| class Interface: |
| """Holds the data from a Wayland protocol <interface> subtree.""" |
| |
| # The containing Protocol, for context. |
| protocol: 'Protocol' = dataclasses.field(repr=False, hash=False) |
| |
| # The value of the name attribute of the <interface>. |
| name: str |
| |
| # The value of the version attribute of the <interface>. |
| version: int |
| |
| # The optional <description> child element of the <interface>. |
| description: Optional[Description] |
| |
| # The child <request> elements for the <interface>. |
| requests: Tuple[Message, ...] = dataclasses.field(init=False) |
| |
| # The child <event> elements for the <interface>. |
| events: Tuple[Message, ...] = dataclasses.field(init=False) |
| |
| # The child <enum> elements for the <interface>. |
| enums: Tuple[Enum, ...] = dataclasses.field(init=False) |
| |
| @staticmethod |
| def parse_xml(protocol: 'Protocol', e: Element) -> 'Interface': |
| interface = Interface(protocol=protocol, |
| name=e.get('name'), |
| version=int(e.get('version'), 0), |
| description=Description.parse_xml( |
| e.find('description'))) |
| |
| # Note: This is needed to finish up since the instance is frozen. |
| object.__setattr__( |
| interface, 'requests', |
| tuple( |
| Message.parse_xml(interface, False, c) |
| for c in e.findall('request'))) |
| object.__setattr__( |
| interface, 'events', |
| tuple( |
| Message.parse_xml(interface, True, c) |
| for c in e.findall('event'))) |
| object.__setattr__( |
| interface, 'enums', |
| tuple(Enum.parse_xml(interface, c) for c in e.findall('enum'))) |
| |
| return interface |
| |
| |
| @dataclasses.dataclass(frozen=True) |
| class Copyright: |
| """Holds the data from a Wayland protocol <copyright> subtree.""" |
| |
| # The text content of the <copyright>. |
| text: str |
| |
| @staticmethod |
| def parse_xml(e: Element) -> Optional['Copyright']: |
| return Copyright(text=e.text.strip()) if e is not None else None |
| |
| |
| @dataclasses.dataclass(frozen=True) |
| class Protocol: |
| """Holds the data from a Wayland protocol <protocol> subtree.""" |
| |
| # The universe of known protocols this is part of. |
| protocols: 'Protocols' = dataclasses.field(repr=False, hash=False) |
| |
| # The containing base filename (no path, no extension), for context. |
| filename: str |
| |
| # The value of the name attribute of the <protocol>. |
| name: str |
| |
| # The optional <copyright> child element of the <protocol>. |
| copyright: Optional[Copyright] |
| |
| # The optional <description> child element of the <protocol>. |
| description: Optional[Description] |
| |
| # The child <interface> elements for the <protocol>. |
| interfaces: Tuple[Interface, ...] = dataclasses.field(init=False) |
| |
| @staticmethod |
| def parse_xml(protocols: 'Protocols', filename: str, |
| e: Element) -> 'Protocol': |
| protocol = Protocol(protocols, |
| filename=filename, |
| name=e.get('name'), |
| copyright=Copyright.parse_xml(e.find('copyright')), |
| description=Description.parse_xml( |
| e.find('description'))) |
| |
| # Note: This is needed to finish up since the instance is frozen. |
| object.__setattr__( |
| protocol, 'interfaces', |
| tuple( |
| Interface.parse_xml(protocol, i) |
| for i in e.findall('interface'))) |
| |
| return protocol |
| |
| |
| @dataclasses.dataclass(frozen=True) |
| class Protocols: |
| """Holds the data from multiple Wayland protocol files.""" |
| |
| # The parsed protocol dataclasses |
| protocols: Tuple[Protocol, ...] = dataclasses.field(init=False) |
| |
| @staticmethod |
| def parse_xml_files(filenames: Iterable[str]) -> 'Protocols': |
| protocols = Protocols() |
| |
| # Note: This is needed to finish up since the instance is frozen. |
| object.__setattr__( |
| protocols, 'protocols', |
| tuple( |
| Protocol.parse_xml( |
| protocols, |
| os.path.splitext(os.path.basename(filename))[0], |
| xml.etree.ElementTree.parse(filename).getroot()) |
| for filename in filenames)) |
| |
| return protocols |