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
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()
|
73
blueprintcompiler/outputs/xml/xml_emitter.py
Normal file
73
blueprintcompiler/outputs/xml/xml_emitter.py
Normal file
|
@ -0,0 +1,73 @@
|
|||
# xml_emitter.py
|
||||
#
|
||||
# Copyright 2021 James Westman <james@jwestman.net>
|
||||
#
|
||||
# This file is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This file is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
from xml.sax import saxutils
|
||||
|
||||
from blueprintcompiler.gir import GirType
|
||||
from blueprintcompiler.language.types import ClassName
|
||||
|
||||
|
||||
class XmlEmitter:
|
||||
def __init__(self, indent=2):
|
||||
self.indent = indent
|
||||
self.result = '<?xml version="1.0" encoding="UTF-8"?>'
|
||||
self._tag_stack = []
|
||||
self._needs_newline = False
|
||||
|
||||
def start_tag(self, tag, **attrs: str | GirType | ClassName | bool | None):
|
||||
self._indent()
|
||||
self.result += f"<{tag}"
|
||||
for key, val in attrs.items():
|
||||
if val is not None:
|
||||
self.result += f' {key.replace("_", "-")}="{saxutils.escape(self._to_string(val))}"'
|
||||
self.result += ">"
|
||||
self._tag_stack.append(tag)
|
||||
self._needs_newline = False
|
||||
|
||||
def put_self_closing(self, tag, **attrs):
|
||||
self._indent()
|
||||
self.result += f"<{tag}"
|
||||
for key, val in attrs.items():
|
||||
if val is not None:
|
||||
self.result += f' {key.replace("_", "-")}="{saxutils.escape(self._to_string(val))}"'
|
||||
self.result += "/>"
|
||||
self._needs_newline = True
|
||||
|
||||
def end_tag(self):
|
||||
tag = self._tag_stack.pop()
|
||||
if self._needs_newline:
|
||||
self._indent()
|
||||
self.result += f"</{tag}>"
|
||||
self._needs_newline = True
|
||||
|
||||
def put_text(self, text: str | int | float):
|
||||
self.result += saxutils.escape(str(text))
|
||||
self._needs_newline = False
|
||||
|
||||
def _indent(self):
|
||||
if self.indent is not None:
|
||||
self.result += "\n" + " " * (self.indent * len(self._tag_stack))
|
||||
|
||||
def _to_string(self, val):
|
||||
if isinstance(val, GirType):
|
||||
return val.glib_type_name
|
||||
elif isinstance(val, ClassName):
|
||||
return val.glib_type_name
|
||||
else:
|
||||
return str(val)
|
Loading…
Add table
Add a link
Reference in a new issue