scanner: switch to using dataclasses

This drops one dependency that we're not fully using anyway. Except for
the per-attribute validators that can be done in __post_init() we're not
using attrs for anything that dataclasses cannot do.
This commit is contained in:
Peter Hutterer 2024-09-11 10:50:26 +10:00
parent a5826424f1
commit dbc06510a1
5 changed files with 90 additions and 90 deletions

View file

@ -40,9 +40,9 @@ variables:
# See the documentation here: #
# https://wayland.freedesktop.org/libinput/doc/latest/building_libinput.html #
###############################################################################
FEDORA_PACKAGES: 'git diffutils gcc gcc-c++ pkgconf-pkg-config systemd-devel libxkbcommon-devel libxml2 doxygen python3-attrs python3-pytest python3-dbusmock python3-jinja2 python3-pip python3-pyyaml golang libabigail '
FEDORA_PACKAGES: 'git diffutils gcc gcc-c++ pkgconf-pkg-config systemd-devel libxkbcommon-devel libxml2 doxygen python3-pytest python3-dbusmock python3-jinja2 python3-pip python3-pyyaml golang libabigail '
FEDORA_PIP_PACKAGES: 'meson ninja structlog strenum '
DEBIAN_PACKAGES: 'git gcc g++ pkg-config libsystemd-dev libxkbcommon-dev libxml2 doxygen python3-attr python3-pytest python3-dbusmock python3-jinja2 python3-pip python3-yaml '
DEBIAN_PACKAGES: 'git gcc g++ pkg-config libsystemd-dev libxkbcommon-dev libxml2 doxygen python3-pytest python3-dbusmock python3-jinja2 python3-pip python3-yaml '
DEBIAN_PIP_PACKAGES: 'meson ninja structlog strenum '
############################ end of package lists #############################
@ -50,8 +50,8 @@ variables:
# changing these will force rebuilding the associated image
# Note: these tags have no meaning and are not tied to a particular
# libinput version
FEDORA_TAG: '2024-07-24.3'
DEBIAN_TAG: '2024-07-24.3'
FEDORA_TAG: '2024-09-11.0'
DEBIAN_TAG: '2024-09-11.0'
FDO_UPSTREAM_REPO: libinput/libei
@ -285,6 +285,7 @@ abicheck@fedora:40:
meson compile -C _build
meson install -C _build
popd
- pip install attrs
script:
- git remote add upstream$CI_JOB_ID https://gitlab.freedesktop.org/$FDO_UPSTREAM_REPO
- git fetch --tags upstream$CI_JOB_ID

View file

@ -285,6 +285,7 @@ abicheck@{{distro.name}}:{{version}}:
meson compile -C _build
meson install -C _build
popd
- pip install attrs
script:
- git remote add upstream$CI_JOB_ID https://gitlab.freedesktop.org/$FDO_UPSTREAM_REPO
- git fetch --tags upstream$CI_JOB_ID

View file

@ -3,7 +3,7 @@
#
# We're happy to rebuild all containers when one changes.
.default_tag: &default_tag '2024-07-24.3'
.default_tag: &default_tag '2024-09-11.0'
last_abi_break: abe85e051e7029bfd2e7913ab980a9e0042b6d0d
minimum_meson_version: 0.57.0
@ -24,7 +24,6 @@ distributions:
- libxkbcommon-devel
- libxml2
- doxygen
- python3-attrs
- python3-pytest
- python3-dbusmock
- python3-jinja2
@ -51,7 +50,6 @@ distributions:
- libxkbcommon-dev
- libxml2
- doxygen
- python3-attr
- python3-pytest
- python3-dbusmock
- python3-jinja2

View file

@ -20,9 +20,9 @@ appear in the XML file.
from typing import Any, Dict, List, Optional, Tuple, Union
from pathlib import Path
from textwrap import dedent
from dataclasses import dataclass, field
import argparse
import attr
import jinja2
import jinja2.environment
import os
@ -55,48 +55,54 @@ def snake2camel(s: str) -> str:
return s.replace("_", " ").title().replace(" ", "")
@attr.s
@dataclass
class Description:
summary: str = attr.ib(default="")
text: str = attr.ib(default="")
summary: str = ""
text: str = ""
@attr.s
@dataclass
class Argument:
"""
Argument to a request or a reply
"""
name: str = attr.ib()
protocol_type: str = attr.ib()
summary: str = attr.ib()
enum: Optional["Enum"] = attr.ib()
interface: Optional["Interface"] = attr.ib()
interface_arg: Optional["Argument"] = attr.ib(default=None)
name: str
protocol_type: str
summary: str
enum: Optional["Enum"]
interface: Optional["Interface"]
interface_arg: Optional["Argument"] = None
"""
For an argument with "interface_arg", this field points to the argument that
contains the interface name.
"""
interface_arg_for: Optional["Argument"] = attr.ib(default=None)
interface_arg_for: Optional["Argument"] = None
"""
For an argument referenced by another argument through "interface_name", this field
points to the other argument that references this argument.
"""
version_arg: Optional["Argument"] = attr.ib(default=None)
version_arg: Optional["Argument"] = None
"""
For an argument with type "new_id", this field points to the argument that
contains the version for this new object.
"""
version_arg_for: Optional["Argument"] = attr.ib(default=None)
version_arg_for: Optional["Argument"] = None
"""
For an argument referenced by another argument of type "new_id", this field
points to the other argument that references this argument.
"""
allow_null: bool = attr.ib(default=False)
allow_null: bool = False
"""
For an argument of type string, specify if the argument may be NULL.
"""
def __post_init(self):
if self.protocol_type is None or self.protocol_type not in PROTOCOL_TYPES:
raise ValueError(f"Failed to parse protocol_type {self.protocol_type}")
if self.interface is not None and self.signature not in ["n", "o"]:
raise ValueError("Interface may only be set for object types")
@property
def signature(self) -> str:
"""
@ -104,11 +110,6 @@ class Argument:
"""
return PROTOCOL_TYPES[self.protocol_type]
@interface.validator # type: ignore
def _validate_interface(self, attribute, value):
if value is not None and self.signature not in ["n", "o"]:
raise ValueError("Interface may only be set for object types")
@property
def as_c_arg(self) -> str:
return f"{self.c_type} {self.name}"
@ -127,12 +128,6 @@ class Argument:
"new_id": "new_id_t",
}[self.protocol_type]
@protocol_type.validator # type: ignore
def _validate_protocol_type(self, attribute, value):
assert (
value is not None and value in PROTOCOL_TYPES
), f"Failed to parse protocol_type {value}"
@classmethod
def create(
cls,
@ -153,26 +148,25 @@ class Argument:
)
@attr.s
@dataclass
class Message:
"""
Parent class for a wire message (Request or Event).
"""
name: str = attr.ib()
since: int = attr.ib()
opcode: int = attr.ib()
interface: "Interface" = attr.ib()
description: Optional[Description] = attr.ib(default=None)
is_destructor: bool = attr.ib(default=False)
context_type: Optional[str] = attr.ib(default=None)
name: str
since: int
opcode: int
interface: "Interface"
description: Optional[Description] = None
is_destructor: bool = False
context_type: Optional[str] = None
arguments: List[Argument] = attr.ib(init=False, factory=list)
arguments: List[Argument] = field(init=False, default_factory=list)
@context_type.validator # type: ignore
def _context_type_validate(self, attr, value):
if value not in [None, "sender", "receiver"]:
raise ValueError(f"Invalid context type {value}")
def __post_init(self):
if self.context_type not in [None, "sender", "receiver"]:
raise ValueError(f"Invalid context type {self.context_type}")
def add_argument(self, arg: Argument) -> None:
if arg.name in [a.name for a in self.arguments]:
@ -198,7 +192,7 @@ class Message:
return None
@attr.s
@dataclass
class Request(Message):
@classmethod
def create(
@ -225,7 +219,7 @@ class Request(Message):
return f"{self.interface.name}_request_{self.name}"
@attr.s
@dataclass
class Event(Message):
@classmethod
def create(
@ -252,17 +246,17 @@ class Event(Message):
return f"{self.interface.name}_event_{self.name}"
@attr.s
@dataclass
class Entry:
"""
An enum entry
"""
name: str = attr.ib()
value: int = attr.ib()
enum: "Enum" = attr.ib()
summary: str = attr.ib()
since: int = attr.ib()
name: str
value: int
enum: "Enum"
summary: str
since: int
@classmethod
def create(
@ -278,15 +272,15 @@ class Entry:
return f"{self.enum.fqdn}_{self.name}"
@attr.s
@dataclass
class Enum:
name: str = attr.ib()
since: int = attr.ib()
interface: "Interface" = attr.ib()
is_bitfield: bool = attr.ib(default=False)
description: Optional[Description] = attr.ib(default=None)
name: str
since: int
interface: "Interface"
is_bitfield: bool = False
description: Optional[Description] = None
entries: List[Entry] = attr.ib(init=False, factory=list)
entries: List[Entry] = field(init=False, default_factory=list)
@classmethod
def create(
@ -329,16 +323,20 @@ class Enum:
return snake2camel(self.name)
@attr.s
@dataclass
class Interface:
protocol_name: str = attr.ib() # name as in the XML, e.g. ei_pointer
version: int = attr.ib()
requests: List[Request] = attr.ib(init=False, factory=list)
events: List[Event] = attr.ib(init=False, factory=list)
enums: List[Enum] = attr.ib(init=False, factory=list)
protocol_name: str # name as in the XML, e.g. ei_pointer
version: int
requests: List[Request] = field(init=False, default_factory=list)
events: List[Event] = field(init=False, default_factory=list)
enums: List[Enum] = field(init=False, default_factory=list)
mode: str = attr.ib(validator=attr.validators.in_(["ei", "eis", "brei"]))
description: Optional[Description] = attr.ib(default=None)
mode: str
description: Optional[Description] = None
def __post_init(self):
if self.mode not in ["ei", "eis", "brei"]:
raise ValueError(f"Invalid mode {self.mode}")
@property
def name(self) -> str:
@ -444,11 +442,11 @@ class Interface:
return cls(protocol_name=protocol_name, version=version, mode=mode)
@attr.s
@dataclass
class XmlError(Exception):
line: int = attr.ib()
column: int = attr.ib()
message: str = attr.ib()
line: int
column: int
message: str
def __str__(self) -> str:
return f"line {self.line}:{self.column}: {self.message}"
@ -458,32 +456,34 @@ class XmlError(Exception):
return cls(line=location[0], column=location[1], message=message)
@attr.s
@dataclass
class Copyright:
text: str = attr.ib(default="")
is_complete: bool = attr.ib(init=False, default=False)
text: str = ""
is_complete: bool = field(init=False, default=False)
@attr.s
@dataclass
class Protocol:
copyright: Optional[str] = attr.ib(default=None)
interfaces: List[Interface] = attr.ib(factory=list)
copyright: Optional[str] = None
interfaces: List[Interface] = field(default_factory=list)
@attr.s
@dataclass
class ProtocolParser(xml.sax.handler.ContentHandler):
component: str = attr.ib()
interfaces: List[Interface] = attr.ib(factory=list)
copyright: Optional[Copyright] = attr.ib(init=False, default=None)
component: str
interfaces: List[Interface] = field(default_factory=list)
copyright: Optional[Copyright] = field(init=False, default=None)
current_interface: Optional[Interface] = attr.ib(init=False, default=None)
current_message: Optional[Union[Message, Enum]] = attr.ib(init=False, default=None)
current_description: Optional[Description] = attr.ib(init=False, default=None)
current_interface: Optional[Interface] = field(init=False, default=None)
current_message: Optional[Union[Message, Enum]] = field(init=False, default=None)
current_description: Optional[Description] = field(init=False, default=None)
# A dict of arg name to interface_arg name mappings
current_interface_arg_names: Dict[str, str] = attr.ib(init=False, default=attr.Factory(dict)) # type: ignore
current_new_id_arg: Optional[Argument] = attr.ib(init=False, default=None)
current_interface_arg_names: Dict[str, str] = field(
init=False, default_factory=dict
)
current_new_id_arg: Optional[Argument] = field(init=False, default=None)
_run_counter: int = attr.ib(init=False, default=0, repr=False)
_run_counter: int = field(init=False, default=0, repr=False)
@property
def location(self) -> Tuple[int, int]:

View file

@ -13,7 +13,7 @@ if xmllint.found()
endif
pymod = import('python')
required_python_modules = ['attr', 'jinja2']
required_python_modules = ['jinja2']
python = pymod.find_installation('python3', modules: required_python_modules)
if python.language_version().version_compare('<3.9')
error('Python 3.9 or later required')