mirror of
https://gitlab.gnome.org/jwestman/blueprint-compiler.git
synced 2025-05-04 15:59:08 -04:00
Add Adw.Breakpoint custom syntax
This commit is contained in:
parent
aafebf0dfb
commit
8fcd08c835
12 changed files with 264 additions and 11 deletions
|
@ -1,8 +1,13 @@
|
|||
from .gtk_list_item_factory import ExtListItemFactory
|
||||
from .adw_message_dialog import ExtAdwMessageDialog
|
||||
from .attributes import BaseAttribute
|
||||
from .adw_breakpoint import (
|
||||
AdwBreakpointSetters,
|
||||
AdwBreakpointSetter,
|
||||
AdwBreakpointCondition,
|
||||
)
|
||||
from .binding import Binding
|
||||
from .contexts import ValueTypeCtx
|
||||
from .contexts import ScopeCtx, ValueTypeCtx
|
||||
from .expression import (
|
||||
CastExpr,
|
||||
ClosureArg,
|
||||
|
@ -53,6 +58,8 @@ from .common import *
|
|||
OBJECT_CONTENT_HOOKS.children = [
|
||||
Signal,
|
||||
Property,
|
||||
AdwBreakpointCondition,
|
||||
AdwBreakpointSetters,
|
||||
ExtAccessibility,
|
||||
ExtAdwMessageDialog,
|
||||
ExtComboBoxItems,
|
||||
|
|
127
blueprintcompiler/language/adw_breakpoint.py
Normal file
127
blueprintcompiler/language/adw_breakpoint.py
Normal file
|
@ -0,0 +1,127 @@
|
|||
# adw_breakpoint.py
|
||||
#
|
||||
# Copyright 2023 James Westman <james@jwestman.net>
|
||||
#
|
||||
# This file is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This file is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
from .common import *
|
||||
from .contexts import ScopeCtx, ValueTypeCtx
|
||||
from .gobject_object import Object, validate_parent_type
|
||||
from .values import Value
|
||||
|
||||
|
||||
class AdwBreakpointCondition(AstNode):
|
||||
grammar = ["condition", "(", UseQuoted("condition"), Match(")").expected()]
|
||||
|
||||
@property
|
||||
def condition(self) -> str:
|
||||
return self.tokens["condition"]
|
||||
|
||||
@validate()
|
||||
def unique(self):
|
||||
self.validate_unique_in_parent("Duplicate condition statement")
|
||||
|
||||
|
||||
class AdwBreakpointSetter(AstNode):
|
||||
grammar = Statement(
|
||||
UseIdent("object"),
|
||||
Match(".").expected(),
|
||||
UseIdent("property"),
|
||||
Match(":").expected(),
|
||||
Value,
|
||||
)
|
||||
|
||||
@property
|
||||
def object_id(self) -> str:
|
||||
return self.tokens["object"]
|
||||
|
||||
@property
|
||||
def object(self) -> T.Optional[Object]:
|
||||
return self.context[ScopeCtx].objects.get(self.object_id)
|
||||
|
||||
@property
|
||||
def property_name(self) -> T.Optional[str]:
|
||||
return self.tokens["property"]
|
||||
|
||||
@property
|
||||
def value(self) -> Value:
|
||||
return self.children[Value][0]
|
||||
|
||||
@property
|
||||
def gir_class(self) -> T.Optional[GirType]:
|
||||
if self.object is not None:
|
||||
return self.object.gir_class
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def gir_property(self):
|
||||
if self.gir_class is not None and not isinstance(self.gir_class, ExternType):
|
||||
return self.gir_class.properties.get(self.property_name)
|
||||
|
||||
@context(ValueTypeCtx)
|
||||
def value_type(self) -> ValueTypeCtx:
|
||||
if self.gir_property is not None:
|
||||
type = self.gir_property.type
|
||||
else:
|
||||
type = None
|
||||
|
||||
return ValueTypeCtx(type, allow_null=True)
|
||||
|
||||
@validate("object")
|
||||
def object_exists(self):
|
||||
if self.object is None:
|
||||
raise CompileError(
|
||||
f"Could not find object with ID {self.object_id}",
|
||||
did_you_mean=(self.object_id, self.context[ScopeCtx].objects.keys()),
|
||||
)
|
||||
|
||||
@validate("property")
|
||||
def property_exists(self):
|
||||
if self.gir_class is None or self.gir_class.incomplete:
|
||||
# Objects that we have no gir data on should not be validated
|
||||
# This happens for classes defined by the app itself
|
||||
return
|
||||
|
||||
if self.gir_property is None:
|
||||
raise CompileError(
|
||||
f"Class {self.gir_class.full_name} does not have a property called {self.property_name}",
|
||||
did_you_mean=(self.property_name, self.gir_class.properties.keys()),
|
||||
)
|
||||
|
||||
@validate()
|
||||
def unique(self):
|
||||
self.validate_unique_in_parent(
|
||||
f"Duplicate setter for {self.object_id}.{self.property_name}",
|
||||
lambda x: x.object_id == self.object_id
|
||||
and x.property_name == self.property_name,
|
||||
)
|
||||
|
||||
|
||||
class AdwBreakpointSetters(AstNode):
|
||||
grammar = ["setters", Match("{").expected(), Until(AdwBreakpointSetter, "}")]
|
||||
|
||||
@property
|
||||
def setters(self) -> T.List[AdwBreakpointSetter]:
|
||||
return self.children[AdwBreakpointSetter]
|
||||
|
||||
@validate()
|
||||
def container_is_breakpoint(self):
|
||||
validate_parent_type(self, "Adw", "Breakpoint", "breakpoint setters")
|
||||
|
||||
@validate()
|
||||
def unique(self):
|
||||
self.validate_unique_in_parent("Duplicate setters block")
|
|
@ -28,6 +28,7 @@ from .gobject_object import Object
|
|||
@dataclass
|
||||
class ValueTypeCtx:
|
||||
value_type: T.Optional[GirType]
|
||||
allow_null: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
|
@ -76,11 +76,6 @@ class Property(AstNode):
|
|||
# This happens for classes defined by the app itself
|
||||
return
|
||||
|
||||
if isinstance(self.parent.parent, Template):
|
||||
# If the property is part of a template, it might be defined by
|
||||
# the application and thus not in gir
|
||||
return
|
||||
|
||||
if self.gir_property is None:
|
||||
raise CompileError(
|
||||
f"Class {self.gir_class.full_name} does not have a property called {self.tokens['name']}",
|
||||
|
|
|
@ -280,10 +280,17 @@ class IdentLiteral(AstNode):
|
|||
elif expected_type is not None:
|
||||
object = self.context[ScopeCtx].objects.get(self.ident)
|
||||
if object is None:
|
||||
raise CompileError(
|
||||
f"Could not find object with ID {self.ident}",
|
||||
did_you_mean=(self.ident, self.context[ScopeCtx].objects.keys()),
|
||||
)
|
||||
if self.ident == "null":
|
||||
if not self.context[ValueTypeCtx].allow_null:
|
||||
raise CompileError("null is not permitted here")
|
||||
else:
|
||||
raise CompileError(
|
||||
f"Could not find object with ID {self.ident}",
|
||||
did_you_mean=(
|
||||
self.ident,
|
||||
self.context[ScopeCtx].objects.keys(),
|
||||
),
|
||||
)
|
||||
elif object.gir_class and not object.gir_class.assignable_to(expected_type):
|
||||
raise CompileError(
|
||||
f"Cannot assign {object.gir_class.full_name} to {expected_type.full_name}"
|
||||
|
|
|
@ -280,6 +280,37 @@ class XmlOutput(OutputFormat):
|
|||
self._emit_attribute(prop.tag_name, "name", prop.name, prop.value, xml)
|
||||
xml.end_tag()
|
||||
|
||||
elif isinstance(extension, AdwBreakpointCondition):
|
||||
xml.start_tag("condition")
|
||||
xml.put_text(extension.condition)
|
||||
xml.end_tag()
|
||||
|
||||
elif isinstance(extension, AdwBreakpointSetters):
|
||||
for setter in extension.setters:
|
||||
attrs = {}
|
||||
|
||||
if isinstance(setter.value.child, Translated):
|
||||
attrs = self._translated_string_attrs(setter.value.child)
|
||||
|
||||
xml.start_tag(
|
||||
"setter",
|
||||
object=setter.object_id,
|
||||
property=setter.property_name,
|
||||
**attrs,
|
||||
)
|
||||
if isinstance(setter.value.child, Translated):
|
||||
xml.put_text(setter.value.child.string)
|
||||
elif (
|
||||
isinstance(setter.value.child, Literal)
|
||||
and isinstance(setter.value.child.value, IdentLiteral)
|
||||
and setter.value.child.value.ident == "null"
|
||||
and setter.context[ScopeCtx].objects.get("null") is None
|
||||
):
|
||||
pass
|
||||
else:
|
||||
self._emit_value(setter.value, xml)
|
||||
xml.end_tag()
|
||||
|
||||
elif isinstance(extension, Filters):
|
||||
xml.start_tag(extension.tokens["tag_name"])
|
||||
for prop in extension.children:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue