decompiler: Add more decompilable tags

Add more tags to the list of things the decompiler can handle. This
required some changes to track the containing object class in the
DecompileCtx, since some objects use the same tag names.

The improved support means we can test the decompiler on most of the
test suite. Any new test samples will by default be tested to ensure the
decompiler produces the original blueprint file.

Also, updated the decompiler to always use double quotes.
This commit is contained in:
James Westman 2024-04-06 13:36:22 -05:00
parent ea4c7245be
commit c1a82a034b
49 changed files with 396 additions and 151 deletions

View file

@ -17,8 +17,8 @@
#
# SPDX-License-Identifier: LGPL-3.0-or-later
import re
import typing as T
from collections import defaultdict
from dataclasses import dataclass
from enum import Enum
@ -30,7 +30,7 @@ from .xml_reader import Element, parse, parse_string
__all__ = ["decompile"]
_DECOMPILERS: T.Dict = {}
_DECOMPILERS: dict[str, list] = defaultdict(list)
_CLOSING = {
"{": "}",
"[": "]",
@ -54,24 +54,25 @@ class DecompileCtx:
def __init__(self) -> None:
self._result: str = ""
self.gir = GirContext()
self._indent: int = 0
self._blocks_need_end: T.List[str] = []
self._last_line_type: LineType = LineType.NONE
self.template_class: T.Optional[str] = None
self._obj_type_stack: list[T.Optional[GirType]] = []
self._node_stack: list[Element] = []
self.gir.add_namespace(get_namespace("Gtk", "4.0"))
@property
def result(self) -> str:
imports = "\n".join(
import_lines = sorted(
[
f"using {ns} {namespace.version};"
for ns, namespace in self.gir.namespaces.items()
if ns != "Gtk"
]
)
full_string = imports + "\n" + self._result
formatted_string = formatter.format(full_string)
return formatted_string
full_string = "\n".join(["using Gtk 4.0;", *import_lines]) + self._result
return formatter.format(full_string)
def type_by_cname(self, cname: str) -> T.Optional[GirType]:
if type := self.gir.get_type_by_cname(cname):
@ -90,10 +91,26 @@ class DecompileCtx:
def start_block(self) -> None:
self._blocks_need_end.append("")
self._obj_type_stack.append(None)
def end_block(self) -> None:
if close := self._blocks_need_end.pop():
self.print(close)
self._obj_type_stack.pop()
@property
def current_obj_type(self) -> T.Optional[GirType]:
return next((x for x in reversed(self._obj_type_stack) if x is not None), None)
def push_obj_type(self, type: T.Optional[GirType]) -> None:
self._obj_type_stack[-1] = type
@property
def current_node(self) -> T.Optional[Element]:
if len(self._node_stack) == 0:
return None
else:
return self._node_stack[-1]
def end_block_with(self, text: str) -> None:
self._blocks_need_end[-1] = text
@ -105,7 +122,7 @@ class DecompileCtx:
if len(self._blocks_need_end):
self._blocks_need_end[-1] = _CLOSING[line[-1]]
def print_attribute(self, name: str, value: str, type: GirType) -> None:
def print_value(self, value: str, type: T.Optional[GirType]) -> None:
def get_enum_name(value):
for member in type.members.values():
if (
@ -117,12 +134,14 @@ class DecompileCtx:
return value.replace("-", "_")
if type is None:
self.print(f"{name}: {escape_quote(value)};")
self.print(f"{escape_quote(value)}")
elif type.assignable_to(FloatType()):
self.print(f"{name}: {value};")
self.print(str(value))
elif type.assignable_to(BoolType()):
val = truthy(value)
self.print(f"{name}: {'true' if val else 'false'};")
self.print("true" if val else "false")
elif type.assignable_to(ArrayType(StringType())):
self.print(f"[{', '.join([escape_quote(x) for x in value.split('\n')])}]")
elif (
type.assignable_to(self.gir.namespaces["Gtk"].lookup_type("Gdk.Pixbuf"))
or type.assignable_to(self.gir.namespaces["Gtk"].lookup_type("Gdk.Texture"))
@ -136,30 +155,42 @@ class DecompileCtx:
self.gir.namespaces["Gtk"].lookup_type("Gtk.ShortcutTrigger")
)
):
self.print(f"{name}: {escape_quote(value)};")
self.print(f"{escape_quote(value)}")
elif value == self.template_class:
self.print(f"{name}: template;")
self.print("template")
elif type.assignable_to(
self.gir.namespaces["Gtk"].lookup_type("GObject.Object")
):
self.print(f"{name}: {value};")
) or isinstance(type, Interface):
self.print(value)
elif isinstance(type, Bitfield):
flags = [get_enum_name(flag) for flag in value.split("|")]
self.print(f"{name}: {' | '.join(flags)};")
self.print(" | ".join(flags))
elif isinstance(type, Enumeration):
self.print(f"{name}: {get_enum_name(value)};")
self.print(get_enum_name(value))
elif isinstance(type, TypeType):
if t := self.type_by_cname(value):
self.print(f"typeof<{full_name(t)}>")
else:
self.print(f"typeof<${value}>")
else:
self.print(f"{name}: {escape_quote(value)};")
self.print(f"{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(
ctx: DecompileCtx, gir: T.Optional[GirContext], xml: Element
) -> None:
try:
decompiler = _DECOMPILERS.get(xml.tag)
if decompiler is None:
decompilers = [d for d in _DECOMPILERS[xml.tag] if d._filter(ctx)]
if len(decompilers) == 0:
raise UnsupportedError(f"unsupported XML tag: <{xml.tag}>")
decompiler = decompilers[0]
args: T.Dict[str, T.Optional[str]] = {
canon(name): value for name, value in xml.attrs.items()
}
@ -169,13 +200,16 @@ def _decompile_element(
else:
args["cdata"] = xml.cdata
ctx._node_stack.append(xml)
ctx.start_block()
gir = decompiler(ctx, gir, **args)
for child in xml.children:
_decompile_element(ctx, gir, child)
if not decompiler._skip_children:
for child in xml.children:
decompile_element(ctx, gir, child)
ctx.end_block()
ctx._node_stack.pop()
except UnsupportedError as e:
raise e
@ -187,7 +221,7 @@ def decompile(data: str) -> str:
ctx = DecompileCtx()
xml = parse(data)
_decompile_element(ctx, None, xml)
decompile_element(ctx, None, xml)
return ctx.result
@ -196,7 +230,7 @@ def decompile_string(data):
ctx = DecompileCtx()
xml = parse_string(data)
_decompile_element(ctx, None, xml)
decompile_element(ctx, None, xml)
return ctx.result
@ -212,7 +246,7 @@ def truthy(string: str) -> bool:
return string.lower() in ["yes", "true", "t", "y", "1"]
def full_name(gir) -> str:
def full_name(gir: GirType) -> str:
return gir.name if gir.full_name.startswith("Gtk.") else gir.full_name
@ -223,17 +257,43 @@ def lookup_by_cname(gir, cname: str) -> T.Optional[GirType]:
return gir.get_containing(Repository).get_type_by_cname(cname)
def decompiler(tag, cdata=False):
def decompiler(
tag,
cdata=False,
parent_type: T.Optional[str] = None,
parent_tag: T.Optional[str] = None,
skip_children=False,
):
def decorator(func):
func._cdata = cdata
_DECOMPILERS[tag] = func
func._skip_children = skip_children
def filter(ctx):
if parent_type is not None:
if (
ctx.current_obj_type is None
or ctx.current_obj_type.full_name != parent_type
):
return False
if parent_tag is not None:
if not any(x.tag == parent_tag for x in ctx._node_stack):
return False
return True
func._filter = filter
_DECOMPILERS[tag].append(func)
return func
return decorator
@decompiler("interface")
def decompile_interface(ctx, gir):
def decompile_interface(ctx, gir, domain=None):
if domain is not None:
ctx.print(f"translation-domain {escape_quote(domain)};")
return gir
@ -284,7 +344,7 @@ def decompile_property(
ctx.print(f"/* Translators: {comments} */")
if cdata is None:
ctx.print(f"{name}: ", False)
ctx.print(f"{name}: ")
ctx.end_block_with(";")
elif bind_source:
flags = ""
@ -295,6 +355,10 @@ def decompile_property(
flags += " inverted"
if "bidirectional" in bind_flags:
flags += " bidirectional"
if bind_source == ctx.template_class:
bind_source = "template"
ctx.print(f"{name}: bind {bind_source}.{bind_property}{flags};")
elif truthy(translatable):
comments, translatable = decompile_translatable(

View file

@ -904,6 +904,10 @@ class Namespace(GirNode):
def get_type_by_cname(self, cname: str) -> T.Optional[GirType]:
"""Gets a type from this namespace by its C name."""
for basic in _BASIC_TYPES.values():
if basic.glib_type_name == cname:
return basic()
for item in self.entries.values():
if (
hasattr(item, "cname")

View file

@ -107,3 +107,9 @@ class SimpleBinding:
no_sync_create: bool = False
bidirectional: bool = False
inverted: bool = False
@decompiler("binding")
def decompile_binding(ctx: DecompileCtx, gir: gir.GirContext, name: str):
ctx.end_block_with(";")
ctx.print(f"{name}: bind ")

View file

@ -18,9 +18,9 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
from ..decompiler import decompile_element
from .common import *
from .contexts import ScopeCtx, ValueTypeCtx
from .gtkbuilder_template import Template
from .types import TypeName
expr = Sequence()
@ -274,3 +274,69 @@ expr.children = [
AnyOf(ClosureExpr, LiteralExpr, ["(", Expression, ")"]),
ZeroOrMore(AnyOf(LookupOp, CastExpr)),
]
@decompiler("lookup", skip_children=True, cdata=True)
def decompile_lookup(
ctx: DecompileCtx, gir: gir.GirContext, cdata: str, name: str, type: str
):
if t := ctx.type_by_cname(type):
type = decompile.full_name(t)
else:
type = "$" + type
assert ctx.current_node is not None
constant = None
if len(ctx.current_node.children) == 0:
constant = cdata
elif (
len(ctx.current_node.children) == 1
and ctx.current_node.children[0].tag == "constant"
):
constant = ctx.current_node.children[0].cdata
if constant is not None:
if constant == ctx.template_class:
ctx.print("template." + name)
else:
ctx.print(constant + "." + name)
return
else:
for child in ctx.current_node.children:
decompile.decompile_element(ctx, gir, child)
ctx.print(f" as <{type}>.{name}")
@decompiler("constant", cdata=True)
def decompile_constant(
ctx: DecompileCtx, gir: gir.GirContext, cdata: str, type: T.Optional[str] = None
):
if type is None:
if cdata == ctx.template_class:
ctx.print("template")
else:
ctx.print(cdata)
else:
ctx.print_value(cdata, ctx.type_by_cname(type))
@decompiler("closure", skip_children=True)
def decompile_closure(ctx: DecompileCtx, gir: gir.GirContext, function: str, type: str):
if t := ctx.type_by_cname(type):
type = decompile.full_name(t)
else:
type = "$" + type
ctx.print(f"${function}(")
assert ctx.current_node is not None
for i, node in enumerate(ctx.current_node.children):
decompile_element(ctx, gir, node)
assert ctx.current_node is not None
if i < len(ctx.current_node.children) - 1:
ctx.print(", ")
ctx.end_block_with(f") as <{type}>")

View file

@ -115,7 +115,7 @@ def validate_parent_type(node, ns: str, name: str, err_msg: str):
@decompiler("object")
def decompile_object(ctx, gir, klass, id=None):
def decompile_object(ctx: DecompileCtx, gir, klass, id=None):
gir_class = ctx.type_by_cname(klass)
klass_name = (
decompile.full_name(gir_class) if gir_class is not None else "$" + klass
@ -124,4 +124,5 @@ def decompile_object(ctx, gir, klass, id=None):
ctx.print(f"{klass_name} {{")
else:
ctx.print(f"{klass_name} {id} {{")
ctx.push_obj_type(gir_class)
return gir_class

View file

@ -89,3 +89,29 @@ class ExtComboBoxItems(AstNode):
)
def items_completer(lsp, ast_node, match_variables):
yield Completion("items", CompletionItemKind.Snippet, snippet="items [$0]")
@decompiler("items", parent_type="Gtk.ComboBoxText")
def decompile_items(ctx: DecompileCtx, gir: gir.GirContext):
ctx.print("items [")
@decompiler("item", parent_type="Gtk.ComboBoxText", cdata=True)
def decompile_item(
ctx: DecompileCtx,
gir: gir.GirContext,
cdata: str,
id: T.Optional[str] = None,
translatable="false",
comments=None,
context=None,
):
comments, translatable = decompile_translatable(
cdata, translatable, context, comments
)
if comments:
ctx.print(comments)
if id:
ctx.print(f"{id}: ")
ctx.print(translatable)
ctx.print(",")

View file

@ -266,7 +266,7 @@ def decompile_submenu(ctx, gir, id=None):
ctx.print("submenu {")
@decompiler("item")
@decompiler("item", parent_tag="menu")
def decompile_item(ctx, gir, id=None):
if id:
ctx.print(f"item {id} {{")

View file

@ -96,3 +96,13 @@ class ExtSizeGroupWidgets(AstNode):
)
def size_group_completer(lsp, ast_node, match_variables):
yield Completion("widgets", CompletionItemKind.Snippet, snippet="widgets [$0]")
@decompiler("widgets")
def size_group_decompiler(ctx, gir: gir.GirContext):
ctx.print("widgets [")
@decompiler("widget")
def widget_decompiler(ctx, gir: gir.GirContext, name: str):
ctx.print(name + ",")

View file

@ -73,3 +73,25 @@ class ExtStringListStrings(AstNode):
)
def strings_completer(lsp, ast_node, match_variables):
yield Completion("strings", CompletionItemKind.Snippet, snippet="strings [$0]")
@decompiler("items", parent_type="Gtk.StringList")
def decompile_strings(ctx: DecompileCtx, gir: gir.GirContext):
ctx.print("strings [")
@decompiler("item", cdata=True, parent_type="Gtk.StringList")
def decompile_item(
ctx: DecompileCtx,
gir: gir.GirContext,
translatable="false",
comments=None,
context=None,
cdata=None,
):
comments, translatable = decompile_translatable(
cdata, translatable, context, comments
)
if comments is not None:
ctx.print(comments)
ctx.print(translatable + ",")

View file

@ -109,14 +109,14 @@ class UnescapeError(Exception):
def escape_quote(string: str) -> str:
return (
"'"
'"'
+ (
string.replace("\\", "\\\\")
.replace("'", "\\'")
.replace('"', '\\"')
.replace("\n", "\\n")
.replace("\t", "\\t")
)
+ "'"
+ '"'
)

View file

@ -7,5 +7,5 @@ Gtk.Box {
checked: true;
}
}
Gtk.Label my_label {}
Gtk.Label my_label {}

View file

@ -2,7 +2,7 @@ using Gtk 4.0;
Box {
accessibility {
label: _('Hello, world!');
label: _("Hello, world!");
labelled-by: my_label;
checked: true;
}

View file

@ -1,25 +1,25 @@
using Gtk 4.0;
Dialog {
[action response=cancel]
Button cancel_button {
label: _("Cancel");
}
[action response=cancel]
Button cancel_button {
label: _("Cancel");
}
[action response=9]
Button custom_response_button {
label: _("Reinstall Windows");
}
[action response=9]
Button custom_response_button {
label: _("Reinstall Windows");
}
[action response=ok default]
Button ok_button {
label: _("Ok");
}
[action response=ok default]
Button ok_button {
label: _("Ok");
}
}
InfoBar {
[action response=ok]
Button ok_info_button {
label: _("Ok");
}
[action response=ok]
Button ok_info_button {
label: _("Ok");
}
}

View file

@ -3,8 +3,8 @@ using Adw 1;
Adw.AlertDialog {
responses [
cancel: _('Cancel'),
discard: _('Discard') destructive,
save: 'Save' suggested disabled,
cancel: _("Cancel"),
discard: _("Discard") destructive,
save: "Save" suggested disabled,
]
}
}

View file

@ -3,8 +3,8 @@ using Adw 1;
Adw.MessageDialog {
responses [
cancel: _('Cancel'),
discard: _('Discard') destructive,
save: 'Save' suggested disabled,
cancel: _("Cancel"),
discard: _("Discard") destructive,
save: "Save" suggested disabled,
]
}
}

View file

@ -2,4 +2,4 @@ using Gtk 4.0;
Label my-label {
label: bind ($my-closure(my-label.margin-bottom)) as <string>;
}
}

View file

@ -1,5 +1,5 @@
using Gtk 4.0;
Label {
label: bind $my-closure (true, 10, "Hello") as <string>;
}
label: bind $my-closure(true, 10, "Hello") as <string>;
}

View file

@ -0,0 +1,5 @@
using Gtk 4.0;
Label my-label {
label: bind $my-closure(my-label.margin-bottom) as <string>;
}

View file

@ -5,5 +5,5 @@ Overlay {
}
Label {
label: bind (label.parent) as <Overlay>.child as <Label>.label;
label: bind label.parent as <Overlay>.child as <Label>.label;
}

View file

@ -1,18 +1,18 @@
using Gtk 4.0;
FileFilter {
name: 'File Filter Name';
name: "File Filter Name";
mime-types [
'text/plain',
'image/ *',
"text/plain",
"image/ *",
]
patterns [
'*.txt',
"*.txt",
]
suffixes [
'png',
"png",
]
}

View file

@ -2,4 +2,4 @@ using Gtk 4.0;
Adjustment {
value: bind 1.0 as <double>;
}
}

View file

@ -0,0 +1,5 @@
using Gtk 4.0;
Adjustment {
value: bind 1;
}

View file

@ -3,8 +3,8 @@ using Gtk 4.0;
Grid {
Label {
layout {
column: '0';
row: '1';
column: "0";
row: "1";
}
}
}

View file

@ -1,11 +1,11 @@
using Gtk 4.0;
Gtk.ListView {
factory: Gtk.BuilderListItemFactory list_item_factory {
template ListItem {
child: Label {
label: bind template.item as <$MyObject>.name;
};
}
};
}
factory: Gtk.BuilderListItemFactory list_item_factory {
template ListItem {
child: Label {
label: bind template.item as <$MyObject>.name;
};
}
};
}

View file

@ -3,26 +3,26 @@ using Gtk 4.0;
menu my-menu {
submenu {
section {
label: 'test section';
label: "test section";
}
item {
label: C_('context', 'test translated item');
label: C_("context", "test translated item");
}
item {
label: 'test item shorthand 1';
label: "test item shorthand 1";
}
item {
label: 'test item shorthand 2';
action: 'app.test-action';
label: "test item shorthand 2";
action: "app.test-action";
}
item {
label: 'test item shorthand 3';
action: 'app.test-action';
icon: 'test-symbolic';
label: "test item shorthand 3";
action: "app.test-action";
icon: "test-symbolic";
}
}
}

View file

@ -0,0 +1,5 @@
using Gtk 4.0;
Label {
label: "Hello,\nworld!";
}

View file

@ -0,0 +1,8 @@
using Gtk 4.0;
Label {
xalign: 0.5;
yalign: 0;
height-request: 1000000;
margin-top: 48;
}

View file

@ -1,6 +1,6 @@
using Gtk 4.0;
Gtk.Shortcut {
Shortcut {
trigger: "Escape";
}
@ -10,4 +10,4 @@ Picture {
ColorButton {
rgba: "rgb(0, 0, 0)";
}
}

View file

@ -3,7 +3,7 @@ using Gtk 4.0;
Scale {
marks [
mark (-1, bottom),
mark (0, top, _('Hello, world!')),
mark (0, top, _("Hello, world!")),
mark (2),
]
}

View file

@ -2,8 +2,13 @@ using Gtk 4.0;
SizeGroup {
mode: horizontal;
widgets [label, button]
widgets [
label,
button,
]
}
Label label {}
Button button {}

View file

@ -1,6 +1,10 @@
using Gtk 4.0;
AboutDialog about {
valign: center;
authors: ["Jane doe <jane-doe@email.com>", "Jhonny D <jd@email.com>"];
valign: center;
authors: [
"Jane doe <jane-doe@email.com>",
"Jhonny D <jd@email.com>"
];
}

View file

@ -7,6 +7,6 @@ StringList greetings {
]
}
Gtk.DropDown {
DropDown {
model: greetings;
}

View file

@ -1,5 +1,5 @@
using Gtk 4.0;
Label {
label: '\\\\\'Test 1 2 3\n & 4 "5\' 6 \t';
label: "\\\\'Test 1 2 3\n & 4 \"5' 6 \t";
}

View file

@ -1,5 +1,8 @@
using Gtk 4.0;
Label {
styles ["class-1", "class-2"]
styles [
"class-1",
"class-2"
]
}

View file

@ -6,4 +6,4 @@ Gtk.BuilderListItemFactory {
}
}
Gtk.Label label {}
Gtk.Label label {}

View file

@ -1,10 +1,10 @@
using Gtk 4.0;
template $TestTemplate: ApplicationWindow {
test-property: 'Hello, world';
test-property: "Hello, world";
test-signal => $on_test_signal();
}
Dialog {
transient-for: template;
}
}

View file

@ -2,4 +2,4 @@ using Gtk 4.0;
template $MyTemplate {
object: bind template.object2;
}
}

View file

@ -1,5 +1,5 @@
using Gtk 4.0;
template $MyTemplate : Box {
template $MyTemplate: Box {
prop1: bind template.prop2 as <$MyObject>.prop3;
}
}

View file

@ -1,5 +1,5 @@
using Gtk 4.0;
template $MyTemplate : $MyParentClass {
template $MyTemplate: $MyParentClass {
prop1: bind template.prop2 as <$MyObject>.prop3;
}
}

View file

@ -1,6 +1,6 @@
using Gtk 4.0;
template $MyTemplate : Gtk.Button {
template $MyTemplate: Gtk.Button {
/* this makes no actual sense, but it tests what I want to test */
child: template;
}
}

View file

@ -0,0 +1,5 @@
using Gtk 4.0;
template $MyTemplate: Button {
child: template;
}

View file

@ -1,3 +1,3 @@
using Gtk 4.0;
template Gtk.ListItem {}
template ListItem {}

View file

@ -1,5 +1,5 @@
using Gtk 4.0;
template $TestTemplate {
test-property: 'Hello, world';
test-property: "Hello, world";
}

View file

@ -2,4 +2,4 @@ using Gtk 4.0;
template $MyTemplate {
prop: bind template.prop2;
}
}

View file

@ -1,9 +1,9 @@
using Gtk 4.0;
Label {
label: _('Hello, world!');
label: _("Hello, world!");
}
Label {
label: C_('translation context', 'Hello');
label: C_("translation context", "Hello");
}

View file

@ -1,3 +1,2 @@
using Gtk 4.0;
translation-domain "blueprint-tests";

View file

@ -8,4 +8,4 @@ Gio.ListStore {
Gio.ListStore {
item-type: typeof<$MyObject>;
}
}

View file

@ -2,6 +2,6 @@ using Gtk 4.0;
$MyComponent component {
$MyComponent2 {
flags-value: 'a|b';
flags-value: "a|b";
}
}

View file

@ -18,6 +18,7 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
import os
import unittest
from pathlib import Path
@ -36,7 +37,6 @@ from blueprintcompiler.errors import (
)
from blueprintcompiler.lsp import LanguageServer
from blueprintcompiler.outputs.xml import XmlOutput
from blueprintcompiler.tokenizer import Token, TokenType, tokenize
class TestSamples(unittest.TestCase):
@ -150,17 +150,27 @@ class TestSamples(unittest.TestCase):
def assert_decompile(self, name):
print(f'assert_decompile("{name}")')
try:
with open((Path(__file__).parent / f"samples/{name}.blp").resolve()) as f:
expected = f.read()
if os.path.exists(
(Path(__file__).parent / f"samples/{name}_dec.blp").resolve()
):
with open(
(Path(__file__).parent / f"samples/{name}_dec.blp").resolve()
) as f:
expected = f.read().strip()
else:
with open(
(Path(__file__).parent / f"samples/{name}.blp").resolve()
) as f:
expected = f.read().strip()
name = name.removesuffix("_dec")
ui_path = (Path(__file__).parent / f"samples/{name}.ui").resolve()
actual = decompiler.decompile(ui_path)
actual = decompiler.decompile(ui_path).strip()
self.assertEqual(actual.strip(), expected.strip())
self.assertEqual(actual, expected)
except PrintableError as e: # pragma: no cover
e.pretty_print(name + ".blp", blueprint)
e.pretty_print(name + ".blp", expected)
raise AssertionError()
def test_samples(self):
@ -173,7 +183,10 @@ class TestSamples(unittest.TestCase):
samples.sort()
for sample in samples:
REQUIRE_ADW_1_4 = ["adw_breakpoint"]
REQUIRE_ADW_1_5 = ["adw_alertdialog_responses"]
REQUIRE_ADW_1_5 = [
"adw_alertdialog_responses",
"adw_alert_dialog_duplicate_flags",
]
SKIP_RUN = [
"adw_breakpoint_template",
@ -193,6 +206,23 @@ class TestSamples(unittest.TestCase):
"unchecked_class",
]
SKIP_DECOMPILE = [
# Not implemented yet
"action_widgets",
# Not implemented yet
"adw_breakpoint",
# Not implemented yet
"adw_breakpoint_template",
# Not implemented yet
"gtkcolumnview",
# Comments are not preserved in either direction
"comments",
# Not implemented yet
"list_factory",
# Not implemented yet
"subscope",
]
if sample in REQUIRE_ADW_1_4 and not self.have_adw_1_4:
continue
if sample in REQUIRE_ADW_1_5 and not self.have_adw_1_5:
@ -201,15 +231,16 @@ class TestSamples(unittest.TestCase):
with self.subTest(sample):
self.assert_sample(sample, skip_run=sample in SKIP_RUN)
with self.subTest("decompile/" + sample):
if sample not in SKIP_DECOMPILE:
self.assert_decompile(sample)
# list the sample_errors directory
sample_errors = [
f.stem for f in Path(__file__).parent.glob("sample_errors/*.blp")
]
sample_errors.sort()
for sample_error in sample_errors:
REQUIRE_ADW_1_4 = ["adw_breakpoint"]
REQUIRE_ADW_1_5 = ["adw_alert_dialog_duplicate_flags"]
if sample_error in REQUIRE_ADW_1_4 and not self.have_adw_1_4:
continue
if sample_error in REQUIRE_ADW_1_5 and not self.have_adw_1_5:
@ -217,27 +248,3 @@ class TestSamples(unittest.TestCase):
with self.subTest(sample_error):
self.assert_sample_error(sample_error)
def test_decompiler(self):
self.assert_decompile("accessibility_dec")
if self.have_adw_1_5:
self.assert_decompile("adw_alertdialog_responses")
self.assert_decompile("adw_messagedialog_responses")
self.assert_decompile("child_type")
self.assert_decompile("file_filter")
self.assert_decompile("flags")
self.assert_decompile("id_prop")
self.assert_decompile("layout_dec")
self.assert_decompile("menu_dec")
self.assert_decompile("property")
self.assert_decompile("property_binding_dec")
self.assert_decompile("placeholder_dec")
self.assert_decompile("scale_marks")
self.assert_decompile("signal")
self.assert_decompile("strings_dec")
self.assert_decompile("style_dec")
self.assert_decompile("template")
self.assert_decompile("template_orphan")
self.assert_decompile("translated")
self.assert_decompile("using")
self.assert_decompile("unchecked_class_dec")