mirror of
https://gitlab.gnome.org/jwestman/blueprint-compiler.git
synced 2025-05-04 15:59:08 -04:00
Separate output into its own module
This commit is contained in:
parent
8cf793023d
commit
a24f16109f
33 changed files with 407 additions and 291 deletions
|
@ -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]+)%/'
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 *
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 ""
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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"):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
7
blueprintcompiler/outputs/__init__.py
Normal file
7
blueprintcompiler/outputs/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
from ..language import UI
|
||||
|
||||
class OutputFormat:
|
||||
def emit(self, ui: UI) -> str:
|
||||
raise NotImplementedError()
|
||||
|
||||
from .xml import XmlOutput
|
272
blueprintcompiler/outputs/xml/__init__.py
Normal file
272
blueprintcompiler/outputs/xml/__init__.py
Normal 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()
|
|
@ -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)
|
|
@ -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:
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue