Separate output into its own module

This commit is contained in:
James Westman 2022-10-14 21:04:37 -05:00
parent 8cf793023d
commit a24f16109f
No known key found for this signature in database
GPG key ID: CE2DBA0ADB654EA6
33 changed files with 407 additions and 291 deletions

View file

@ -18,7 +18,7 @@ build:
- ninja -C _build docs/en
- git clone https://gitlab.gnome.org/jwestman/blueprint-regression-tests.git
- cd blueprint-regression-tests
- git checkout d14b95b6c1fc0cddd4b0ad21d224b05edee2d01f
- git checkout 94613f275efc810610768d5ee8b2aec28392c3e8
- ./test.sh
- cd ..
coverage: '/TOTAL.*\s([.\d]+)%/'

View file

@ -23,7 +23,6 @@ import typing as T
from .errors import *
from .lsp_utils import SemanticToken
from .xml_emitter import XmlEmitter
class Children:
@ -96,16 +95,6 @@ class AstNode:
if isinstance(item, attr_type):
yield name, item
def generate(self) -> str:
""" Generates an XML string from the node. """
xml = XmlEmitter()
self.emit_xml(xml)
return xml.result
def emit_xml(self, xml: XmlEmitter):
""" Emits the XML representation of this AST node to the XmlEmitter. """
raise NotImplementedError()
def get_docs(self, idx: int) -> T.Optional[str]:
for name, attr in self._attrs_by_type(Docs):
if attr.token_name:

View file

@ -28,11 +28,11 @@ from gi.repository import GIRepository # type: ignore
from .errors import CompileError, CompilerBugError
from . import typelib, xml_reader
_namespace_cache = {}
_namespace_cache: T.Dict[str, "Namespace"] = {}
_xml_cache = {}
def get_namespace(namespace, version):
def get_namespace(namespace, version) -> "Namespace":
search_paths = GIRepository.Repository.get_search_path()
filename = f"{namespace}-{version}.typelib"
@ -518,11 +518,11 @@ class Namespace(GirNode):
return get_xml(self.name, self.version).get_elements("namespace")[0]
@cached_property
def name(self):
def name(self) -> str:
return self.tl.HEADER_NAMESPACE
@cached_property
def version(self):
def version(self) -> str:
return self.tl.HEADER_NSVERSION
@property

View file

@ -23,6 +23,7 @@ import difflib
import os
from . import decompiler, tokenizer, parser
from .outputs.xml import XmlOutput
from .errors import MultipleErrors, PrintableError
from .utils import Colors
@ -57,7 +58,8 @@ def decompile_file(in_file, out_file) -> T.Union[str, CouldNotPort]:
if len(ast.errors):
raise MultipleErrors(ast.errors)
ast.generate()
output = XmlOutput()
output.emit(ast)
except PrintableError as e:
e.pretty_print(out_file, decompiled)

View file

@ -1,16 +1,13 @@
""" Contains all the syntax beyond basic objects, properties, signal, and
templates. """
from .attributes import BaseAttribute, BaseTypedAttribute
from .expression import Expr
from .expression import IdentExpr, LookupOp, Expr
from .gobject_object import Object, ObjectContent
from .gobject_property import Property
from .gobject_signal import Signal
from .gtk_a11y import A11y
from .gtk_combo_box_text import Items
from .gtk_file_filter import mime_types, patterns, suffixes
from .gtk_file_filter import mime_types, patterns, suffixes, Filters
from .gtk_layout import Layout
from .gtk_menu import menu
from .gtk_menu import menu, Menu, MenuAttribute
from .gtk_size_group import Widgets
from .gtk_string_list import Strings
from .gtk_styles import Styles
@ -18,7 +15,8 @@ from .gtkbuilder_child import Child
from .gtkbuilder_template import Template
from .imports import GtkDirective, Import
from .ui import UI
from .values import TypeValue, IdentValue, TranslatedStringValue, FlagsValue, QuotedValue, NumberValue
from .types import ClassName
from .values import TypeValue, IdentValue, TranslatedStringValue, FlagsValue, Flag, QuotedValue, NumberValue, Value
from .common import *

View file

@ -32,17 +32,6 @@ class BaseAttribute(AstNode):
def name(self):
return self.tokens["name"]
def emit_xml(self, xml: XmlEmitter):
value = self.children[Value][0]
attrs = { self.attr_name: self.name }
if isinstance(value, TranslatedStringValue):
attrs = { **attrs, **value.attrs }
xml.start_tag(self.tag_name, **attrs)
value.emit_xml(xml)
xml.end_tag()
class BaseTypedAttribute(BaseAttribute):
""" A BaseAttribute whose parent has a value_type property that can assist

View file

@ -27,7 +27,6 @@ from ..decompiler import DecompileCtx, decompiler
from ..gir import StringType, BoolType, IntType, FloatType, GirType, Enumeration
from ..lsp_utils import Completion, CompletionItemKind, SemanticToken, SemanticTokenType
from ..parse_tree import *
from ..xml_emitter import XmlEmitter
OBJECT_CONTENT_HOOKS = AnyOf()

View file

@ -27,9 +27,6 @@ expr = Pratt()
class Expr(AstNode):
grammar = expr
def emit_xml(self, xml: XmlEmitter):
self.children[-1].emit_xml(xml)
class InfixExpr(AstNode):
@property
@ -41,19 +38,17 @@ class InfixExpr(AstNode):
class IdentExpr(AstNode):
grammar = UseIdent("ident")
def emit_xml(self, xml: XmlEmitter):
xml.start_tag("constant")
xml.put_text(self.tokens["ident"])
xml.end_tag()
@property
def ident(self) -> str:
return self.tokens["ident"]
class LookupOp(InfixExpr):
grammar = [".", UseIdent("property")]
def emit_xml(self, xml: XmlEmitter):
xml.start_tag("lookup", name=self.tokens["property"])
self.lhs.emit_xml(xml)
xml.end_tag()
@property
def property_name(self) -> str:
return self.tokens["property"]
expr.children = [

View file

@ -33,10 +33,6 @@ class ObjectContent(AstNode):
def gir_class(self):
return self.parent.gir_class
def emit_xml(self, xml: XmlEmitter):
for x in self.children:
x.emit_xml(xml)
class Object(AstNode):
grammar: T.Any = [
ConcreteClassName,
@ -44,9 +40,21 @@ class Object(AstNode):
ObjectContent,
]
@property
def id(self) -> str:
return self.tokens["id"]
@property
def class_name(self) -> ClassName | None:
return self.children[ClassName][0]
@property
def content(self) -> ObjectContent:
return self.children[ObjectContent][0]
@property
def gir_class(self):
return self.children[ClassName][0].gir_type
return self.class_name.gir_type
@cached_property
def action_widgets(self) -> T.List[ResponseId]:
@ -62,28 +70,6 @@ class Object(AstNode):
if child.response_id
]
def emit_start_tag(self, xml: XmlEmitter):
xml.start_tag("object", **{
"class": self.children[ClassName][0].glib_type_name,
"id": self.tokens["id"],
})
def emit_xml(self, xml: XmlEmitter):
self.emit_start_tag(xml)
for child in self.children:
child.emit_xml(xml)
# List action widgets
action_widgets = self.action_widgets
if action_widgets:
xml.start_tag("action-widgets")
for action_widget in action_widgets:
action_widget.emit_action_widget(xml)
xml.end_tag()
xml.end_tag()
def validate_parent_type(node, ns: str, name: str, err_msg: str):
parent = node.root.gir.get_type(name, ns)

View file

@ -133,44 +133,3 @@ class Property(AstNode):
def property_docs(self):
if self.gir_property is not None:
return self.gir_property.doc
def emit_xml(self, xml: XmlEmitter):
values = self.children[Value]
value = values[0] if len(values) == 1 else None
bind_flags = []
if self.tokens["bind_source"] and not self.tokens["no_sync_create"]:
bind_flags.append("sync-create")
if self.tokens["inverted"]:
bind_flags.append("invert-boolean")
if self.tokens["bidirectional"]:
bind_flags.append("bidirectional")
bind_flags_str = "|".join(bind_flags) or None
props = {
"name": self.tokens["name"],
"bind-source": self.tokens["bind_source"],
"bind-property": self.tokens["bind_property"],
"bind-flags": bind_flags_str,
}
if isinstance(value, TranslatedStringValue):
props = { **props, **value.attrs }
if len(self.children[Object]) == 1:
xml.start_tag("property", **props)
self.children[Object][0].emit_xml(xml)
xml.end_tag()
elif value is None:
if self.tokens["binding"]:
xml.start_tag("binding", **props)
for x in self.children:
x.emit_xml(xml)
xml.end_tag()
else:
xml.put_self_closing("property", **props);
else:
xml.start_tag("property", **props)
value.emit_xml(xml)
xml.end_tag()

View file

@ -40,6 +40,30 @@ class Signal(AstNode):
)),
)
@property
def name(self) -> str:
return self.tokens["name"]
@property
def detail_name(self) -> str | None:
return self.tokens["detail_name"]
@property
def handler(self) -> str:
return self.tokens["handler"]
@property
def object_id(self) -> str | None:
return self.tokens["object"]
@property
def is_swapped(self) -> bool:
return self.tokens["swapped"] or False
@property
def is_after(self) -> bool:
return self.tokens["after"] or False
@property
def gir_signal(self):
@ -89,19 +113,6 @@ class Signal(AstNode):
return self.gir_signal.doc
def emit_xml(self, xml: XmlEmitter):
name = self.tokens["name"]
if self.tokens["detail_name"]:
name += "::" + self.tokens["detail_name"]
xml.put_self_closing(
"signal",
name=name,
handler=self.tokens["handler"],
swapped="true" if self.tokens["swapped"] else None,
object=self.tokens["object"]
)
@decompiler("signal")
def decompile_signal(ctx, gir, name, handler, swapped="false", object=None):
object_name = object or ""

View file

@ -167,12 +167,6 @@ class A11y(AstNode):
def unique_in_parent(self):
self.validate_unique_in_parent("Duplicate accessibility block")
def emit_xml(self, xml: XmlEmitter):
xml.start_tag("accessibility")
for child in self.children:
child.emit_xml(xml)
xml.end_tag()
@completer(
applies_in=[ObjectContent],

View file

@ -60,12 +60,6 @@ class Items(AstNode):
def unique_in_parent(self):
self.validate_unique_in_parent("Duplicate items block")
def emit_xml(self, xml: XmlEmitter):
xml.start_tag("items")
for child in self.children:
child.emit_xml(xml)
xml.end_tag()
@completer(
applies_in=[ObjectContent],

View file

@ -39,18 +39,9 @@ class Filters(AstNode):
)
wrapped_validator(self)
def emit_xml(self, xml: XmlEmitter):
xml.start_tag(self.tokens["tag_name"])
for child in self.children:
child.emit_xml(xml)
xml.end_tag()
class FilterString(AstNode):
def emit_xml(self, xml):
xml.start_tag(self.tokens["tag_name"])
xml.put_text(self.tokens["name"])
xml.end_tag()
pass
def create_node(tag_name: str, singular: str):

View file

@ -64,12 +64,6 @@ class Layout(AstNode):
def unique_in_parent(self):
self.validate_unique_in_parent("Duplicate layout block")
def emit_xml(self, xml: XmlEmitter):
xml.start_tag("layout")
for child in self.children:
child.emit_xml(xml)
xml.end_tag()
@completer(
applies_in=[ObjectContent],

View file

@ -19,22 +19,26 @@
import typing as T
from blueprintcompiler.language.values import Value
from .attributes import BaseAttribute
from .gobject_object import Object, ObjectContent
from .common import *
class Menu(Object):
def emit_xml(self, xml: XmlEmitter):
xml.start_tag(self.tokens["tag"], id=self.tokens["id"])
for child in self.children:
child.emit_xml(xml)
xml.end_tag()
class Menu(AstNode):
@property
def gir_class(self):
return self.root.gir.namespaces["Gtk"].lookup_type("Gio.Menu")
@property
def id(self) -> str:
return self.tokens["id"]
@property
def tag(self) -> str:
return self.tokens["tag"]
class MenuAttribute(BaseAttribute):
tag_name = "attribute"
@ -43,6 +47,10 @@ class MenuAttribute(BaseAttribute):
def value_type(self):
return None
@property
def value(self) -> Value:
return self.children[Value][0]
menu_contents = Sequence()

View file

@ -39,9 +39,6 @@ class Widget(AstNode):
f"Cannot assign {object.gir_class.full_name} to {type.full_name}"
)
def emit_xml(self, xml: XmlEmitter):
xml.put_self_closing("widget", name=self.tokens["name"])
class Widgets(AstNode):
grammar = [
@ -59,12 +56,6 @@ class Widgets(AstNode):
def unique_in_parent(self):
self.validate_unique_in_parent("Duplicate widgets block")
def emit_xml(self, xml: XmlEmitter):
xml.start_tag("widgets")
for child in self.children:
child.emit_xml(xml)
xml.end_tag()
@completer(
applies_in=[ObjectContent],

View file

@ -31,13 +31,6 @@ class Item(AstNode):
def value_type(self):
return StringType()
def emit_xml(self, xml: XmlEmitter):
value = self.children[Value][0]
attrs = value.attrs if isinstance(value, TranslatedStringValue) else {}
xml.start_tag("item", **attrs)
value.emit_xml(xml)
xml.end_tag()
class Strings(AstNode):
grammar = [
@ -55,12 +48,6 @@ class Strings(AstNode):
def unique_in_parent(self):
self.validate_unique_in_parent("Duplicate strings block")
def emit_xml(self, xml: XmlEmitter):
xml.start_tag("items")
for child in self.children:
child.emit_xml(xml)
xml.end_tag()
@completer(
applies_in=[ObjectContent],

View file

@ -25,9 +25,6 @@ from .common import *
class StyleClass(AstNode):
grammar = UseQuoted("name")
def emit_xml(self, xml):
xml.put_self_closing("class", name=self.tokens["name"])
class Styles(AstNode):
grammar = [
@ -45,12 +42,6 @@ class Styles(AstNode):
def unique_in_parent(self):
self.validate_unique_in_parent("Duplicate styles block")
def emit_xml(self, xml: XmlEmitter):
xml.start_tag("style")
for child in self.children:
child.emit_xml(xml)
xml.end_tag()
@completer(
applies_in=[ObjectContent],

View file

@ -41,6 +41,10 @@ class Child(AstNode):
Object,
]
@property
def object(self) -> Object:
return self.children[Object][0]
@validate()
def parent_can_have_child(self):
if gir_class := self.parent.gir_class:
@ -70,17 +74,6 @@ class Child(AstNode):
else:
return None
def emit_xml(self, xml: XmlEmitter):
child_type = internal_child = None
if self.tokens["internal_child"]:
internal_child = self.tokens["child_type"]
else:
child_type = self.tokens["child_type"]
xml.start_tag("child", type=child_type, internal_child=internal_child)
for child in self.children:
child.emit_xml(xml)
xml.end_tag()
@decompiler("child")
def decompile_child(ctx, gir, type=None, internal_child=None):

View file

@ -34,28 +34,27 @@ class Template(Object):
ObjectContent,
]
@property
def id(self) -> str:
return self.tokens["id"]
@property
def class_name(self) -> ClassName | None:
if len(self.children[ClassName]):
return self.children[ClassName][0]
else:
return None
@property
def gir_class(self):
# Templates might not have a parent class defined
if len(self.children[ClassName]):
return self.children[ClassName][0].gir_type
if class_name := self.class_name:
return class_name.gir_type
@validate("id")
def unique_in_parent(self):
self.validate_unique_in_parent(f"Only one template may be defined per file, but this file contains {len(self.parent.children[Template])}",)
def emit_start_tag(self, xml: XmlEmitter):
if len(self.children[ClassName]):
parent = self.children[ClassName][0].glib_type_name
else:
parent = None
xml.start_tag(
"template",
**{"class": self.tokens["id"]},
parent=parent
)
@decompiler("template")
def decompile_template(ctx: DecompileCtx, gir, klass, parent="Widget"):

View file

@ -61,10 +61,6 @@ class GtkDirective(AstNode):
return gir.get_namespace("Gtk", self.tokens["version"])
def emit_xml(self, xml: XmlEmitter):
xml.put_self_closing("requires", lib="gtk", version=self.tokens["version"])
class Import(AstNode):
grammar = Statement(
"using",
@ -82,6 +78,3 @@ class Import(AstNode):
return gir.get_namespace(self.tokens["namespace"], self.tokens["version"])
except CompileError:
return None
def emit_xml(self, xml):
pass

View file

@ -120,6 +120,14 @@ class ResponseId(AstNode):
if widget.tokens["is_default"]:
raise CompileError("Default response is already set")
@property
def response_id(self) -> str:
return self.tokens["response_id"]
@property
def is_default(self) -> bool:
return self.tokens["is_default"] or False
@property
def widget_id(self) -> str:
"""Get action widget ID."""
@ -128,25 +136,3 @@ class ResponseId(AstNode):
_object: Object = self.parent.children[Object][0]
return _object.tokens["id"]
def emit_xml(self, xml: XmlEmitter) -> None:
"""Emit nothing.
Response ID don't have to emit any XML in place,
but have to emit action-widget tag in separate
place (see `ResponseId.emit_action_widget`)
"""
def emit_action_widget(self, xml: XmlEmitter) -> None:
"""Emit action-widget XML.
Must be called while <action-widgets> tag is open.
For more details see `GtkDialog` and `GtkInfoBar` docs.
"""
xml.start_tag(
"action-widget",
response=self.tokens["response_id"],
default=self.tokens["is_default"]
)
xml.put_text(self.widget_id)
xml.end_tag()

View file

@ -76,9 +76,6 @@ class TypeName(AstNode):
if self.gir_type:
return self.gir_type.doc
def emit_xml(self, xml: XmlEmitter):
pass
class ClassName(TypeName):
@validate("namespace", "class_name")

View file

@ -86,10 +86,3 @@ class UI(AstNode):
token = obj.group.tokens["id"]
raise CompileError(f"Duplicate object ID '{obj.tokens['id']}'", token.start, token.end)
passed[obj.tokens["id"]] = obj
def emit_xml(self, xml: XmlEmitter):
xml.start_tag("interface")
for x in self.children:
x.emit_xml(xml)
xml.end_tag()

View file

@ -46,14 +46,12 @@ class TranslatedStringValue(Value):
)
@property
def attrs(self):
attrs = { "translatable": "true" }
if "context" in self.tokens:
attrs["context"] = self.tokens["context"]
return attrs
def string(self) -> str:
return self.tokens["value"]
def emit_xml(self, xml: XmlEmitter):
xml.put_text(self.tokens["value"])
@property
def context(self) -> str | None:
return self.tokens["context"]
class TypeValue(Value):
@ -68,9 +66,6 @@ class TypeValue(Value):
def type_name(self):
return self.children[TypeName][0]
def emit_xml(self, xml: XmlEmitter):
xml.put_text(self.type_name.glib_type_name)
@validate()
def validate_for_type(self):
type = self.parent.value_type
@ -81,8 +76,9 @@ class TypeValue(Value):
class QuotedValue(Value):
grammar = UseQuoted("value")
def emit_xml(self, xml: XmlEmitter):
xml.put_text(self.tokens["value"])
@property
def value(self) -> str:
return self.tokens["value"]
@validate()
def validate_for_type(self):
@ -119,8 +115,9 @@ class QuotedValue(Value):
class NumberValue(Value):
grammar = UseNumber("value")
def emit_xml(self, xml: XmlEmitter):
xml.put_text(self.tokens["value"])
@property
def value(self) -> int | float:
return self.tokens["value"]
@validate()
def validate_for_type(self):
@ -179,19 +176,10 @@ class FlagsValue(Value):
if type is not None and not isinstance(type, gir.Bitfield):
raise CompileError(f"{type.full_name} is not a bitfield type")
def emit_xml(self, xml: XmlEmitter):
xml.put_text("|".join([flag.tokens["value"] for flag in self.children[Flag]]))
class IdentValue(Value):
grammar = UseIdent("value")
def emit_xml(self, xml: XmlEmitter):
if isinstance(self.parent.value_type, gir.Enumeration):
xml.put_text(self.parent.value_type.members[self.tokens["value"]].nick)
else:
xml.put_text(self.tokens["value"])
@validate()
def validate_for_type(self):
type = self.parent.value_type

View file

@ -25,7 +25,7 @@ from .errors import PrintableError, report_bug, MultipleErrors
from .lsp import LanguageServer
from . import parser, tokenizer, decompiler, interactive_port
from .utils import Colors
from .xml_emitter import XmlEmitter
from .outputs import XmlOutput
VERSION = "uninstalled"
LIBDIR = None
@ -141,7 +141,9 @@ class BlueprintApp:
if len(ast.errors):
raise MultipleErrors(ast.errors)
return ast.generate(), warnings
formatter = XmlOutput()
return formatter.emit(ast), warnings
def main(version, libdir):

View file

@ -0,0 +1,7 @@
from ..language import UI
class OutputFormat:
def emit(self, ui: UI) -> str:
raise NotImplementedError()
from .xml import XmlOutput

View file

@ -0,0 +1,272 @@
from .. import OutputFormat
from ...language import *
from .xml_emitter import XmlEmitter
class XmlOutput(OutputFormat):
def emit(self, ui: UI) -> str:
xml = XmlEmitter()
self._emit_ui(ui, xml)
return xml.result
def _emit_ui(self, ui: UI, xml: XmlEmitter):
xml.start_tag("interface")
for x in ui.children:
if isinstance(x, GtkDirective):
self._emit_gtk_directive(x, xml)
elif isinstance(x, Import):
pass
elif isinstance(x, Template):
self._emit_template(x, xml)
elif isinstance(x, Object):
self._emit_object(x, xml)
elif isinstance(x, Menu):
self._emit_menu(x, xml)
else:
raise CompilerBugError()
xml.end_tag()
def _emit_gtk_directive(self, gtk: GtkDirective, xml: XmlEmitter):
xml.put_self_closing("requires", lib="gtk", version=gtk.gir_namespace.version)
def _emit_template(self, template: Template, xml: XmlEmitter):
xml.start_tag("template", **{"class": template.id}, parent=template.class_name)
self._emit_object_or_template(template, xml)
xml.end_tag()
def _emit_object(self, obj: Object, xml: XmlEmitter):
xml.start_tag(
"object",
**{"class": obj.class_name},
id=obj.id,
)
self._emit_object_or_template(obj, xml)
xml.end_tag()
def _emit_object_or_template(self, obj: Object | Template, xml: XmlEmitter):
for child in obj.content.children:
if isinstance(child, Property):
self._emit_property(child, xml)
elif isinstance(child, Signal):
self._emit_signal(child, xml)
elif isinstance(child, Child):
self._emit_child(child, xml)
else:
self._emit_extensions(child, xml)
# List action widgets
action_widgets = obj.action_widgets
if action_widgets:
xml.start_tag("action-widgets")
for action_widget in action_widgets:
xml.start_tag(
"action-widget",
response=action_widget.response_id,
default=action_widget.is_default or None,
)
xml.put_text(action_widget.widget_id)
xml.end_tag()
xml.end_tag()
def _emit_menu(self, menu: Menu, xml: XmlEmitter):
xml.start_tag(menu.tag, id=menu.id)
for child in menu.children:
if isinstance(child, Menu):
self._emit_menu(child, xml)
elif isinstance(child, MenuAttribute):
self._emit_attribute("attribute", "name", child.name, child.value, xml)
else:
raise CompilerBugError()
xml.end_tag()
def _emit_property(self, property: Property, xml: XmlEmitter):
values = property.children[Value]
value = values[0] if len(values) == 1 else None
bind_flags = []
if property.tokens["bind_source"] and not property.tokens["no_sync_create"]:
bind_flags.append("sync-create")
if property.tokens["inverted"]:
bind_flags.append("invert-boolean")
if property.tokens["bidirectional"]:
bind_flags.append("bidirectional")
bind_flags_str = "|".join(bind_flags) or None
props = {
"name": property.tokens["name"],
"bind-source": property.tokens["bind_source"],
"bind-property": property.tokens["bind_property"],
"bind-flags": bind_flags_str,
}
if isinstance(value, TranslatedStringValue):
xml.start_tag("property", **props, **self._translated_string_attrs(value))
xml.put_text(value.string)
xml.end_tag()
elif len(property.children[Object]) == 1:
xml.start_tag("property", **props)
self._emit_object(property.children[Object][0], xml)
xml.end_tag()
elif value is None:
if property.tokens["binding"]:
xml.start_tag("binding", **props)
self._emit_expression(property.children[Expr][0], xml)
xml.end_tag()
else:
xml.put_self_closing("property", **props)
else:
xml.start_tag("property", **props)
self._emit_value(value, xml)
xml.end_tag()
def _translated_string_attrs(
self, translated: TranslatedStringValue
) -> T.Dict[str, str | None]:
return {
"translatable": "true",
"context": translated.context,
}
def _emit_signal(self, signal: Signal, xml: XmlEmitter):
name = signal.name
if signal.detail_name:
name += "::" + signal.detail_name
xml.put_self_closing(
"signal",
name=name,
handler=signal.handler,
swapped=signal.is_swapped or None,
object=signal.object_id,
)
def _emit_child(self, child: Child, xml: XmlEmitter):
child_type = internal_child = None
if child.tokens["internal_child"]:
internal_child = child.tokens["child_type"]
else:
child_type = child.tokens["child_type"]
xml.start_tag("child", type=child_type, internal_child=internal_child)
self._emit_object(child.object, xml)
xml.end_tag()
def _emit_value(self, value: Value, xml: XmlEmitter):
if isinstance(value, IdentValue):
if isinstance(value.parent.value_type, gir.Enumeration):
xml.put_text(
value.parent.value_type.members[value.tokens["value"]].nick
)
else:
xml.put_text(value.tokens["value"])
elif isinstance(value, QuotedValue) or isinstance(value, NumberValue):
xml.put_text(value.value)
elif isinstance(value, FlagsValue):
xml.put_text("|".join([flag.tokens["value"] for flag in value.children]))
elif isinstance(value, TranslatedStringValue):
raise CompilerBugError("translated values must be handled in the parent")
elif isinstance(value, TypeValue):
xml.put_text(value.type_name.glib_type_name)
else:
raise CompilerBugError()
def _emit_expression(self, expression: Expr, xml: XmlEmitter):
self._emit_expression_part(expression.children[-1], xml)
def _emit_expression_part(self, expression, xml: XmlEmitter):
if isinstance(expression, IdentExpr):
self._emit_ident_expr(expression, xml)
elif isinstance(expression, LookupOp):
self._emit_lookup_op(expression, xml)
elif isinstance(expression, Expr):
self._emit_expression(expression, xml)
else:
raise CompilerBugError()
def _emit_ident_expr(self, expr: IdentExpr, xml: XmlEmitter):
xml.start_tag("constant")
xml.put_text(expr.ident)
xml.end_tag()
def _emit_lookup_op(self, expr: LookupOp, xml: XmlEmitter):
xml.start_tag("lookup", name=expr.property_name)
self._emit_expression_part(expr.lhs, xml)
xml.end_tag()
def _emit_attribute(
self, tag: str, attr: str, name: str, value: Value, xml: XmlEmitter
):
attrs = {attr: name}
if isinstance(value, TranslatedStringValue):
xml.start_tag(tag, **attrs, **self._translated_string_attrs(value))
xml.put_text(value.string)
xml.end_tag()
else:
xml.start_tag(tag, **attrs)
self._emit_value(value, xml)
xml.end_tag()
def _emit_extensions(self, extension, xml: XmlEmitter):
if isinstance(extension, A11y):
xml.start_tag("accessibility")
for child in extension.children:
self._emit_attribute(
child.tag_name, "name", child.name, child.children[Value][0], xml
)
xml.end_tag()
elif isinstance(extension, Filters):
xml.start_tag(extension.tokens["tag_name"])
for child in extension.children:
xml.start_tag(child.tokens["tag_name"])
xml.put_text(child.tokens["name"])
xml.end_tag()
xml.end_tag()
elif isinstance(extension, Items):
xml.start_tag("items")
for child in extension.children:
self._emit_attribute(
"item", "id", child.name, child.children[Value][0], xml
)
xml.end_tag()
elif isinstance(extension, Layout):
xml.start_tag("layout")
for child in extension.children:
self._emit_attribute(
"property", "name", child.name, child.children[Value][0], xml
)
xml.end_tag()
elif isinstance(extension, Strings):
xml.start_tag("items")
for child in extension.children:
value = child.children[Value][0]
if isinstance(value, TranslatedStringValue):
xml.start_tag("item", **self._translated_string_attrs(value))
xml.put_text(value.string)
xml.end_tag()
else:
xml.start_tag("item")
self._emit_value(value, xml)
xml.end_tag()
xml.end_tag()
elif isinstance(extension, Styles):
xml.start_tag("style")
for child in extension.children:
xml.put_self_closing("class", name=child.tokens["name"])
xml.end_tag()
elif isinstance(extension, Widgets):
xml.start_tag("widgets")
for child in extension.children:
xml.put_self_closing("widget", name=child.tokens["name"])
xml.end_tag()
else:
raise CompilerBugError()

View file

@ -17,9 +17,10 @@
#
# SPDX-License-Identifier: LGPL-3.0-or-later
from xml.sax import saxutils
from . import gir
from blueprintcompiler.gir import GirType
from blueprintcompiler.language.types import ClassName
class XmlEmitter:
@ -29,7 +30,7 @@ class XmlEmitter:
self._tag_stack = []
self._needs_newline = False
def start_tag(self, tag, **attrs):
def start_tag(self, tag, **attrs: str | GirType | ClassName | bool | None):
self._indent()
self.result += f"<{tag}"
for key, val in attrs.items():
@ -55,7 +56,7 @@ class XmlEmitter:
self.result += f"</{tag}>"
self._needs_newline = True
def put_text(self, text):
def put_text(self, text: str | int | float):
self.result += saxutils.escape(str(text))
self._needs_newline = False
@ -64,7 +65,9 @@ class XmlEmitter:
self.result += "\n" + " " * (self.indent * len(self._tag_stack))
def _to_string(self, val):
if isinstance(val, gir.GirType):
if isinstance(val, GirType):
return val.glib_type_name
elif isinstance(val, ClassName):
return val.glib_type_name
else:
return str(val)

View file

@ -1,6 +1,8 @@
import os, sys
from pythonfuzz.main import PythonFuzz
from blueprintcompiler.outputs.xml import XmlOutput
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
from blueprintcompiler import tokenizer, parser, decompiler, gir
@ -17,8 +19,9 @@ def fuzz(buf):
tokens = tokenizer.tokenize(blueprint)
ast, errors, warnings = parser.parse(tokens)
xml = XmlOutput()
if errors is None and len(ast.errors) == 0:
actual = ast.generate()
xml.emit(ast)
except CompilerBugError as e:
raise e
except PrintableError:

View file

@ -5,7 +5,7 @@
<signal name="activate" handler="click" object="button"/>
</object>
<object class="GtkButton" id="button">
<signal name="clicked" handler="on_button_clicked" swapped="true"/>
<signal name="clicked" handler="on_button_clicked" swapped="True"/>
<signal name="notify::visible" handler="on_button_notify_visible"/>
</object>
</interface>

View file

@ -28,6 +28,7 @@ from blueprintcompiler.completions import complete
from blueprintcompiler.errors import PrintableError, MultipleErrors, CompileError
from blueprintcompiler.tokenizer import Token, TokenType, tokenize
from blueprintcompiler import utils
from blueprintcompiler.outputs.xml import XmlOutput
class TestSamples(unittest.TestCase):
@ -56,7 +57,8 @@ class TestSamples(unittest.TestCase):
if len(warnings):
raise MultipleErrors(warnings)
actual = ast.generate()
xml = XmlOutput()
actual = xml.emit(ast)
if actual.strip() != expected.strip(): # pragma: no cover
diff = difflib.unified_diff(expected.splitlines(), actual.splitlines())
print("\n".join(diff))