Refactor child types

Didn't change the actual syntax, but changed the rules around to be less
confusing.
This commit is contained in:
James Westman 2023-04-29 21:16:14 -05:00
parent 9dcd06de51
commit 71f52d350a
11 changed files with 74 additions and 55 deletions

View file

@ -109,9 +109,9 @@ class AstNode:
else: else:
return self.parent.root return self.parent.root
def parent_by_type(self, type): def parent_by_type(self, type: T.Type[TType]) -> TType:
if self.parent is None: if self.parent is None:
return None raise CompilerBugError()
elif isinstance(self.parent, type): elif isinstance(self.parent, type):
return self.parent return self.parent
else: else:

View file

@ -23,7 +23,7 @@ from .gtk_menu import menu, Menu, MenuAttribute
from .gtk_size_group import Widgets from .gtk_size_group import Widgets
from .gtk_string_list import Strings from .gtk_string_list import Strings
from .gtk_styles import Styles from .gtk_styles import Styles
from .gtkbuilder_child import Child from .gtkbuilder_child import Child, ChildType, ChildInternal, ChildExtension
from .gtkbuilder_template import Template from .gtkbuilder_template import Template
from .imports import GtkDirective, Import from .imports import GtkDirective, Import
from .property_binding import PropertyBinding from .property_binding import PropertyBinding

View file

@ -22,7 +22,7 @@ import typing as T
from functools import cached_property from functools import cached_property
from .common import * from .common import *
from .response_id import ResponseId from .response_id import ExtResponse
from .types import ClassName, ConcreteClassName from .types import ClassName, ConcreteClassName
@ -60,7 +60,7 @@ class Object(AstNode):
return self.class_name.gir_type return self.class_name.gir_type
@cached_property @cached_property
def action_widgets(self) -> T.List[ResponseId]: def action_widgets(self) -> T.List[ExtResponse]:
"""Get list of widget's action widgets. """Get list of widget's action widgets.
Empty if object doesn't have action widgets. Empty if object doesn't have action widgets.
@ -69,7 +69,7 @@ class Object(AstNode):
return [ return [
child.response_id child.response_id
for child in self.children[ObjectContent][0].children[Child] for child in self.content.children[Child]
if child.response_id if child.response_id
] ]

View file

@ -21,7 +21,7 @@
from functools import cached_property from functools import cached_property
from .gobject_object import Object from .gobject_object import Object
from .response_id import ResponseId from .response_id import ExtResponse
from .common import * from .common import *
ALLOWED_PARENTS: T.List[T.Tuple[str, str]] = [ ALLOWED_PARENTS: T.List[T.Tuple[str, str]] = [
@ -30,20 +30,49 @@ ALLOWED_PARENTS: T.List[T.Tuple[str, str]] = [
] ]
class ChildInternal(AstNode):
grammar = ["internal-child", UseIdent("internal_child")]
@property
def internal_child(self) -> str:
return self.tokens["internal_child"]
class ChildType(AstNode):
grammar = UseIdent("child_type").expected("a child type")
@property
def child_type(self) -> str:
return self.tokens["child_type"]
class ChildExtension(AstNode):
grammar = ExtResponse
@property
def child(self) -> ExtResponse:
return self.children[0]
class ChildAnnotation(AstNode):
grammar = ["[", AnyOf(ChildInternal, ChildExtension, ChildType), "]"]
@property
def child(self) -> T.Union[ChildInternal, ChildExtension, ChildType]:
return self.children[0]
class Child(AstNode): class Child(AstNode):
grammar = [ grammar = [
Optional( Optional(ChildAnnotation),
[
"[",
Optional(["internal-child", UseLiteral("internal_child", True)]),
UseIdent("child_type").expected("a child type"),
Optional(ResponseId),
"]",
]
),
Object, Object,
] ]
@property
def annotation(self) -> T.Optional[ChildAnnotation]:
annotations = self.children[ChildAnnotation]
return annotations[0] if len(annotations) else None
@property @property
def object(self) -> Object: def object(self) -> Object:
return self.children[Object][0] return self.children[Object][0]
@ -69,15 +98,17 @@ class Child(AstNode):
) )
@cached_property @cached_property
def response_id(self) -> T.Optional[ResponseId]: def response_id(self) -> T.Optional[ExtResponse]:
"""Get action widget's response ID. """Get action widget's response ID.
If child is not action widget, returns `None`. If child is not action widget, returns `None`.
""" """
response_ids = self.children[ResponseId] if (
self.annotation is not None
if response_ids: and isinstance(self.annotation.child, ChildExtension)
return response_ids[0] and isinstance(self.annotation.child.child, ExtResponse)
):
return self.annotation.child.child
else: else:
return None return None

View file

@ -23,12 +23,13 @@ import typing as T
from .common import * from .common import *
class ResponseId(AstNode): class ExtResponse(AstNode):
"""Response ID of action widget.""" """Response ID of action widget."""
ALLOWED_PARENTS: T.List[T.Tuple[str, str]] = [("Gtk", "Dialog"), ("Gtk", "InfoBar")] ALLOWED_PARENTS: T.List[T.Tuple[str, str]] = [("Gtk", "Dialog"), ("Gtk", "InfoBar")]
grammar = [ grammar = [
Keyword("action"),
Keyword("response"), Keyword("response"),
"=", "=",
AnyOf( AnyOf(
@ -41,13 +42,6 @@ class ResponseId(AstNode):
Optional([Keyword("default"), UseLiteral("is_default", True)]), Optional([Keyword("default"), UseLiteral("is_default", True)]),
] ]
@validate()
def child_type_is_action(self) -> None:
"""Check that child type is "action"."""
child_type = self.parent.tokens["child_type"]
if child_type != "action":
raise CompileError(f"Only action widget can have response ID")
@validate() @validate()
def parent_has_action_widgets(self) -> None: def parent_has_action_widgets(self) -> None:
"""Chech that parent widget has allowed type.""" """Chech that parent widget has allowed type."""
@ -59,7 +53,7 @@ class ResponseId(AstNode):
gir = self.root.gir gir = self.root.gir
for namespace, name in ResponseId.ALLOWED_PARENTS: for namespace, name in ExtResponse.ALLOWED_PARENTS:
parent_type = gir.get_type(name, namespace) parent_type = gir.get_type(name, namespace)
if container_type.assignable_to(parent_type): if container_type.assignable_to(parent_type):
break break
@ -71,10 +65,10 @@ class ResponseId(AstNode):
@validate() @validate()
def widget_have_id(self) -> None: def widget_have_id(self) -> None:
"""Check that action widget have ID.""" """Check that action widget have ID."""
from .gobject_object import Object from .gtkbuilder_child import Child
_object = self.parent.children[Object][0] object = self.parent_by_type(Child).object
if _object.tokens["id"] is None: if object.id is None:
raise CompileError(f"Action widget must have ID") raise CompileError(f"Action widget must have ID")
@validate("response_id") @validate("response_id")
@ -102,10 +96,9 @@ class ResponseId(AstNode):
@validate("default") @validate("default")
def no_multiple_default(self) -> None: def no_multiple_default(self) -> None:
"""Only one action widget in dialog can be default.""" """Only one action widget in dialog can be default."""
from .gtkbuilder_child import Child
from .gobject_object import Object from .gobject_object import Object
if not self.tokens["is_default"]: if not self.is_default:
return return
action_widgets = self.parent_by_type(Object).action_widgets action_widgets = self.parent_by_type(Object).action_widgets
@ -126,7 +119,7 @@ class ResponseId(AstNode):
@property @property
def widget_id(self) -> str: def widget_id(self) -> str:
"""Get action widget ID.""" """Get action widget ID."""
from .gobject_object import Object from .gtkbuilder_child import Child
_object: Object = self.parent.children[Object][0] object = self.parent_by_type(Child).object
return _object.tokens["id"] return object.id

View file

@ -170,11 +170,16 @@ class XmlOutput(OutputFormat):
def _emit_child(self, child: Child, xml: XmlEmitter): def _emit_child(self, child: Child, xml: XmlEmitter):
child_type = internal_child = None child_type = internal_child = None
if child.annotation is not None:
if child.tokens["internal_child"]: annotation = child.annotation.child
internal_child = child.tokens["child_type"] if isinstance(annotation, ChildType):
else: child_type = annotation.child_type
child_type = child.tokens["child_type"] elif isinstance(annotation, ChildInternal):
internal_child = annotation.internal_child
elif isinstance(annotation, ChildExtension):
child_type = "action"
else:
raise CompilerBugError()
xml.start_tag("child", type=child_type, internal_child=internal_child) xml.start_tag("child", type=child_type, internal_child=internal_child)
self._emit_object(child.object, xml) self._emit_object(child.object, xml)

View file

@ -1 +1 @@
4,13,15,Action widget must have ID 4,6,22,Action widget must have ID

View file

@ -1 +1 @@
4,13,11,Gtk.Box doesn't have action widgets 4,6,18,Gtk.Box doesn't have action widgets

View file

@ -1,8 +0,0 @@
using Gtk 4.0;
Dialog {
[some_type response=ok]
Button ok_button {
}
}

View file

@ -1 +0,0 @@
4,16,11,Only action widget can have response ID

View file

@ -216,7 +216,6 @@ class TestSamples(unittest.TestCase):
self.assert_sample_error("action_widget_float_response") self.assert_sample_error("action_widget_float_response")
self.assert_sample_error("action_widget_have_no_id") self.assert_sample_error("action_widget_have_no_id")
self.assert_sample_error("action_widget_multiple_default") self.assert_sample_error("action_widget_multiple_default")
self.assert_sample_error("action_widget_not_action")
self.assert_sample_error("action_widget_in_invalid_container") self.assert_sample_error("action_widget_in_invalid_container")
self.assert_sample_error("action_widget_response_dne") self.assert_sample_error("action_widget_response_dne")
self.assert_sample_error("action_widget_negative_response") self.assert_sample_error("action_widget_negative_response")