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
|
- ninja -C _build docs/en
|
||||||
- git clone https://gitlab.gnome.org/jwestman/blueprint-regression-tests.git
|
- git clone https://gitlab.gnome.org/jwestman/blueprint-regression-tests.git
|
||||||
- cd blueprint-regression-tests
|
- cd blueprint-regression-tests
|
||||||
- git checkout d14b95b6c1fc0cddd4b0ad21d224b05edee2d01f
|
- git checkout 94613f275efc810610768d5ee8b2aec28392c3e8
|
||||||
- ./test.sh
|
- ./test.sh
|
||||||
- cd ..
|
- cd ..
|
||||||
coverage: '/TOTAL.*\s([.\d]+)%/'
|
coverage: '/TOTAL.*\s([.\d]+)%/'
|
||||||
|
|
|
@ -23,7 +23,6 @@ import typing as T
|
||||||
|
|
||||||
from .errors import *
|
from .errors import *
|
||||||
from .lsp_utils import SemanticToken
|
from .lsp_utils import SemanticToken
|
||||||
from .xml_emitter import XmlEmitter
|
|
||||||
|
|
||||||
|
|
||||||
class Children:
|
class Children:
|
||||||
|
@ -96,16 +95,6 @@ class AstNode:
|
||||||
if isinstance(item, attr_type):
|
if isinstance(item, attr_type):
|
||||||
yield name, item
|
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]:
|
def get_docs(self, idx: int) -> T.Optional[str]:
|
||||||
for name, attr in self._attrs_by_type(Docs):
|
for name, attr in self._attrs_by_type(Docs):
|
||||||
if attr.token_name:
|
if attr.token_name:
|
||||||
|
|
|
@ -28,11 +28,11 @@ from gi.repository import GIRepository # type: ignore
|
||||||
from .errors import CompileError, CompilerBugError
|
from .errors import CompileError, CompilerBugError
|
||||||
from . import typelib, xml_reader
|
from . import typelib, xml_reader
|
||||||
|
|
||||||
_namespace_cache = {}
|
_namespace_cache: T.Dict[str, "Namespace"] = {}
|
||||||
_xml_cache = {}
|
_xml_cache = {}
|
||||||
|
|
||||||
|
|
||||||
def get_namespace(namespace, version):
|
def get_namespace(namespace, version) -> "Namespace":
|
||||||
search_paths = GIRepository.Repository.get_search_path()
|
search_paths = GIRepository.Repository.get_search_path()
|
||||||
|
|
||||||
filename = f"{namespace}-{version}.typelib"
|
filename = f"{namespace}-{version}.typelib"
|
||||||
|
@ -518,11 +518,11 @@ class Namespace(GirNode):
|
||||||
return get_xml(self.name, self.version).get_elements("namespace")[0]
|
return get_xml(self.name, self.version).get_elements("namespace")[0]
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def name(self):
|
def name(self) -> str:
|
||||||
return self.tl.HEADER_NAMESPACE
|
return self.tl.HEADER_NAMESPACE
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def version(self):
|
def version(self) -> str:
|
||||||
return self.tl.HEADER_NSVERSION
|
return self.tl.HEADER_NSVERSION
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -23,6 +23,7 @@ import difflib
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from . import decompiler, tokenizer, parser
|
from . import decompiler, tokenizer, parser
|
||||||
|
from .outputs.xml import XmlOutput
|
||||||
from .errors import MultipleErrors, PrintableError
|
from .errors import MultipleErrors, PrintableError
|
||||||
from .utils import Colors
|
from .utils import Colors
|
||||||
|
|
||||||
|
@ -57,7 +58,8 @@ def decompile_file(in_file, out_file) -> T.Union[str, CouldNotPort]:
|
||||||
if len(ast.errors):
|
if len(ast.errors):
|
||||||
raise MultipleErrors(ast.errors)
|
raise MultipleErrors(ast.errors)
|
||||||
|
|
||||||
ast.generate()
|
output = XmlOutput()
|
||||||
|
output.emit(ast)
|
||||||
except PrintableError as e:
|
except PrintableError as e:
|
||||||
e.pretty_print(out_file, decompiled)
|
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 .attributes import BaseAttribute, BaseTypedAttribute
|
||||||
from .expression import Expr
|
from .expression import IdentExpr, LookupOp, Expr
|
||||||
from .gobject_object import Object, ObjectContent
|
from .gobject_object import Object, ObjectContent
|
||||||
from .gobject_property import Property
|
from .gobject_property import Property
|
||||||
from .gobject_signal import Signal
|
from .gobject_signal import Signal
|
||||||
from .gtk_a11y import A11y
|
from .gtk_a11y import A11y
|
||||||
from .gtk_combo_box_text import Items
|
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_layout import Layout
|
||||||
from .gtk_menu import menu
|
from .gtk_menu import menu, Menu, MenuAttribute
|
||||||
from .gtk_size_group import Widgets
|
from .gtk_size_group import Widgets
|
||||||
from .gtk_string_list import Strings
|
from .gtk_string_list import Strings
|
||||||
from .gtk_styles import Styles
|
from .gtk_styles import Styles
|
||||||
|
@ -18,7 +15,8 @@ from .gtkbuilder_child import Child
|
||||||
from .gtkbuilder_template import Template
|
from .gtkbuilder_template import Template
|
||||||
from .imports import GtkDirective, Import
|
from .imports import GtkDirective, Import
|
||||||
from .ui import UI
|
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 *
|
from .common import *
|
||||||
|
|
||||||
|
|
|
@ -32,17 +32,6 @@ class BaseAttribute(AstNode):
|
||||||
def name(self):
|
def name(self):
|
||||||
return self.tokens["name"]
|
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):
|
class BaseTypedAttribute(BaseAttribute):
|
||||||
""" A BaseAttribute whose parent has a value_type property that can assist
|
""" 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 ..gir import StringType, BoolType, IntType, FloatType, GirType, Enumeration
|
||||||
from ..lsp_utils import Completion, CompletionItemKind, SemanticToken, SemanticTokenType
|
from ..lsp_utils import Completion, CompletionItemKind, SemanticToken, SemanticTokenType
|
||||||
from ..parse_tree import *
|
from ..parse_tree import *
|
||||||
from ..xml_emitter import XmlEmitter
|
|
||||||
|
|
||||||
|
|
||||||
OBJECT_CONTENT_HOOKS = AnyOf()
|
OBJECT_CONTENT_HOOKS = AnyOf()
|
||||||
|
|
|
@ -27,9 +27,6 @@ expr = Pratt()
|
||||||
class Expr(AstNode):
|
class Expr(AstNode):
|
||||||
grammar = expr
|
grammar = expr
|
||||||
|
|
||||||
def emit_xml(self, xml: XmlEmitter):
|
|
||||||
self.children[-1].emit_xml(xml)
|
|
||||||
|
|
||||||
|
|
||||||
class InfixExpr(AstNode):
|
class InfixExpr(AstNode):
|
||||||
@property
|
@property
|
||||||
|
@ -41,19 +38,17 @@ class InfixExpr(AstNode):
|
||||||
class IdentExpr(AstNode):
|
class IdentExpr(AstNode):
|
||||||
grammar = UseIdent("ident")
|
grammar = UseIdent("ident")
|
||||||
|
|
||||||
def emit_xml(self, xml: XmlEmitter):
|
@property
|
||||||
xml.start_tag("constant")
|
def ident(self) -> str:
|
||||||
xml.put_text(self.tokens["ident"])
|
return self.tokens["ident"]
|
||||||
xml.end_tag()
|
|
||||||
|
|
||||||
|
|
||||||
class LookupOp(InfixExpr):
|
class LookupOp(InfixExpr):
|
||||||
grammar = [".", UseIdent("property")]
|
grammar = [".", UseIdent("property")]
|
||||||
|
|
||||||
def emit_xml(self, xml: XmlEmitter):
|
@property
|
||||||
xml.start_tag("lookup", name=self.tokens["property"])
|
def property_name(self) -> str:
|
||||||
self.lhs.emit_xml(xml)
|
return self.tokens["property"]
|
||||||
xml.end_tag()
|
|
||||||
|
|
||||||
|
|
||||||
expr.children = [
|
expr.children = [
|
||||||
|
|
|
@ -33,10 +33,6 @@ class ObjectContent(AstNode):
|
||||||
def gir_class(self):
|
def gir_class(self):
|
||||||
return self.parent.gir_class
|
return self.parent.gir_class
|
||||||
|
|
||||||
def emit_xml(self, xml: XmlEmitter):
|
|
||||||
for x in self.children:
|
|
||||||
x.emit_xml(xml)
|
|
||||||
|
|
||||||
class Object(AstNode):
|
class Object(AstNode):
|
||||||
grammar: T.Any = [
|
grammar: T.Any = [
|
||||||
ConcreteClassName,
|
ConcreteClassName,
|
||||||
|
@ -44,9 +40,21 @@ class Object(AstNode):
|
||||||
ObjectContent,
|
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
|
@property
|
||||||
def gir_class(self):
|
def gir_class(self):
|
||||||
return self.children[ClassName][0].gir_type
|
return self.class_name.gir_type
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def action_widgets(self) -> T.List[ResponseId]:
|
def action_widgets(self) -> T.List[ResponseId]:
|
||||||
|
@ -62,28 +70,6 @@ class Object(AstNode):
|
||||||
if child.response_id
|
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):
|
def validate_parent_type(node, ns: str, name: str, err_msg: str):
|
||||||
parent = node.root.gir.get_type(name, ns)
|
parent = node.root.gir.get_type(name, ns)
|
||||||
|
|
|
@ -133,44 +133,3 @@ class Property(AstNode):
|
||||||
def property_docs(self):
|
def property_docs(self):
|
||||||
if self.gir_property is not None:
|
if self.gir_property is not None:
|
||||||
return self.gir_property.doc
|
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
|
@property
|
||||||
def gir_signal(self):
|
def gir_signal(self):
|
||||||
|
@ -89,19 +113,6 @@ class Signal(AstNode):
|
||||||
return self.gir_signal.doc
|
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")
|
@decompiler("signal")
|
||||||
def decompile_signal(ctx, gir, name, handler, swapped="false", object=None):
|
def decompile_signal(ctx, gir, name, handler, swapped="false", object=None):
|
||||||
object_name = object or ""
|
object_name = object or ""
|
||||||
|
|
|
@ -167,12 +167,6 @@ class A11y(AstNode):
|
||||||
def unique_in_parent(self):
|
def unique_in_parent(self):
|
||||||
self.validate_unique_in_parent("Duplicate accessibility block")
|
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(
|
@completer(
|
||||||
applies_in=[ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
|
|
|
@ -60,12 +60,6 @@ class Items(AstNode):
|
||||||
def unique_in_parent(self):
|
def unique_in_parent(self):
|
||||||
self.validate_unique_in_parent("Duplicate items block")
|
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(
|
@completer(
|
||||||
applies_in=[ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
|
|
|
@ -39,18 +39,9 @@ class Filters(AstNode):
|
||||||
)
|
)
|
||||||
wrapped_validator(self)
|
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):
|
class FilterString(AstNode):
|
||||||
def emit_xml(self, xml):
|
pass
|
||||||
xml.start_tag(self.tokens["tag_name"])
|
|
||||||
xml.put_text(self.tokens["name"])
|
|
||||||
xml.end_tag()
|
|
||||||
|
|
||||||
|
|
||||||
def create_node(tag_name: str, singular: str):
|
def create_node(tag_name: str, singular: str):
|
||||||
|
|
|
@ -64,12 +64,6 @@ class Layout(AstNode):
|
||||||
def unique_in_parent(self):
|
def unique_in_parent(self):
|
||||||
self.validate_unique_in_parent("Duplicate layout block")
|
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(
|
@completer(
|
||||||
applies_in=[ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
|
|
|
@ -19,22 +19,26 @@
|
||||||
|
|
||||||
import typing as T
|
import typing as T
|
||||||
|
|
||||||
|
from blueprintcompiler.language.values import Value
|
||||||
|
|
||||||
from .attributes import BaseAttribute
|
from .attributes import BaseAttribute
|
||||||
from .gobject_object import Object, ObjectContent
|
from .gobject_object import Object, ObjectContent
|
||||||
from .common import *
|
from .common import *
|
||||||
|
|
||||||
|
|
||||||
class Menu(Object):
|
class Menu(AstNode):
|
||||||
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()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def gir_class(self):
|
def gir_class(self):
|
||||||
return self.root.gir.namespaces["Gtk"].lookup_type("Gio.Menu")
|
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):
|
class MenuAttribute(BaseAttribute):
|
||||||
tag_name = "attribute"
|
tag_name = "attribute"
|
||||||
|
@ -43,6 +47,10 @@ class MenuAttribute(BaseAttribute):
|
||||||
def value_type(self):
|
def value_type(self):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self) -> Value:
|
||||||
|
return self.children[Value][0]
|
||||||
|
|
||||||
|
|
||||||
menu_contents = Sequence()
|
menu_contents = Sequence()
|
||||||
|
|
||||||
|
|
|
@ -39,9 +39,6 @@ class Widget(AstNode):
|
||||||
f"Cannot assign {object.gir_class.full_name} to {type.full_name}"
|
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):
|
class Widgets(AstNode):
|
||||||
grammar = [
|
grammar = [
|
||||||
|
@ -59,12 +56,6 @@ class Widgets(AstNode):
|
||||||
def unique_in_parent(self):
|
def unique_in_parent(self):
|
||||||
self.validate_unique_in_parent("Duplicate widgets block")
|
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(
|
@completer(
|
||||||
applies_in=[ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
|
|
|
@ -31,13 +31,6 @@ class Item(AstNode):
|
||||||
def value_type(self):
|
def value_type(self):
|
||||||
return StringType()
|
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):
|
class Strings(AstNode):
|
||||||
grammar = [
|
grammar = [
|
||||||
|
@ -55,12 +48,6 @@ class Strings(AstNode):
|
||||||
def unique_in_parent(self):
|
def unique_in_parent(self):
|
||||||
self.validate_unique_in_parent("Duplicate strings block")
|
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(
|
@completer(
|
||||||
applies_in=[ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
|
|
|
@ -25,9 +25,6 @@ from .common import *
|
||||||
class StyleClass(AstNode):
|
class StyleClass(AstNode):
|
||||||
grammar = UseQuoted("name")
|
grammar = UseQuoted("name")
|
||||||
|
|
||||||
def emit_xml(self, xml):
|
|
||||||
xml.put_self_closing("class", name=self.tokens["name"])
|
|
||||||
|
|
||||||
|
|
||||||
class Styles(AstNode):
|
class Styles(AstNode):
|
||||||
grammar = [
|
grammar = [
|
||||||
|
@ -45,12 +42,6 @@ class Styles(AstNode):
|
||||||
def unique_in_parent(self):
|
def unique_in_parent(self):
|
||||||
self.validate_unique_in_parent("Duplicate styles block")
|
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(
|
@completer(
|
||||||
applies_in=[ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
|
|
|
@ -41,6 +41,10 @@ class Child(AstNode):
|
||||||
Object,
|
Object,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def object(self) -> Object:
|
||||||
|
return self.children[Object][0]
|
||||||
|
|
||||||
@validate()
|
@validate()
|
||||||
def parent_can_have_child(self):
|
def parent_can_have_child(self):
|
||||||
if gir_class := self.parent.gir_class:
|
if gir_class := self.parent.gir_class:
|
||||||
|
@ -70,17 +74,6 @@ class Child(AstNode):
|
||||||
else:
|
else:
|
||||||
return None
|
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")
|
@decompiler("child")
|
||||||
def decompile_child(ctx, gir, type=None, internal_child=None):
|
def decompile_child(ctx, gir, type=None, internal_child=None):
|
||||||
|
|
|
@ -34,28 +34,27 @@ class Template(Object):
|
||||||
ObjectContent,
|
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
|
@property
|
||||||
def gir_class(self):
|
def gir_class(self):
|
||||||
# Templates might not have a parent class defined
|
# Templates might not have a parent class defined
|
||||||
if len(self.children[ClassName]):
|
if class_name := self.class_name:
|
||||||
return self.children[ClassName][0].gir_type
|
return class_name.gir_type
|
||||||
|
|
||||||
@validate("id")
|
@validate("id")
|
||||||
def unique_in_parent(self):
|
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])}",)
|
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")
|
@decompiler("template")
|
||||||
def decompile_template(ctx: DecompileCtx, gir, klass, parent="Widget"):
|
def decompile_template(ctx: DecompileCtx, gir, klass, parent="Widget"):
|
||||||
|
|
|
@ -61,10 +61,6 @@ class GtkDirective(AstNode):
|
||||||
return gir.get_namespace("Gtk", self.tokens["version"])
|
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):
|
class Import(AstNode):
|
||||||
grammar = Statement(
|
grammar = Statement(
|
||||||
"using",
|
"using",
|
||||||
|
@ -82,6 +78,3 @@ class Import(AstNode):
|
||||||
return gir.get_namespace(self.tokens["namespace"], self.tokens["version"])
|
return gir.get_namespace(self.tokens["namespace"], self.tokens["version"])
|
||||||
except CompileError:
|
except CompileError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def emit_xml(self, xml):
|
|
||||||
pass
|
|
||||||
|
|
|
@ -120,6 +120,14 @@ class ResponseId(AstNode):
|
||||||
if widget.tokens["is_default"]:
|
if widget.tokens["is_default"]:
|
||||||
raise CompileError("Default response is already set")
|
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
|
@property
|
||||||
def widget_id(self) -> str:
|
def widget_id(self) -> str:
|
||||||
"""Get action widget ID."""
|
"""Get action widget ID."""
|
||||||
|
@ -128,25 +136,3 @@ class ResponseId(AstNode):
|
||||||
_object: Object = self.parent.children[Object][0]
|
_object: Object = self.parent.children[Object][0]
|
||||||
return _object.tokens["id"]
|
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:
|
if self.gir_type:
|
||||||
return self.gir_type.doc
|
return self.gir_type.doc
|
||||||
|
|
||||||
def emit_xml(self, xml: XmlEmitter):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ClassName(TypeName):
|
class ClassName(TypeName):
|
||||||
@validate("namespace", "class_name")
|
@validate("namespace", "class_name")
|
||||||
|
|
|
@ -86,10 +86,3 @@ class UI(AstNode):
|
||||||
token = obj.group.tokens["id"]
|
token = obj.group.tokens["id"]
|
||||||
raise CompileError(f"Duplicate object ID '{obj.tokens['id']}'", token.start, token.end)
|
raise CompileError(f"Duplicate object ID '{obj.tokens['id']}'", token.start, token.end)
|
||||||
passed[obj.tokens["id"]] = obj
|
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
|
@property
|
||||||
def attrs(self):
|
def string(self) -> str:
|
||||||
attrs = { "translatable": "true" }
|
return self.tokens["value"]
|
||||||
if "context" in self.tokens:
|
|
||||||
attrs["context"] = self.tokens["context"]
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
def emit_xml(self, xml: XmlEmitter):
|
@property
|
||||||
xml.put_text(self.tokens["value"])
|
def context(self) -> str | None:
|
||||||
|
return self.tokens["context"]
|
||||||
|
|
||||||
|
|
||||||
class TypeValue(Value):
|
class TypeValue(Value):
|
||||||
|
@ -68,9 +66,6 @@ class TypeValue(Value):
|
||||||
def type_name(self):
|
def type_name(self):
|
||||||
return self.children[TypeName][0]
|
return self.children[TypeName][0]
|
||||||
|
|
||||||
def emit_xml(self, xml: XmlEmitter):
|
|
||||||
xml.put_text(self.type_name.glib_type_name)
|
|
||||||
|
|
||||||
@validate()
|
@validate()
|
||||||
def validate_for_type(self):
|
def validate_for_type(self):
|
||||||
type = self.parent.value_type
|
type = self.parent.value_type
|
||||||
|
@ -81,8 +76,9 @@ class TypeValue(Value):
|
||||||
class QuotedValue(Value):
|
class QuotedValue(Value):
|
||||||
grammar = UseQuoted("value")
|
grammar = UseQuoted("value")
|
||||||
|
|
||||||
def emit_xml(self, xml: XmlEmitter):
|
@property
|
||||||
xml.put_text(self.tokens["value"])
|
def value(self) -> str:
|
||||||
|
return self.tokens["value"]
|
||||||
|
|
||||||
@validate()
|
@validate()
|
||||||
def validate_for_type(self):
|
def validate_for_type(self):
|
||||||
|
@ -119,8 +115,9 @@ class QuotedValue(Value):
|
||||||
class NumberValue(Value):
|
class NumberValue(Value):
|
||||||
grammar = UseNumber("value")
|
grammar = UseNumber("value")
|
||||||
|
|
||||||
def emit_xml(self, xml: XmlEmitter):
|
@property
|
||||||
xml.put_text(self.tokens["value"])
|
def value(self) -> int | float:
|
||||||
|
return self.tokens["value"]
|
||||||
|
|
||||||
@validate()
|
@validate()
|
||||||
def validate_for_type(self):
|
def validate_for_type(self):
|
||||||
|
@ -179,19 +176,10 @@ class FlagsValue(Value):
|
||||||
if type is not None and not isinstance(type, gir.Bitfield):
|
if type is not None and not isinstance(type, gir.Bitfield):
|
||||||
raise CompileError(f"{type.full_name} is not a bitfield type")
|
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):
|
class IdentValue(Value):
|
||||||
grammar = UseIdent("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()
|
@validate()
|
||||||
def validate_for_type(self):
|
def validate_for_type(self):
|
||||||
type = self.parent.value_type
|
type = self.parent.value_type
|
||||||
|
|
|
@ -25,7 +25,7 @@ from .errors import PrintableError, report_bug, MultipleErrors
|
||||||
from .lsp import LanguageServer
|
from .lsp import LanguageServer
|
||||||
from . import parser, tokenizer, decompiler, interactive_port
|
from . import parser, tokenizer, decompiler, interactive_port
|
||||||
from .utils import Colors
|
from .utils import Colors
|
||||||
from .xml_emitter import XmlEmitter
|
from .outputs import XmlOutput
|
||||||
|
|
||||||
VERSION = "uninstalled"
|
VERSION = "uninstalled"
|
||||||
LIBDIR = None
|
LIBDIR = None
|
||||||
|
@ -141,7 +141,9 @@ class BlueprintApp:
|
||||||
if len(ast.errors):
|
if len(ast.errors):
|
||||||
raise MultipleErrors(ast.errors)
|
raise MultipleErrors(ast.errors)
|
||||||
|
|
||||||
return ast.generate(), warnings
|
formatter = XmlOutput()
|
||||||
|
|
||||||
|
return formatter.emit(ast), warnings
|
||||||
|
|
||||||
|
|
||||||
def main(version, libdir):
|
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
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
from xml.sax import saxutils
|
from xml.sax import saxutils
|
||||||
from . import gir
|
|
||||||
|
from blueprintcompiler.gir import GirType
|
||||||
|
from blueprintcompiler.language.types import ClassName
|
||||||
|
|
||||||
|
|
||||||
class XmlEmitter:
|
class XmlEmitter:
|
||||||
|
@ -29,7 +30,7 @@ class XmlEmitter:
|
||||||
self._tag_stack = []
|
self._tag_stack = []
|
||||||
self._needs_newline = False
|
self._needs_newline = False
|
||||||
|
|
||||||
def start_tag(self, tag, **attrs):
|
def start_tag(self, tag, **attrs: str | GirType | ClassName | bool | None):
|
||||||
self._indent()
|
self._indent()
|
||||||
self.result += f"<{tag}"
|
self.result += f"<{tag}"
|
||||||
for key, val in attrs.items():
|
for key, val in attrs.items():
|
||||||
|
@ -55,7 +56,7 @@ class XmlEmitter:
|
||||||
self.result += f"</{tag}>"
|
self.result += f"</{tag}>"
|
||||||
self._needs_newline = True
|
self._needs_newline = True
|
||||||
|
|
||||||
def put_text(self, text):
|
def put_text(self, text: str | int | float):
|
||||||
self.result += saxutils.escape(str(text))
|
self.result += saxutils.escape(str(text))
|
||||||
self._needs_newline = False
|
self._needs_newline = False
|
||||||
|
|
||||||
|
@ -64,7 +65,9 @@ class XmlEmitter:
|
||||||
self.result += "\n" + " " * (self.indent * len(self._tag_stack))
|
self.result += "\n" + " " * (self.indent * len(self._tag_stack))
|
||||||
|
|
||||||
def _to_string(self, val):
|
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
|
return val.glib_type_name
|
||||||
else:
|
else:
|
||||||
return str(val)
|
return str(val)
|
|
@ -1,6 +1,8 @@
|
||||||
import os, sys
|
import os, sys
|
||||||
from pythonfuzz.main import PythonFuzz
|
from pythonfuzz.main import PythonFuzz
|
||||||
|
|
||||||
|
from blueprintcompiler.outputs.xml import XmlOutput
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||||
|
|
||||||
from blueprintcompiler import tokenizer, parser, decompiler, gir
|
from blueprintcompiler import tokenizer, parser, decompiler, gir
|
||||||
|
@ -17,8 +19,9 @@ def fuzz(buf):
|
||||||
tokens = tokenizer.tokenize(blueprint)
|
tokens = tokenizer.tokenize(blueprint)
|
||||||
ast, errors, warnings = parser.parse(tokens)
|
ast, errors, warnings = parser.parse(tokens)
|
||||||
|
|
||||||
|
xml = XmlOutput()
|
||||||
if errors is None and len(ast.errors) == 0:
|
if errors is None and len(ast.errors) == 0:
|
||||||
actual = ast.generate()
|
xml.emit(ast)
|
||||||
except CompilerBugError as e:
|
except CompilerBugError as e:
|
||||||
raise e
|
raise e
|
||||||
except PrintableError:
|
except PrintableError:
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<signal name="activate" handler="click" object="button"/>
|
<signal name="activate" handler="click" object="button"/>
|
||||||
</object>
|
</object>
|
||||||
<object class="GtkButton" id="button">
|
<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"/>
|
<signal name="notify::visible" handler="on_button_notify_visible"/>
|
||||||
</object>
|
</object>
|
||||||
</interface>
|
</interface>
|
||||||
|
|
|
@ -28,6 +28,7 @@ from blueprintcompiler.completions import complete
|
||||||
from blueprintcompiler.errors import PrintableError, MultipleErrors, CompileError
|
from blueprintcompiler.errors import PrintableError, MultipleErrors, CompileError
|
||||||
from blueprintcompiler.tokenizer import Token, TokenType, tokenize
|
from blueprintcompiler.tokenizer import Token, TokenType, tokenize
|
||||||
from blueprintcompiler import utils
|
from blueprintcompiler import utils
|
||||||
|
from blueprintcompiler.outputs.xml import XmlOutput
|
||||||
|
|
||||||
|
|
||||||
class TestSamples(unittest.TestCase):
|
class TestSamples(unittest.TestCase):
|
||||||
|
@ -56,7 +57,8 @@ class TestSamples(unittest.TestCase):
|
||||||
if len(warnings):
|
if len(warnings):
|
||||||
raise MultipleErrors(warnings)
|
raise MultipleErrors(warnings)
|
||||||
|
|
||||||
actual = ast.generate()
|
xml = XmlOutput()
|
||||||
|
actual = xml.emit(ast)
|
||||||
if actual.strip() != expected.strip(): # pragma: no cover
|
if actual.strip() != expected.strip(): # pragma: no cover
|
||||||
diff = difflib.unified_diff(expected.splitlines(), actual.splitlines())
|
diff = difflib.unified_diff(expected.splitlines(), actual.splitlines())
|
||||||
print("\n".join(diff))
|
print("\n".join(diff))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue