mirror of
https://gitlab.gnome.org/jwestman/blueprint-compiler.git
synced 2025-05-04 15:59:08 -04:00
decompiler: Support Adw.Breakpoint syntax
Also, improve handling of translated strings.
This commit is contained in:
parent
078ce2f5b8
commit
a12ac1b976
9 changed files with 141 additions and 48 deletions
|
@ -56,7 +56,6 @@ class DecompileCtx:
|
||||||
self.gir = GirContext()
|
self.gir = GirContext()
|
||||||
self._blocks_need_end: T.List[str] = []
|
self._blocks_need_end: T.List[str] = []
|
||||||
self._last_line_type: LineType = LineType.NONE
|
self._last_line_type: LineType = LineType.NONE
|
||||||
self.template_class: T.Optional[str] = None
|
|
||||||
self._obj_type_stack: list[T.Optional[GirType]] = []
|
self._obj_type_stack: list[T.Optional[GirType]] = []
|
||||||
self._node_stack: list[Element] = []
|
self._node_stack: list[Element] = []
|
||||||
|
|
||||||
|
@ -112,6 +111,45 @@ class DecompileCtx:
|
||||||
else:
|
else:
|
||||||
return self._node_stack[-1]
|
return self._node_stack[-1]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parent_node(self) -> T.Optional[Element]:
|
||||||
|
if len(self._node_stack) < 2:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return self._node_stack[-2]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def root_node(self) -> T.Optional[Element]:
|
||||||
|
if len(self._node_stack) == 0:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return self._node_stack[0]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def template_class(self) -> T.Optional[str]:
|
||||||
|
assert self.root_node is not None
|
||||||
|
for child in self.root_node.children:
|
||||||
|
if child.tag == "template":
|
||||||
|
return child["class"]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def find_object(self, id: str) -> T.Optional[Element]:
|
||||||
|
assert self.root_node is not None
|
||||||
|
for child in self.root_node.children:
|
||||||
|
if child.tag == "template" and child["class"] == id:
|
||||||
|
return child
|
||||||
|
|
||||||
|
def find_in_children(node: Element) -> T.Optional[Element]:
|
||||||
|
if node.tag in ["object", "menu"] and node["id"] == id:
|
||||||
|
return node
|
||||||
|
else:
|
||||||
|
for child in node.children:
|
||||||
|
if result := find_in_children(child):
|
||||||
|
return result
|
||||||
|
return None
|
||||||
|
|
||||||
|
return find_in_children(self.root_node)
|
||||||
|
|
||||||
def end_block_with(self, text: str) -> None:
|
def end_block_with(self, text: str) -> None:
|
||||||
self._blocks_need_end[-1] = text
|
self._blocks_need_end[-1] = text
|
||||||
|
|
||||||
|
@ -122,7 +160,15 @@ class DecompileCtx:
|
||||||
if len(self._blocks_need_end):
|
if len(self._blocks_need_end):
|
||||||
self._blocks_need_end[-1] = _CLOSING[line[-1]]
|
self._blocks_need_end[-1] = _CLOSING[line[-1]]
|
||||||
|
|
||||||
def print_value(self, value: str, type: T.Optional[GirType]) -> None:
|
# Converts a value from an XML element to a blueprint string
|
||||||
|
# based on the given type. Returns a tuple of translator comments
|
||||||
|
# (if any) and the decompiled syntax.
|
||||||
|
def decompile_value(
|
||||||
|
self,
|
||||||
|
value: str,
|
||||||
|
type: T.Optional[GirType],
|
||||||
|
translatable: T.Optional[T.Tuple[str, str, str]] = None,
|
||||||
|
) -> T.Tuple[str, str]:
|
||||||
def get_enum_name(value):
|
def get_enum_name(value):
|
||||||
for member in type.members.values():
|
for member in type.members.values():
|
||||||
if (
|
if (
|
||||||
|
@ -133,16 +179,18 @@ class DecompileCtx:
|
||||||
return member.name
|
return member.name
|
||||||
return value.replace("-", "_")
|
return value.replace("-", "_")
|
||||||
|
|
||||||
if type is None:
|
if translatable is not None and truthy(translatable[0]):
|
||||||
self.print(f"{escape_quote(value)}")
|
return decompile_translatable(value, *translatable)
|
||||||
|
elif type is None:
|
||||||
|
return "", f"{escape_quote(value)}"
|
||||||
elif type.assignable_to(FloatType()):
|
elif type.assignable_to(FloatType()):
|
||||||
self.print(str(value))
|
return "", str(value)
|
||||||
elif type.assignable_to(BoolType()):
|
elif type.assignable_to(BoolType()):
|
||||||
val = truthy(value)
|
val = truthy(value)
|
||||||
self.print("true" if val else "false")
|
return "", ("true" if val else "false")
|
||||||
elif type.assignable_to(ArrayType(StringType())):
|
elif type.assignable_to(ArrayType(StringType())):
|
||||||
items = ", ".join([escape_quote(x) for x in value.split("\n")])
|
items = ", ".join([escape_quote(x) for x in value.split("\n")])
|
||||||
self.print(f"[{items}]")
|
return "", f"[{items}]"
|
||||||
elif (
|
elif (
|
||||||
type.assignable_to(self.gir.namespaces["Gtk"].lookup_type("Gdk.Pixbuf"))
|
type.assignable_to(self.gir.namespaces["Gtk"].lookup_type("Gdk.Pixbuf"))
|
||||||
or type.assignable_to(self.gir.namespaces["Gtk"].lookup_type("Gdk.Texture"))
|
or type.assignable_to(self.gir.namespaces["Gtk"].lookup_type("Gdk.Texture"))
|
||||||
|
@ -156,30 +204,25 @@ class DecompileCtx:
|
||||||
self.gir.namespaces["Gtk"].lookup_type("Gtk.ShortcutTrigger")
|
self.gir.namespaces["Gtk"].lookup_type("Gtk.ShortcutTrigger")
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
self.print(f"{escape_quote(value)}")
|
return "", escape_quote(value)
|
||||||
elif value == self.template_class:
|
elif value == self.template_class:
|
||||||
self.print("template")
|
return "", "template"
|
||||||
elif type.assignable_to(
|
elif type.assignable_to(
|
||||||
self.gir.namespaces["Gtk"].lookup_type("GObject.Object")
|
self.gir.namespaces["Gtk"].lookup_type("GObject.Object")
|
||||||
) or isinstance(type, Interface):
|
) or isinstance(type, Interface):
|
||||||
self.print(value)
|
return "", ("null" if value == "" else value)
|
||||||
elif isinstance(type, Bitfield):
|
elif isinstance(type, Bitfield):
|
||||||
flags = [get_enum_name(flag) for flag in value.split("|")]
|
flags = [get_enum_name(flag) for flag in value.split("|")]
|
||||||
self.print(" | ".join(flags))
|
return "", " | ".join(flags)
|
||||||
elif isinstance(type, Enumeration):
|
elif isinstance(type, Enumeration):
|
||||||
self.print(get_enum_name(value))
|
return "", get_enum_name(value)
|
||||||
elif isinstance(type, TypeType):
|
elif isinstance(type, TypeType):
|
||||||
if t := self.type_by_cname(value):
|
if t := self.type_by_cname(value):
|
||||||
self.print(f"typeof<{full_name(t)}>")
|
return "", f"typeof<{full_name(t)}>"
|
||||||
else:
|
else:
|
||||||
self.print(f"typeof<${value}>")
|
return "", f"typeof<${value}>"
|
||||||
else:
|
else:
|
||||||
self.print(f"{escape_quote(value)}")
|
return "", escape_quote(value)
|
||||||
|
|
||||||
def print_attribute(self, name: str, value: str, type: GirType) -> None:
|
|
||||||
self.print(f"{name}: ")
|
|
||||||
self.print_value(value, type)
|
|
||||||
self.print(";")
|
|
||||||
|
|
||||||
|
|
||||||
def decompile_element(
|
def decompile_element(
|
||||||
|
@ -247,7 +290,7 @@ def canon(string: str) -> str:
|
||||||
|
|
||||||
|
|
||||||
def truthy(string: str) -> bool:
|
def truthy(string: str) -> bool:
|
||||||
return string.lower() in ["yes", "true", "t", "y", "1"]
|
return string is not None and string.lower() in ["yes", "true", "t", "y", "1"]
|
||||||
|
|
||||||
|
|
||||||
def full_name(gir: GirType) -> str:
|
def full_name(gir: GirType) -> str:
|
||||||
|
@ -318,9 +361,11 @@ def decompile_translatable(
|
||||||
translatable: T.Optional[str],
|
translatable: T.Optional[str],
|
||||||
context: T.Optional[str],
|
context: T.Optional[str],
|
||||||
comments: T.Optional[str],
|
comments: T.Optional[str],
|
||||||
) -> T.Tuple[T.Optional[str], str]:
|
) -> T.Tuple[str, str]:
|
||||||
if translatable is not None and truthy(translatable):
|
if translatable is not None and truthy(translatable):
|
||||||
if comments is not None:
|
if comments is None:
|
||||||
|
comments = ""
|
||||||
|
else:
|
||||||
comments = comments.replace("/*", " ").replace("*/", " ")
|
comments = comments.replace("/*", " ").replace("*/", " ")
|
||||||
comments = f"/* Translators: {comments} */"
|
comments = f"/* Translators: {comments} */"
|
||||||
|
|
||||||
|
@ -329,7 +374,7 @@ def decompile_translatable(
|
||||||
else:
|
else:
|
||||||
return comments, f"_({escape_quote(string)})"
|
return comments, f"_({escape_quote(string)})"
|
||||||
else:
|
else:
|
||||||
return comments, f"{escape_quote(string)}"
|
return "", f"{escape_quote(string)}"
|
||||||
|
|
||||||
|
|
||||||
@decompiler("property", cdata=True)
|
@decompiler("property", cdata=True)
|
||||||
|
@ -376,7 +421,8 @@ def decompile_property(
|
||||||
elif gir is None or gir.properties.get(name) is None:
|
elif gir is None or gir.properties.get(name) is None:
|
||||||
ctx.print(f"{name}: {escape_quote(cdata)};")
|
ctx.print(f"{name}: {escape_quote(cdata)};")
|
||||||
else:
|
else:
|
||||||
ctx.print_attribute(name, cdata, gir.properties.get(name).type)
|
_, string = ctx.decompile_value(cdata, gir.properties.get(name).type)
|
||||||
|
ctx.print(f"{name}: {string};")
|
||||||
return gir
|
return gir
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -195,3 +195,42 @@ class AdwBreakpointSetters(AstNode):
|
||||||
@validate()
|
@validate()
|
||||||
def unique(self):
|
def unique(self):
|
||||||
self.validate_unique_in_parent("Duplicate setters block")
|
self.validate_unique_in_parent("Duplicate setters block")
|
||||||
|
|
||||||
|
|
||||||
|
@decompiler("condition", cdata=True)
|
||||||
|
def decompile_condition(ctx: DecompileCtx, gir, cdata):
|
||||||
|
ctx.print(f"condition({escape_quote(cdata)})")
|
||||||
|
|
||||||
|
|
||||||
|
@decompiler("setter", element=True)
|
||||||
|
def decompile_setter(ctx: DecompileCtx, gir, element):
|
||||||
|
assert ctx.parent_node is not None
|
||||||
|
# only run for the first setter
|
||||||
|
for child in ctx.parent_node.children:
|
||||||
|
if child.tag == "setter":
|
||||||
|
if child != element:
|
||||||
|
# already decompiled
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
ctx.print("setters {")
|
||||||
|
for child in ctx.parent_node.children:
|
||||||
|
if child.tag == "setter":
|
||||||
|
object_id = child["object"]
|
||||||
|
property_name = child["property"]
|
||||||
|
obj = ctx.find_object(object_id)
|
||||||
|
if obj is not None:
|
||||||
|
gir_class = ctx.type_by_cname(obj["class"])
|
||||||
|
else:
|
||||||
|
gir_class = None
|
||||||
|
|
||||||
|
if object_id == ctx.template_class:
|
||||||
|
object_id = "template"
|
||||||
|
|
||||||
|
comments, string = ctx.decompile_value(
|
||||||
|
child.cdata,
|
||||||
|
gir_class,
|
||||||
|
(child["translatable"], child["context"], child["comments"]),
|
||||||
|
)
|
||||||
|
ctx.print(f"{comments} {object_id}.{property_name}: {string};")
|
||||||
|
|
|
@ -319,7 +319,8 @@ def decompile_constant(
|
||||||
else:
|
else:
|
||||||
ctx.print(cdata)
|
ctx.print(cdata)
|
||||||
else:
|
else:
|
||||||
ctx.print_value(cdata, ctx.type_by_cname(type))
|
_, string = ctx.decompile_value(cdata, ctx.type_by_cname(type))
|
||||||
|
ctx.print(string)
|
||||||
|
|
||||||
|
|
||||||
@decompiler("closure", skip_children=True)
|
@decompiler("closure", skip_children=True)
|
||||||
|
|
|
@ -249,17 +249,12 @@ def a11y_name_completer(lsp, ast_node, match_variables):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def decompile_attr(ctx: DecompileCtx, attr):
|
|
||||||
if attr["translatable"] is not None and decompile.truthy(attr["translatable"]):
|
|
||||||
ctx.print(f"_({escape_quote(attr.cdata)})")
|
|
||||||
else:
|
|
||||||
ctx.print_value(attr.cdata, get_types(ctx.gir).get(attr["name"]))
|
|
||||||
|
|
||||||
|
|
||||||
@decompiler("accessibility", skip_children=True, element=True)
|
@decompiler("accessibility", skip_children=True, element=True)
|
||||||
def decompile_accessibility(ctx: DecompileCtx, _gir, element):
|
def decompile_accessibility(ctx: DecompileCtx, _gir, element):
|
||||||
ctx.print("accessibility {")
|
ctx.print("accessibility {")
|
||||||
already_printed = set()
|
already_printed = set()
|
||||||
|
types = get_types(ctx.gir)
|
||||||
|
|
||||||
for child in element.children:
|
for child in element.children:
|
||||||
name = child["name"]
|
name = child["name"]
|
||||||
|
|
||||||
|
@ -270,13 +265,20 @@ def decompile_accessibility(ctx: DecompileCtx, _gir, element):
|
||||||
ctx.print(f"{name}: [")
|
ctx.print(f"{name}: [")
|
||||||
for value in element.children:
|
for value in element.children:
|
||||||
if value["name"] == name:
|
if value["name"] == name:
|
||||||
decompile_attr(ctx, value)
|
comments, string = ctx.decompile_value(
|
||||||
ctx.print(", ")
|
value.cdata,
|
||||||
|
types.get(value["name"]),
|
||||||
|
(value["translatable"], value["context"], value["comments"]),
|
||||||
|
)
|
||||||
|
ctx.print(f"{comments} {string},")
|
||||||
ctx.print("];")
|
ctx.print("];")
|
||||||
else:
|
else:
|
||||||
ctx.print(f"{name}:")
|
comments, string = ctx.decompile_value(
|
||||||
decompile_attr(ctx, child)
|
child.cdata,
|
||||||
ctx.print(";")
|
types.get(child["name"]),
|
||||||
|
(child["translatable"], child["context"], child["comments"]),
|
||||||
|
)
|
||||||
|
ctx.print(f"{comments} {name}: {string};")
|
||||||
|
|
||||||
already_printed.add(name)
|
already_printed.add(name)
|
||||||
ctx.print("}")
|
ctx.print("}")
|
||||||
|
|
|
@ -102,6 +102,4 @@ def decompile_template(ctx: DecompileCtx, gir, klass, parent=None):
|
||||||
else:
|
else:
|
||||||
ctx.print(f"template {class_name(klass)} : {class_name(parent)} {{")
|
ctx.print(f"template {class_name(klass)} : {class_name(parent)} {{")
|
||||||
|
|
||||||
ctx.template_class = klass
|
|
||||||
|
|
||||||
return ctx.type_by_cname(klass) or ctx.type_by_cname(parent)
|
return ctx.type_by_cname(klass) or ctx.type_by_cname(parent)
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
using Gtk 4.0;
|
using Gtk 4.0;
|
||||||
using Adw 1;
|
using Adw 1;
|
||||||
|
|
||||||
Gtk.Label label {}
|
Label label {}
|
||||||
|
|
||||||
Adw.Breakpoint {
|
Adw.Breakpoint {
|
||||||
condition ("max-width: 600px")
|
condition ("max-width: 600px")
|
||||||
|
|
||||||
setters {
|
setters {
|
||||||
label.label: _("Hello, world!");
|
label.label: C_("test", "Hello, world!");
|
||||||
label.visible: false;
|
label.visible: false;
|
||||||
label.extra-menu: null;
|
label.extra-menu: null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ corresponding .blp file and regenerate this file with blueprint-compiler.
|
||||||
<object class="GtkLabel" id="label"></object>
|
<object class="GtkLabel" id="label"></object>
|
||||||
<object class="AdwBreakpoint">
|
<object class="AdwBreakpoint">
|
||||||
<condition>max-width: 600px</condition>
|
<condition>max-width: 600px</condition>
|
||||||
<setter object="label" property="label" translatable="yes">Hello, world!</setter>
|
<setter object="label" property="label" translatable="yes" context="test">Hello, world!</setter>
|
||||||
<setter object="label" property="visible">false</setter>
|
<setter object="label" property="visible">false</setter>
|
||||||
<setter object="label" property="extra-menu"></setter>
|
<setter object="label" property="extra-menu"></setter>
|
||||||
</object>
|
</object>
|
||||||
|
|
10
tests/samples/adw_breakpoint_template_dec.blp
Normal file
10
tests/samples/adw_breakpoint_template_dec.blp
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
using Adw 1;
|
||||||
|
|
||||||
|
template $MyTemplate {
|
||||||
|
Adw.Breakpoint {
|
||||||
|
setters {
|
||||||
|
template.some-prop: "true";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -210,10 +210,6 @@ class TestSamples(unittest.TestCase):
|
||||||
# Not implemented yet
|
# Not implemented yet
|
||||||
"action_widgets",
|
"action_widgets",
|
||||||
# Not implemented yet
|
# Not implemented yet
|
||||||
"adw_breakpoint",
|
|
||||||
# Not implemented yet
|
|
||||||
"adw_breakpoint_template",
|
|
||||||
# Not implemented yet
|
|
||||||
"gtkcolumnview",
|
"gtkcolumnview",
|
||||||
# Comments are not preserved in either direction
|
# Comments are not preserved in either direction
|
||||||
"comments",
|
"comments",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue