Compare commits

...

30 commits

Author SHA1 Message Date
James Westman
a691b962fd
Update regression tests 2025-07-05 18:08:21 -05:00
James Westman
c75e00253b
lsp: Fix crash on object references
Fix a crash that occurred when you hovered over a reference to an object
that has an invalid class name.
2025-07-05 17:00:06 -05:00
James Westman
61acfbda98
fuzz: Add option for more complete testing
Add a FUZZ_LEVEL environment variable that can be used to test the
language server features under the fuzzer in addition to the compiler.
It's not enabled by default, but can be useful to run locally.
2025-07-05 16:58:11 -05:00
James Westman
fe8a629d4b
values: Don't allow assigning true/false to object
Fix a bug in the type checking code where it would not produce an error
if you assigned "true" or "false" to an object property.
2025-07-05 11:31:07 -05:00
James Westman
9cee6f9f8e
gtk_a11y: Add new state and property 2025-07-05 11:29:41 -05:00
Alice Mikhaylenko
60e704ee9a main: Skip unchanged blp files in batch-compile
Don't invalidate everything every time, otherwise we rebuild everything
even when no file has changed at all. With e.g. Vala this means more or
less rebuilding the entire project with no incremental builds.
2025-07-05 10:10:33 -05:00
Jordan Petridis
0dc27cd01d ci: Use the same build job for creating the dist tarball as well
We don't need two jobs and its fine to always run meson dist
2025-07-02 14:37:00 +03:00
James Westman
07c9c9df9c Release v0.18.0 2025-07-01 21:12:35 -05:00
James Westman
ee089aa7f9 ci: Configure tarball releases 2025-07-01 21:07:54 -05:00
James Westman
6e010148b2 Remove donate link from README 2025-06-14 15:11:39 -05:00
Matthijs Velsink
5a951696a7 docs: Small improvements for extensions
- Add Gtk.Scale mark example
- Add ExtScaleMarks to the index
- Keep the index alphabetically sorted
- Use the same order in the text
2025-06-14 11:44:06 -05:00
James Westman
71dcc02198
Allow inline menus
Newer versions of GTK allow menus to be specified inline in properties.
2025-06-14 10:33:03 -05:00
James Westman
4d42bd68c1
Post-release version bump 2025-06-14 10:19:25 -05:00
James Westman
5c7fb03da7 Fix incorrect error with Adw.AlertDialog responses 2025-05-07 17:08:26 -05:00
James Westman
2e42dc6848
decompiler: Fix bug in signals with template object
If a signal handler had the template as its object, the decompiler would
output the class name instead of the 'template' keyword.
2025-05-03 07:46:34 -05:00
James Westman
a12d3f5c81
decompile: Fix bug in lookup tags
A lookup tag with no type attribute would crash the decompiler, even if
that was valid. This wasn't caught by the tests since blueprint never
generates such XML.

Also fixed a bug in the tests that caused decompiler-only tests not to
run.
2025-04-25 20:13:01 -05:00
James Westman
a83c7e936d
black: Update formatting 2025-04-25 18:32:33 -05:00
James Westman
3816f4fe8d
Add .doap file 2025-04-25 18:29:55 -05:00
James Westman
e9d61cb6f9
Update URLs after move to GNOME namespace on GitLab 2025-04-25 18:29:55 -05:00
James Westman
6a77bfee0a
tests: Fix typing 2025-04-19 13:27:20 -05:00
James Westman
f50b898e4c adw_breakpoint: Fix crash in language server
Fix a crash that happened when an AdwBreakpointSetter rule was
incomplete, such as when you're still typing it. Fixes #189.
2025-04-01 19:27:59 -05:00
Tom Greig
cc09f3d3bb Add tests for nested templates
Basically just a copy of the list_factory test, but with an extra copy
of the list factory inside it.
2025-03-30 10:27:11 +01:00
Tom Greig
f93d5d2acd Handle nested CDATA from nested templates
When putting CDATA into the output, any instances of ']]>' in the text
are replaced with ']]]]><![CDATA[>'.  This allows nested templates, e.g.
from list views inside list views to work properly.
2025-03-28 20:53:03 +00:00
Sertonix
394014429e Sort keys in collect-sections.py
This makes sure that the reference_docs.json file
is build reproducible.

Ref https://reproducible-builds.org/
2025-03-24 22:58:43 +00:00
Chris Mayo
a4e0c3701b docs: Update overview example using format and compile 2025-03-21 01:01:24 +00:00
kotontrion
c1fbcef6d0 Merge branch blueprint-compiler:main into main 2025-03-02 15:26:42 +00:00
James Westman
404ae76787
Update MAINTENANCE.md 2025-01-17 17:25:21 -06:00
kotontrion
e07da3c339 flags: use nick instead of name 2024-12-18 17:46:26 +00:00
kotontrion
2ae41020ab Fix flag return value type 2024-12-18 17:46:26 +00:00
kotontrion
f48b840cfa compile: fix flag values
gtk builder does not support combining interger values with | in flags
properties, so the short names are used instead.
2024-12-18 17:46:26 +00:00
49 changed files with 395 additions and 98 deletions

View file

@ -1,9 +1,12 @@
stages:
- build
- pages
include:
- project: "GNOME/citemplates"
file: "templates/default-rules.yml"
- component: "gitlab.gnome.org/GNOME/citemplates/release-service@master"
inputs:
dist-job-name: "build"
build:
image: registry.gitlab.gnome.org/jwestman/blueprint-compiler
image: registry.gitlab.gnome.org/gnome/blueprint-compiler
stage: build
script:
- black --check --diff ./ tests
@ -19,21 +22,24 @@ build:
- ninja -C _build docs/en
- git clone https://gitlab.gnome.org/jwestman/blueprint-regression-tests.git
- cd blueprint-regression-tests
- git checkout 5f9e155c1333e84e6f683cdb26b02a5925fd8db3
- git checkout 57e988aa0f7c1e16fc806a6751df5abffe4bf8a5
- ./test.sh
- cd ..
- meson dist -C _build --include-subprojects --no-tests
- cp -r "_build/meson-dist/" "${CI_PROJECT_DIR}/public-dist/"
coverage: '/TOTAL.*\s([.\d]+)%/'
artifacts:
paths:
- _build
- htmlcov
- public-dist
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
fuzz:
image: registry.gitlab.gnome.org/jwestman/blueprint-compiler
image: registry.gitlab.gnome.org/gnome/blueprint-compiler
stage: build
script:
- meson _build
@ -46,7 +52,7 @@ fuzz:
- crashes
pages:
stage: pages
stage: deploy
dependencies:
- build
script:

View file

@ -8,7 +8,7 @@ in the NEWS file.
3. Make a new commit with just these two changes. Use `Release v{version}` as the commit message. Tag the commit as `v{version}` and push the tag.
4. Create a "Post-release version bump" commit.
5. Go to the Releases page in GitLab and create a new release from the tag.
6. Announce the release through relevant channels (Twitter, TWIG, etc.)
6. Announce the release through relevant channels (Mastodon, TWIG, etc.)
## Related projects

19
NEWS.md
View file

@ -1,3 +1,22 @@
# v0.18.0
## Added
- GtkBuilder now allows menus to be specified inline as a property value. Blueprint now supports this as well.
## Fixed
- Made reference_docs.json build reproducible (Sertonix)
- Correctly emit XML for nested templates (Tom Greig)
- Fix crash in language server while typing an AdwBreakpointSetter rule
- Update URLs after move to GNOME namespace on GitLab
- Fix crash when decompiling a lookup tag with no type attribute
- Fix incorrect result when decompiling a signal that has the template as its object
- Fix an incorrect "Duplicate object ID" error when an Adw.AlertDialog response had the same ID as an object
## Documentation
- Updated syntax in the example on the Overview page (Chris Mayo)
- Added examples of Gtk.Scale marks (Matthijs Velsink)
- Corrected errors in the index on the Extensions page (Matthijs Velsink)
# v0.16.0
## Added

View file

@ -82,10 +82,6 @@ Visual Studio Code
- [Blueprint Language Plugin by bodil](https://github.com/bodil/vscode-blueprint)
## Donate
You can support my work on GitHub Sponsors! <https://github.com/sponsors/jameswestman>
## Getting in Touch
Matrix room: [#blueprint-language:matrix.org](https://matrix.to/#/#blueprint-language:matrix.org)

27
blueprint-compiler.doap Normal file
View file

@ -0,0 +1,27 @@
<Project xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
xmlns:foaf="http://xmlns.com/foaf/0.1/"
xmlns:gnome="http://api.gnome.org/doap-extensions#"
xmlns="http://usefulinc.com/ns/doap#">
<name xml:lang="en">Blueprint</name>
<shortdesc xml:lang="en">A modern language for creating GTK interfaces</shortdesc>
<description xml:lang="en">Blueprint is a language and associated tooling for building user interfaces for GTK.</description>
<category rdf:resource="http://api.gnome.org/doap-extensions#apps" />
<programming-language>Python</programming-language>
<homepage
rdf:resource="https://gnome.gitlab.gnome.org/blueprint-compiler/" />
<download-page
rdf:resource="https://gitlab.gnome.org/GNOME/blueprint-compiler/-/releases" />
<bug-database
rdf:resource="https://gitlab.gnome.org/GNOME/blueprint-compiler/issues" />
<maintainer>
<foaf:Person>
<foaf:name>James Westman</foaf:name>
<foaf:mbox rdf:resource="mailto:james@jwestman.net" />
<gnome:userid>jwestman</gnome:userid>
</foaf:Person>
</maintainer>
</Project>

View file

@ -255,7 +255,11 @@ def decompile_element(
ctx._node_stack.append(xml)
ctx.start_block()
gir = decompiler(*args, **kwargs)
try:
gir = decompiler(*args, **kwargs)
except TypeError as e:
raise UnsupportedError(tag=xml.tag)
if not decompiler._skip_children:
for child in xml.children:
@ -266,8 +270,6 @@ def decompile_element(
except UnsupportedError as e:
raise e
except TypeError as e:
raise UnsupportedError(tag=xml.tag)
def decompile(data: str) -> str:

View file

@ -219,7 +219,7 @@ def report_bug(): # pragma: no cover
f"""{Colors.BOLD}{Colors.RED}***** COMPILER BUG *****
The blueprint-compiler program has crashed. Please report the above stacktrace,
along with the input file(s) if possible, on GitLab:
{Colors.BOLD}{Colors.BLUE}{Colors.UNDERLINE}https://gitlab.gnome.org/jwestman/blueprint-compiler/-/issues/new?issue
{Colors.BOLD}{Colors.BLUE}{Colors.UNDERLINE}https://gitlab.gnome.org/GNOME/blueprint-compiler/-/issues/new?issue
{Colors.CLEAR}"""
)

View file

@ -71,7 +71,7 @@ def decompile_file(in_file, out_file) -> T.Union[str, CouldNotPort]:
print(
f"""{Colors.FAINT}Either the original XML file had an error, or there is a bug in the
porting tool. If you think it's a bug (which is likely), please file an issue on GitLab:
{Colors.BLUE}{Colors.UNDERLINE}https://gitlab.gnome.org/jwestman/blueprint-compiler/-/issues/new?issue{Colors.CLEAR}\n"""
{Colors.BLUE}{Colors.UNDERLINE}https://gitlab.gnome.org/GNOME/blueprint-compiler/-/issues/new?issue{Colors.CLEAR}\n"""
)
return CouldNotPort("does not compile")
@ -136,7 +136,7 @@ def step1():
wrap.write(
f"""[wrap-git]
directory = blueprint-compiler
url = https://gitlab.gnome.org/jwestman/blueprint-compiler.git
url = https://gitlab.gnome.org/GNOME/blueprint-compiler.git
revision = {VERSION}
depth = 1

View file

@ -81,8 +81,8 @@ class AdwBreakpointSetter(AstNode):
return self.tokens["property"]
@property
def value(self) -> Value:
return self.children[Value][0]
def value(self) -> T.Optional[Value]:
return self.children[Value][0] if len(self.children[Value]) > 0 else None
@property
def gir_class(self) -> T.Optional[GirType]:
@ -106,7 +106,10 @@ class AdwBreakpointSetter(AstNode):
return None
@property
def document_symbol(self) -> DocumentSymbol:
def document_symbol(self) -> T.Optional[DocumentSymbol]:
if self.value is None:
return None
return DocumentSymbol(
f"{self.object_id}.{self.property_name}",
SymbolKind.Property,

View file

@ -60,19 +60,21 @@ class ScopeCtx:
passed = {}
for obj in self._iter_recursive(self.node):
if obj.tokens["id"] is None:
from .gtk_menu import Menu
if not (isinstance(obj, Object) or isinstance(obj, Menu)) or obj.id is None:
continue
if obj.tokens["id"] in passed:
if obj.id in passed:
token = obj.group.tokens["id"]
if not isinstance(obj, Template) and not isinstance(
obj, ExtListItemFactory
):
raise CompileError(
f"Duplicate object ID '{obj.tokens['id']}'",
f"Duplicate object ID '{obj.id}'",
token.range,
)
passed[obj.tokens["id"]] = obj
passed[obj.id] = obj
def _iter_recursive(self, node: AstNode):
yield node

View file

@ -302,12 +302,18 @@ expr.children = [
@decompiler("lookup", skip_children=True, cdata=True)
def decompile_lookup(
ctx: DecompileCtx, gir: gir.GirContext, cdata: str, name: str, type: str
ctx: DecompileCtx,
gir: gir.GirContext,
cdata: str,
name: str,
type: T.Optional[str] = None,
):
if ctx.parent_node is not None and ctx.parent_node.tag == "property":
ctx.print("expr ")
if t := ctx.type_by_cname(type):
if type is None:
type = ""
elif t := ctx.type_by_cname(type):
type = decompile.full_name(t)
else:
type = "$" + type
@ -327,7 +333,7 @@ def decompile_lookup(
if constant == ctx.template_class:
ctx.print("template." + name)
elif constant == "":
ctx.print("item as <" + type + ">." + name)
ctx.print(f"item as <{type}>.{name}")
else:
ctx.print(constant + "." + name)
return

View file

@ -71,12 +71,15 @@ class Object(AstNode):
@property
def signature(self) -> str:
if self.id:
return f"{self.class_name.gir_type.full_name} {self.id}"
elif t := self.class_name.gir_type:
return f"{t.full_name}"
if t := self.class_name.gir_type:
result = t.full_name
else:
return f"{self.class_name.as_string}"
result = self.class_name.as_string
if self.id:
result += " " + self.id
return result
@property
def document_symbol(self) -> T.Optional[DocumentSymbol]:

View file

@ -21,12 +21,15 @@
from .binding import Binding
from .common import *
from .contexts import ValueTypeCtx
from .gtk_menu import menu
from .values import ArrayValue, ExprValue, ObjectValue, Value
class Property(AstNode):
grammar = Statement(
UseIdent("name"), ":", AnyOf(Binding, ExprValue, ObjectValue, Value, ArrayValue)
UseIdent("name"),
":",
AnyOf(Binding, ExprValue, menu, ObjectValue, Value, ArrayValue),
)
@property

View file

@ -225,8 +225,14 @@ class Signal(AstNode):
@decompiler("signal")
def decompile_signal(ctx, gir, name, handler, swapped=None, after="false", object=None):
def decompile_signal(
ctx: DecompileCtx, gir, name, handler, swapped=None, after="false", object=None
):
object_name = object or ""
if object_name == ctx.template_class:
object_name = "template"
name = name.replace("_", "-")
line = f"{name} => ${handler}({object_name})"

View file

@ -31,6 +31,7 @@ def get_property_types(gir):
"autocomplete": gir.get_type("AccessibleAutocomplete", "Gtk"),
"description": StringType(),
"has-popup": BoolType(),
"help-text": StringType(),
"key-shortcuts": StringType(),
"label": StringType(),
"level": IntType(),
@ -86,6 +87,7 @@ def get_state_types(gir):
"invalid": gir.get_type("AccessibleInvalidState", "Gtk"),
"pressed": gir.get_type("AccessibleTristate", "Gtk"),
"selected": BoolType(),
"visited": BoolType(),
}

View file

@ -60,11 +60,6 @@ class Menu(AstNode):
def items(self) -> T.List[T.Union["Menu", "MenuAttribute"]]:
return self.children
@validate("menu")
def has_id(self):
if self.tokens["tag"] == "menu" and self.tokens["id"] is None:
raise CompileError("Menu requires an ID")
@validate("id")
def object_id_not_reserved(self):
if self.id in RESERVED_IDS:

View file

@ -225,12 +225,12 @@ class Flag(AstNode):
return self.tokens["value"]
@property
def value(self) -> T.Optional[int]:
def value(self) -> T.Optional[str]:
type = self.context[ValueTypeCtx].value_type
if not isinstance(type, Enumeration):
return None
elif member := type.members.get(self.name):
return member.value
return member.nick
else:
return None
@ -338,7 +338,14 @@ class IdentLiteral(AstNode):
raise CompileError(
'"item" can only be used in an expression literal'
)
elif self.ident not in ["true", "false"]:
elif self.ident in ["true", "false"]:
if expected_type is not None and not isinstance(
expected_type, gir.BoolType
):
raise CompileError(
f"Cannot assign boolean to {expected_type.full_name}"
)
else:
raise CompileError(
f"Could not find object with ID {self.ident}",
did_you_mean=(

View file

@ -170,6 +170,18 @@ class BlueprintApp:
add_typelib_search_path(typelib_path)
for file in opts.inputs:
path = os.path.join(
opts.output_dir,
os.path.relpath(os.path.splitext(file.name)[0] + ".ui", opts.input_dir),
)
if os.path.isfile(path):
in_time = os.path.getmtime(file.name)
out_time = os.path.getmtime(path)
if out_time >= in_time:
continue
data = file.read()
file_abs = os.path.abspath(file.name)
input_dir_abs = os.path.abspath(opts.input_dir)
@ -186,12 +198,6 @@ class BlueprintApp:
for warning in warnings:
warning.pretty_print(file.name, data, stream=sys.stderr)
path = os.path.join(
opts.output_dir,
os.path.relpath(
os.path.splitext(file.name)[0] + ".ui", opts.input_dir
),
)
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w") as file:
file.write(xml)

View file

@ -139,6 +139,11 @@ class XmlOutput(OutputFormat):
self._emit_expression(value.expression, xml)
xml.end_tag()
elif isinstance(value, Menu):
xml.start_tag("property", **props)
self._emit_menu(value, xml)
xml.end_tag()
elif isinstance(value, ObjectValue):
xml.start_tag("property", **props)
self._emit_object(value.object, xml)
@ -308,6 +313,9 @@ class XmlOutput(OutputFormat):
elif isinstance(extension, AdwBreakpointSetters):
for setter in extension.setters:
if setter.value is None:
continue
attrs = {}
if isinstance(setter.value.child, Translated):

View file

@ -73,6 +73,7 @@ class XmlEmitter:
self._needs_newline = False
def put_cdata(self, text: str):
text = text.replace("]]>", "]]]]><![CDATA[>")
self.result += f"<![CDATA[{text}]]>"
self._needs_newline = False

View file

@ -17,7 +17,7 @@
#
# SPDX-License-Identifier: LGPL-3.0-or-later
""" Utilities for parsing an AST from a token stream. """
"""Utilities for parsing an AST from a token stream."""
import typing as T
from enum import Enum

View file

@ -9,7 +9,7 @@ from pathlib import Path
__all__ = ["get_docs_section"]
DOCS_ROOT = "https://jwestman.pages.gitlab.gnome.org/blueprint-compiler"
DOCS_ROOT = "https://gnome.pages.gitlab.gnome.org/blueprint-compiler"
sections: dict[str, "Section"] = {}
@ -132,5 +132,8 @@ if __name__ == "__main__":
# print the sections to a json file
with open(outfile, "w") as f:
json.dump(
{name: section.to_json() for name, section in sections.items()}, f, indent=2
{name: section.to_json() for name, section in sections.items()},
f,
indent=2,
sort_keys=True,
)

View file

@ -16,8 +16,8 @@ a module in your flatpak manifest:
"sources": [
{
"type": "git",
"url": "https://gitlab.gnome.org/jwestman/blueprint-compiler",
"tag": "v0.16.0"
"url": "https://gitlab.gnome.org/GNOME/blueprint-compiler",
"tag": "v0.18.0"
}
]
}

View file

@ -26,7 +26,7 @@ Blueprint is a markup language and compiler for GTK 4 user interfaces.
using Gtk 4.0;
template MyAppWindow : ApplicationWindow {
template $MyAppWindow: ApplicationWindow {
default-width: 600;
default-height: 300;
title: _("Hello, Blueprint!");
@ -35,7 +35,7 @@ Blueprint is a markup language and compiler for GTK 4 user interfaces.
HeaderBar {}
Label {
label: bind MyAppWindow.main_text;
label: bind template.main_text;
}
}
@ -59,7 +59,7 @@ Features
Links
-----
- `Source code <https://gitlab.gnome.org/jwestman/blueprint-compiler>`_
- `Source code <https://gitlab.gnome.org/GNOME/blueprint-compiler>`_
- `Workbench <https://github.com/sonnyp/Workbench>`_ lets you try, preview and export Blueprint
- `GNOME Builder <https://developer.gnome.org/documentation/introduction/builder.html>`_ provides builtin support
- `Vim syntax highlighting plugin by thetek42 <https://github.com/thetek42/vim-blueprint-syntax>`_

View file

@ -10,20 +10,21 @@ Properties are the main way to set values on objects, but they are limited by th
Extensions are a feature of ``Gtk.Buildable``--see `Gtk.Buildable.custom_tag_start() <https://docs.gtk.org/gtk4/vfunc.Buildable.custom_tag_start.html>`_ for internal details.
Because they aren't part of the type system, they aren't present in typelib files like properties and signals are. Therefore, if a library adds a new extension, syntax for it must be added to Blueprint manually. If there's a commonly used extension that isn't supported by Blueprint, please `file an issue <https://gitlab.gnome.org/jwestman/blueprint-compiler/-/issues>`_.
Because they aren't part of the type system, they aren't present in typelib files like properties and signals are. Therefore, if a library adds a new extension, syntax for it must be added to Blueprint manually. If there's a commonly used extension that isn't supported by Blueprint, please `file an issue <https://gitlab.gnome.org/GNOME/blueprint-compiler/-/issues>`_.
.. rst-class:: grammar-block
Extension = :ref:`ExtAccessibility<Syntax ExtAccessibility>`
| :ref:`ExtAdwAlertDialog<Syntax ExtAdwAlertDialog>`
| :ref:`ExtAdwMessageDialog<Syntax ExtAdwMessageDialog>`
| :ref:`ExtAdwBreakpoint<Syntax ExtAdwBreakpoint>`
| :ref:`ExtAdwMessageDialog<Syntax ExtAdwMessageDialog>`
| :ref:`ExtComboBoxItems<Syntax ExtComboBoxItems>`
| :ref:`ExtFileFilterMimeTypes<Syntax ExtFileFilter>`
| :ref:`ExtFileFilterPatterns<Syntax ExtFileFilter>`
| :ref:`ExtFileFilterSuffixes<Syntax ExtFileFilter>`
| :ref:`ExtLayout<Syntax ExtLayout>`
| :ref:`ExtListItemFactory<Syntax ExtListItemFactory>`
| :ref:`ExtScaleMarks<Syntax ExtScaleMarks>`
| :ref:`ExtSizeGroupWidgets<Syntax ExtSizeGroupWidgets>`
| :ref:`ExtStringListStrings<Syntax ExtStringListStrings>`
| :ref:`ExtStyles<Syntax ExtStyles>`
@ -47,25 +48,6 @@ The ``accessibility`` block defines values relevant to accessibility software. T
Relations which allow for a list of values, for example `labelled-by`, must be given as a single relation with a list of values instead of duplicating the relation like done in Gtk.Builder.
.. _Syntax ExtAdwBreakpoint:
Adw.Breakpoint
--------------
.. rst-class:: grammar-block
ExtAdwBreakpointCondition = 'condition' '(' <condition::ref:`QUOTED<Syntax QUOTED>`> ')'
ExtAdwBreakpoint = 'setters' '{' ExtAdwBreakpointSetter* '}'
ExtAdwBreakpointSetter = <object::ref:`IDENT<Syntax IDENT>`> '.' <property::ref:`IDENT<Syntax IDENT>`> ':' :ref:`Value <Syntax Value>` ';'
Valid in `Adw.Breakpoint <https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Breakpoint.html>`_.
Defines the condition for a breakpoint and the properties that will be set at that breakpoint. See the documentation for `Adw.Breakpoint <https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Breakpoint.html>`_.
.. note::
The `Adw.Breakpoint:condition <https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/property.Breakpoint.condition.html>`_ property has type `Adw.BreakpointCondition <https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/struct.BreakpointCondition.html>`_, which GtkBuilder doesn't know how to parse from a string. Therefore, the ``condition`` syntax is used instead.
.. _Syntax ExtAdwAlertDialog:
@ -96,6 +78,26 @@ The ``responses`` block defines the buttons that will be added to the dialog. Th
}
.. _Syntax ExtAdwBreakpoint:
Adw.Breakpoint
--------------
.. rst-class:: grammar-block
ExtAdwBreakpointCondition = 'condition' '(' <condition::ref:`QUOTED<Syntax QUOTED>`> ')'
ExtAdwBreakpoint = 'setters' '{' ExtAdwBreakpointSetter* '}'
ExtAdwBreakpointSetter = <object::ref:`IDENT<Syntax IDENT>`> '.' <property::ref:`IDENT<Syntax IDENT>`> ':' :ref:`Value <Syntax Value>` ';'
Valid in `Adw.Breakpoint <https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Breakpoint.html>`_.
Defines the condition for a breakpoint and the properties that will be set at that breakpoint. See the documentation for `Adw.Breakpoint <https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Breakpoint.html>`_.
.. note::
The `Adw.Breakpoint:condition <https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/property.Breakpoint.condition.html>`_ property has type `Adw.BreakpointCondition <https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/struct.BreakpointCondition.html>`_, which GtkBuilder doesn't know how to parse from a string. Therefore, the ``condition`` syntax is used instead.
.. _Syntax ExtAdwMessageDialog:
Adw.MessageDialog Responses
@ -262,6 +264,16 @@ Valid in `Gtk.Scale <https://docs.gtk.org/gtk4/class.Scale.html>`_.
The ``marks`` block defines the marks on a scale. A single ``mark`` has up to three arguments: a value, an optional position, and an optional label. The position can be ``left``, ``right``, ``top``, or ``bottom``. The label may be translated.
.. code-block:: blueprint
Scale {
marks [
mark (-1, bottom),
mark (0, top, _("Origin")),
mark (2),
]
}
.. _Syntax ExtSizeGroupWidgets:

View file

@ -58,7 +58,7 @@ Properties
.. rst-class:: grammar-block
Property = <name::ref:`IDENT<Syntax IDENT>`> ':' ( :ref:`Binding<Syntax Binding>` | :ref:`ExprValue<Syntax ExprValue>` | :ref:`ObjectValue<Syntax ObjectValue>` | :ref:`Value<Syntax Value>` ) ';'
Property = <name::ref:`IDENT<Syntax IDENT>`> ':' ( :ref:`Binding<Syntax Binding>` | :ref:`ExprValue<Syntax ExprValue>` | :ref:`Menu<Syntax Menu>` | :ref:`ObjectValue<Syntax ObjectValue>` | :ref:`Value<Syntax Value>` ) ';'
Properties specify the details of each object, like a label's text, an image's icon name, or the margins on a container.

View file

@ -8,7 +8,7 @@ Setting up Blueprint on a new or existing project
Using the porting tool
~~~~~~~~~~~~~~~~~~~~~~
Clone `blueprint-compiler <https://gitlab.gnome.org/jwestman/blueprint-compiler>`_
Clone `blueprint-compiler <https://gitlab.gnome.org/GNOME/blueprint-compiler>`_
from source. You can install it using ``meson _build`` and ``ninja -C _build install``,
or you can leave it uninstalled.
@ -29,7 +29,7 @@ blueprint-compiler works as a meson subproject.
[wrap-git]
directory = blueprint-compiler
url = https://gitlab.gnome.org/jwestman/blueprint-compiler.git
url = https://gitlab.gnome.org/GNOME/blueprint-compiler.git
revision = main
depth = 1

View file

@ -1,5 +1,5 @@
project('blueprint-compiler',
version: '0.16.0',
version: '0.18.0',
)
prefix = get_option('prefix')

View file

@ -7,25 +7,28 @@ from blueprintcompiler.outputs.xml import XmlOutput
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
from blueprintcompiler import decompiler, gir, parser, tokenizer, utils
from blueprintcompiler import gir, parser, tokenizer
from blueprintcompiler.completions import complete
from blueprintcompiler.errors import (
CompileError,
CompilerBugError,
MultipleErrors,
PrintableError,
)
from blueprintcompiler.tokenizer import Token, TokenType, tokenize
from blueprintcompiler.lsp import LanguageServer
fuzz_level = int(os.getenv("FUZZ_LEVEL") or "0")
@PythonFuzz
def fuzz(buf):
def fuzz(buf: bytes):
try:
blueprint = buf.decode("ascii")
tokens = tokenizer.tokenize(blueprint)
ast, errors, warnings = parser.parse(tokens)
if fuzz_level >= 1:
assert_ast_doesnt_crash(blueprint, tokens, ast)
xml = XmlOutput()
if errors is None and ast is not None:
xml.emit(ast)
@ -37,6 +40,17 @@ def fuzz(buf):
pass
def assert_ast_doesnt_crash(text, tokens, ast):
lsp = LanguageServer()
for i in range(len(text) + 1):
ast.get_docs(i)
for i in range(len(text) + 1):
list(complete(lsp, ast, tokens, i))
for i in range(len(text) + 1):
ast.get_reference(i)
ast.get_document_symbols()
if __name__ == "__main__":
# Make sure Gtk 4.0 is accessible, otherwise every test will fail on that
# and nothing interesting will be tested

View file

@ -0,0 +1,5 @@
using Gtk 4.0;
Button {
child: false;
}

View file

@ -0,0 +1 @@
4,10,5,Cannot assign boolean to Gtk.Widget

View file

@ -1 +0,0 @@
4,15,4,Namespace Gtk does not contain a type called menu

View file

@ -1 +0,0 @@
3,1,4,Menu requires an ID

View file

@ -7,7 +7,7 @@ corresponding .blp file and regenerate this file with blueprint-compiler.
<interface>
<requires lib="gtk" version="4.0"/>
<object class="GApplication">
<property name="flags">1|4</property>
<property name="flags">is-service|handles-open</property>
</object>
<object class="GtkEventControllerScroll">
<property name="flags">1</property>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
DO NOT EDIT!
This file was @generated by blueprint-compiler. Instead, edit the
corresponding .blp file and regenerate this file with blueprint-compiler.
-->
<interface>
<requires lib="gtk" version="4.0"/>
<object class="GtkMenuButton">
<property name="menu-model">
<menu id="primary_menu"></menu>
</property>
</object>
</interface>

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<template class="GtkListItem">
<property name="child">
<object class="GtkLabel">
<binding name="label">
<lookup type="RecentObject" name="filename">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
</object>
</property>
</template>
</interface>

View file

@ -0,0 +1,7 @@
using Gtk 4.0;
template ListItem {
child: Label {
label: bind template.item as <$RecentObject>.filename;
};
}

View file

@ -0,0 +1,11 @@
using Gtk 4.0;
using Adw 1;
Adw.AlertDialog dialog1 {
responses [
ok: "Ok",
cancel: "Cancel",
]
}
Button cancel {}

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
DO NOT EDIT!
This file was @generated by blueprint-compiler. Instead, edit the
corresponding .blp file and regenerate this file with blueprint-compiler.
-->
<interface>
<requires lib="gtk" version="4.0"/>
<object class="AdwAlertDialog" id="dialog1">
<responses>
<response id="ok">Ok</response>
<response id="cancel">Cancel</response>
</responses>
</object>
<object class="GtkButton" id="cancel"></object>
</interface>

View file

@ -0,0 +1,17 @@
using Gtk 4.0;
Gtk.ListView {
factory: Gtk.BuilderListItemFactory list_item_factory {
template ListItem {
child: Gtk.ListView {
factory: Gtk.BuilderListItemFactory list_item_factory {
template ListItem {
child: Gtk.Label {
label: bind template.item as <$MyObject>.name;
};
}
};
};
}
};
}

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
DO NOT EDIT!
This file was @generated by blueprint-compiler. Instead, edit the
corresponding .blp file and regenerate this file with blueprint-compiler.
-->
<interface>
<requires lib="gtk" version="4.0"/>
<object class="GtkListView">
<property name="factory">
<object class="GtkBuilderListItemFactory" id="list_item_factory">
<property name="bytes"><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="child">
<object class="GtkListView">
<property name="factory">
<object class="GtkBuilderListItemFactory" id="list_item_factory">
<property name="bytes"><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="child">
<object class="GtkLabel">
<binding name="label">
<lookup name="name" type="MyObject">
<lookup name="item" type="GtkListItem">
<constant>GtkListItem</constant>
</lookup>
</lookup>
</binding>
</object>
</property>
</template>
</interface>]]]]><![CDATA[></property>
</object>
</property>
</object>
</property>
</template>
</interface>]]></property>
</object>
</property>
</object>
</interface>

View file

@ -0,0 +1,18 @@
using Gtk 4.0;
ListView {
factory: BuilderListItemFactory list_item_factory {
template ListItem {
child: ListView {
factory: BuilderListItemFactory list_item_factory {
template ListItem {
child: Label {
label: bind template.item as <$MyObject>.name;
};
}
};
};
}
};
}

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
DO NOT EDIT!
This file was @generated by blueprint-compiler. Instead, edit the
corresponding .blp file and regenerate this file with blueprint-compiler.
-->
<interface>
<requires lib="gtk" version="4.0"/>
<menu></menu>
</interface>

View file

@ -0,0 +1,7 @@
using Gtk 4.0;
template $MyTemplate {
Button {
clicked => $my_signal_handler(template);
}
}

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
DO NOT EDIT!
This file was @generated by blueprint-compiler. Instead, edit the
corresponding .blp file and regenerate this file with blueprint-compiler.
-->
<interface>
<requires lib="gtk" version="4.0"/>
<template class="MyTemplate">
<child>
<object class="GtkButton">
<signal name="clicked" handler="my_signal_handler" object="MyTemplate"/>
</object>
</child>
</template>
</interface>

View file

@ -181,11 +181,7 @@ class TestSamples(unittest.TestCase):
def test_samples(self):
# list the samples directory
samples = [
f.stem
for f in Path(__file__).parent.glob("samples/*.blp")
if not f.stem.endswith("_dec")
]
samples = [f.stem for f in Path(__file__).parent.glob("samples/*.blp")]
samples.sort()
for sample in samples:
REQUIRE_ADW_1_4 = ["adw_breakpoint"]
@ -202,6 +198,7 @@ class TestSamples(unittest.TestCase):
"parseable",
"signal",
"signal_not_swapped",
"signal_template_object",
"template",
"template_binding",
"template_binding_extern",
@ -215,7 +212,7 @@ class TestSamples(unittest.TestCase):
]
# Decompiler-only tests
SKIP_COMPILE = ["issue_177", "translator_comments"]
SKIP_COMPILE = ["issue_177", "issue_187", "translator_comments"]
SKIP_DECOMPILE = [
# Comments are not preserved in either direction
@ -228,7 +225,7 @@ class TestSamples(unittest.TestCase):
continue
with self.subTest(sample):
if sample not in SKIP_COMPILE:
if sample not in SKIP_COMPILE and not sample.endswith("_dec"):
self.assert_sample(sample, skip_run=sample in SKIP_RUN)
with self.subTest("decompile/" + sample):

View file

@ -25,7 +25,7 @@ from blueprintcompiler.tokenizer import Token, TokenType, tokenize
class TestTokenizer(unittest.TestCase):
def assert_tokenize(self, string: str, expect: [Token]):
def assert_tokenize(self, string: str, expect: list[Token]):
try:
tokens = tokenize(string)
self.assertEqual(len(tokens), len(expect))