From 7cf3c0bfb1bf634ff40f5c21266ae9f0c31edaa4 Mon Sep 17 00:00:00 2001 From: James Westman Date: Sun, 24 Oct 2021 13:16:33 -0500 Subject: [PATCH] Support menus and object properties --- gtkblueprinttool/ast.py | 56 +++++++++++++++--- gtkblueprinttool/parser.py | 105 ++++++++++++++++++++++++++++++--- gtkblueprinttool/xml_reader.py | 2 +- tests/test_parser.py | 11 ++++ 4 files changed, 159 insertions(+), 15 deletions(-) diff --git a/gtkblueprinttool/ast.py b/gtkblueprinttool/ast.py index a5073e5..027f73a 100644 --- a/gtkblueprinttool/ast.py +++ b/gtkblueprinttool/ast.py @@ -128,7 +128,7 @@ class AstNode: class UI(AstNode): """ The AST node for the entire file """ - def __init__(self, gtk_directives=[], imports=[], objects=[], templates=[]): + def __init__(self, gtk_directives=[], imports=[], objects=[], templates=[], menus=[]): super().__init__() assert_true(len(gtk_directives) == 1) @@ -136,6 +136,7 @@ class UI(AstNode): self.imports = imports self.objects = objects self.templates = templates + self.menus = menus @validate() def gir(self): @@ -156,10 +157,8 @@ class UI(AstNode): def emit_xml(self, xml: XmlEmitter): xml.start_tag("interface") self.gtk_directive.emit_xml(xml) - for object in self.objects: - object.emit_xml(xml) - for template in self.templates: - template.emit_xml(xml) + for x in [*self.templates, *self.objects, *self.menus]: + x.emit_xml(xml) xml.end_tag() @@ -288,13 +287,14 @@ class ObjectContent(AstNode): class Property(AstNode): child_type = "properties" - def __init__(self, name, value=None, translatable=False, bind_source=None, bind_property=None): + def __init__(self, name, value=None, translatable=False, bind_source=None, bind_property=None, objects=None): super().__init__() self.name = name self.value = value self.translatable = translatable self.bind_source = bind_source self.bind_property = bind_property + self.objects = objects @validate() @@ -339,7 +339,11 @@ class Property(AstNode): "bind-source": self.bind_source, "bind-property": self.bind_property, } - if self.value is None: + if self.objects is not None: + xml.start_tag("property", **props) + self.objects[0].emit_xml(xml) + xml.end_tag() + elif self.value is None: xml.put_self_closing("property", **props) else: xml.start_tag("property", **props) @@ -424,3 +428,41 @@ class StyleClass(AstNode): def emit_xml(self, xml): xml.put_self_closing("class", name=self.name) + + +class Menu(AstNode): + child_type = "menus" + + def __init__(self, tag, id=None, menus=None, attributes=None): + super().__init__() + self.tag = tag + self.id = id + self.menus = menus or [] + self.attributes = attributes or [] + + def emit_xml(self, xml: XmlEmitter): + xml.start_tag(self.tag, id=self.id) + for attr in self.attributes: + attr.emit_xml(xml) + for menu in self.menus: + menu.emit_xml(xml) + xml.end_tag() + + +class MenuAttribute(AstNode): + child_type = "attributes" + + def __init__(self, name, value, translatable=False): + super().__init__() + self.name = name + self.value = value + self.translatable = translatable + + def emit_xml(self, xml: XmlEmitter): + xml.start_tag( + "attribute", + name=self.name, + translatable="yes" if self.translatable else None, + ) + xml.put_text(str(self.value)) + xml.end_tag() diff --git a/gtkblueprinttool/parser.py b/gtkblueprinttool/parser.py index ce39057..841cbcf 100644 --- a/gtkblueprinttool/parser.py +++ b/gtkblueprinttool/parser.py @@ -77,12 +77,20 @@ def parse(tokens) -> ast.UI: UseQuoted("value"), ) + object = Group( + ast.Object, + None + ) + property = Group( ast.Property, Sequence( UseIdent("name"), Op(":"), - value.expected("a value"), + AnyOf( + object, + value, + ).expected("a value"), StmtEnd().expected("`;`"), ) ).recover() @@ -120,11 +128,6 @@ def parse(tokens) -> ast.UI: ) ).recover() - object = Group( - ast.Object, - None - ) - child = Group( ast.Child, Sequence( @@ -152,6 +155,93 @@ def parse(tokens) -> ast.UI: ) ) + menu_contents = Sequence() + + menu_section = Group( + ast.Menu, + Sequence( + Keyword("section"), + UseLiteral("tag", "section"), + Optional(UseIdent("id")), + menu_contents + ) + ) + + menu_submenu = Group( + ast.Menu, + Sequence( + Keyword("submenu"), + UseLiteral("tag", "submenu"), + Optional(UseIdent("id")), + menu_contents + ) + ) + + menu_attribute = Group( + ast.MenuAttribute, + Sequence( + UseIdent("name"), + Op(":"), + value.expected("a value"), + StmtEnd().expected("`;`"), + ) + ) + + menu_item = Group( + ast.Menu, + Sequence( + Keyword("item"), + UseLiteral("tag", "item"), + Optional(UseIdent("id")), + OpenBlock().expected("`{`"), + ZeroOrMore(menu_attribute), + CloseBlock().err("Could not understand statement"), + ) + ) + + menu_item_shorthand = Group( + ast.Menu, + Sequence( + Keyword("item"), + UseLiteral("tag", "item"), + Group( + ast.MenuAttribute, + Sequence(UseLiteral("name", "label"), value), + ), + Optional(Group( + ast.MenuAttribute, + Sequence(UseLiteral("name", "action"), value), + )), + Optional(Group( + ast.MenuAttribute, + Sequence(UseLiteral("name", "verb-icon-name"), value), + )), + StmtEnd().expected("`;`"), + ) + ) + + menu_contents.children = [ + OpenBlock().expected("`{`"), + ZeroOrMore(AnyOf( + menu_section, + menu_submenu, + menu_item_shorthand, + menu_item, + menu_attribute, + )), + CloseBlock().err("Could not understand statement"), + ] + + menu = Group( + ast.Menu, + Sequence( + Keyword("menu"), + UseLiteral("tag", "menu"), + Optional(UseIdent("id")), + menu_contents + ), + ) + object_content = Group( ast.ObjectContent, Sequence( @@ -171,7 +261,7 @@ def parse(tokens) -> ast.UI: object.child = Sequence( class_name, Optional(UseIdent("id")), - object_content.expected("block"), + object_content, ) template = Group( @@ -192,6 +282,7 @@ def parse(tokens) -> ast.UI: ZeroOrMore(import_statement), ZeroOrMore(AnyOf( template, + menu, object, )), Eof().err("Failed to parse the rest of the file"), diff --git a/gtkblueprinttool/xml_reader.py b/gtkblueprinttool/xml_reader.py index 621c035..94061fe 100644 --- a/gtkblueprinttool/xml_reader.py +++ b/gtkblueprinttool/xml_reader.py @@ -26,7 +26,7 @@ from .utils import lazy_prop PARSE_GIR = set([ "repository", "namespace", "class", "interface", "property", "glib:signal", - "include", + "include", "implements", ]) diff --git a/tests/test_parser.py b/tests/test_parser.py index cc957f3..d8a70a6 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -44,6 +44,17 @@ class TestParser(unittest.TestCase): } } + menu my_menu { + section { + item { + label: "Run"; + target: "run"; + } + } + submenu {} + item _("Copy") copy-symbolic app.copy; + } + Label { style "dim-label", "my-class"; label: "Text";