Support menus and object properties

This commit is contained in:
James Westman 2021-10-24 13:16:33 -05:00
parent b00401d53f
commit 7cf3c0bfb1
No known key found for this signature in database
GPG key ID: CE2DBA0ADB654EA6
4 changed files with 159 additions and 15 deletions

View file

@ -128,7 +128,7 @@ class AstNode:
class UI(AstNode): class UI(AstNode):
""" The AST node for the entire file """ """ 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__() super().__init__()
assert_true(len(gtk_directives) == 1) assert_true(len(gtk_directives) == 1)
@ -136,6 +136,7 @@ class UI(AstNode):
self.imports = imports self.imports = imports
self.objects = objects self.objects = objects
self.templates = templates self.templates = templates
self.menus = menus
@validate() @validate()
def gir(self): def gir(self):
@ -156,10 +157,8 @@ class UI(AstNode):
def emit_xml(self, xml: XmlEmitter): def emit_xml(self, xml: XmlEmitter):
xml.start_tag("interface") xml.start_tag("interface")
self.gtk_directive.emit_xml(xml) self.gtk_directive.emit_xml(xml)
for object in self.objects: for x in [*self.templates, *self.objects, *self.menus]:
object.emit_xml(xml) x.emit_xml(xml)
for template in self.templates:
template.emit_xml(xml)
xml.end_tag() xml.end_tag()
@ -288,13 +287,14 @@ class ObjectContent(AstNode):
class Property(AstNode): class Property(AstNode):
child_type = "properties" 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__() super().__init__()
self.name = name self.name = name
self.value = value self.value = value
self.translatable = translatable self.translatable = translatable
self.bind_source = bind_source self.bind_source = bind_source
self.bind_property = bind_property self.bind_property = bind_property
self.objects = objects
@validate() @validate()
@ -339,7 +339,11 @@ class Property(AstNode):
"bind-source": self.bind_source, "bind-source": self.bind_source,
"bind-property": self.bind_property, "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) xml.put_self_closing("property", **props)
else: else:
xml.start_tag("property", **props) xml.start_tag("property", **props)
@ -424,3 +428,41 @@ class StyleClass(AstNode):
def emit_xml(self, xml): def emit_xml(self, xml):
xml.put_self_closing("class", name=self.name) 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()

View file

@ -77,12 +77,20 @@ def parse(tokens) -> ast.UI:
UseQuoted("value"), UseQuoted("value"),
) )
object = Group(
ast.Object,
None
)
property = Group( property = Group(
ast.Property, ast.Property,
Sequence( Sequence(
UseIdent("name"), UseIdent("name"),
Op(":"), Op(":"),
value.expected("a value"), AnyOf(
object,
value,
).expected("a value"),
StmtEnd().expected("`;`"), StmtEnd().expected("`;`"),
) )
).recover() ).recover()
@ -120,11 +128,6 @@ def parse(tokens) -> ast.UI:
) )
).recover() ).recover()
object = Group(
ast.Object,
None
)
child = Group( child = Group(
ast.Child, ast.Child,
Sequence( 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( object_content = Group(
ast.ObjectContent, ast.ObjectContent,
Sequence( Sequence(
@ -171,7 +261,7 @@ def parse(tokens) -> ast.UI:
object.child = Sequence( object.child = Sequence(
class_name, class_name,
Optional(UseIdent("id")), Optional(UseIdent("id")),
object_content.expected("block"), object_content,
) )
template = Group( template = Group(
@ -192,6 +282,7 @@ def parse(tokens) -> ast.UI:
ZeroOrMore(import_statement), ZeroOrMore(import_statement),
ZeroOrMore(AnyOf( ZeroOrMore(AnyOf(
template, template,
menu,
object, object,
)), )),
Eof().err("Failed to parse the rest of the file"), Eof().err("Failed to parse the rest of the file"),

View file

@ -26,7 +26,7 @@ from .utils import lazy_prop
PARSE_GIR = set([ PARSE_GIR = set([
"repository", "namespace", "class", "interface", "property", "glib:signal", "repository", "namespace", "class", "interface", "property", "glib:signal",
"include", "include", "implements",
]) ])

View file

@ -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 { Label {
style "dim-label", "my-class"; style "dim-label", "my-class";
label: "Text"; label: "Text";