ei-scanner: Expose interface_arg, and also provide interface_arg_for

To make this practical to use in a template, we want relations in both
directions. And at least for consistency with other things, these fields
should contain the `Argument` instead of just its name string.

So we need to do this after are the arguments in the message have been
initially parsed. Adding these fields when parsing the request/event
close tag seems to work well enough.

Co-authored-by: Peter Hutterer <peter.hutterer@who-t.net>
This commit is contained in:
Ian Douglas Scott 2023-05-24 12:04:00 -07:00 committed by Peter Hutterer
parent 00226da59e
commit 1d8cd84c56
2 changed files with 54 additions and 1 deletions

View file

@ -17,7 +17,7 @@ Opcodes for events and request are assigned in order as they
appear in the XML file.
"""
from typing import Any, List, Optional, Tuple, Union
from typing import Any, Dict, List, Optional, Tuple, Union
from pathlib import Path
from textwrap import dedent
@ -72,6 +72,16 @@ class Argument:
summary: str = attr.ib()
enum: Optional["Enum"] = attr.ib()
interface: Optional["Interface"] = attr.ib()
interface_arg: Optional["Argument"] = attr.ib(default=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)
"""
For an argument referenced by another argument through "interface_name", this field
points to the other argument that references this argument.
"""
@property
def signature(self) -> str:
@ -165,6 +175,12 @@ class Message:
def camel_name(self) -> str:
return snake2camel(self.name)
def find_argument(self, name: str) -> Optional[Argument]:
for a in self.arguments:
if a.name == name:
return a
return None
@attr.s
class Request(Message):
@ -447,6 +463,8 @@ class ProtocolParser(xml.sax.handler.ContentHandler):
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)
# 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
_run_counter: int = attr.ib(init=False, default=0, repr=False)
@ -658,6 +676,13 @@ class ProtocolParser(xml.sax.handler.ContentHandler):
interface = self.interface_by_name(interface_name)
else:
interface = None
# interface_arg is set to the name of some other arg that specifies the actual
# interface name for this argument
interface_arg_name = attrs.get("interface_arg", None)
if interface_arg_name is not None:
self.current_interface_arg_names[name] = interface_arg_name
enum_name = attrs.get("enum", None)
enum = None
if enum_name is not None:
@ -734,6 +759,21 @@ class ProtocolParser(xml.sax.handler.ContentHandler):
if self._run_counter <= 2:
return
# Populate `interface_arg` and `interface_arg_for`, now we have all arguments
if name in ["request", "event"]:
assert isinstance(self.current_message, Message)
# obj is the argument of type object that the interface applies to
# iname is the argument of type "interface_name" that specifies the interface
for obj, iname in self.current_interface_arg_names.items():
obj_arg = self.current_message.find_argument(obj)
iname_arg = self.current_message.find_argument(iname)
assert obj_arg is not None
assert iname_arg is not None
obj_arg.interface_arg = iname_arg
iname_arg.interface_arg_for = obj_arg
self.current_interface_arg_names = {}
if name == "request":
assert isinstance(self.current_message, Request)
self.current_message = None

View file

@ -45,6 +45,19 @@ class TestScanner:
assert "connection" in [i.plainname for i in protocol.interfaces]
assert "button" in [i.plainname for i in protocol.interfaces]
@pytest.mark.parametrize("component", ("ei",))
def test_interface_arg(self, protocol: Protocol):
intf = next((i for i in protocol.interfaces if i.name == "ei_device"))
event = next((e for e in intf.events if e.name == "interface"))
obj, interface_name, version = event.arguments
assert obj.interface_arg == interface_name
assert obj.interface_arg_for is None
assert interface_name.interface_arg_for == obj
assert interface_name.interface_arg is None
assert version.interface_arg is None
assert version.interface_arg_for is None
@pytest.mark.parametrize("method", ("yamlfile", "jsonfile", "string"))
def test_cli_extra_data(self, tmp_path, method):
result_path = tmp_path / "result"