diff --git a/docs/src/main/asciidoc/qute-reference.adoc b/docs/src/main/asciidoc/qute-reference.adoc index b9e12fd8393b9..5eae99886f0e2 100644 --- a/docs/src/main/asciidoc/qute-reference.adoc +++ b/docs/src/main/asciidoc/qute-reference.adoc @@ -576,9 +576,13 @@ It may be empty, i.e. the start tag ends with `/`: `{#myEmptySection /}`. Sections usually contain nested expressions and other sections. The end tag starts with `/` and contains the name of the section (optional): `{#if foo}Foo!{/if}` or `{#if foo}Foo!{/}`. -The start tag can also define parameters: `{#if item.isActive}`. -Parameters are separated by one or more spaces. -However, parameters can have optional names separated by the equals sign prefixed and suffixed with any number of spaces, e.g. `{#let id='Foo'}` and `{#let id = 'Foo'}` are equivalents where the name of the parameter is `id` and the value is `Foo`. +A start tag can define parameters with optional names, e.g. `{#if item.isActive}` and `{#let foo=1 bar=false}`. +Parameters are separated by one or more spaces. +Names are separated from the values by the equals sign. +Names and values can be prefixed and suffixed with any number of spaces, e.g. `{#let id='Foo'}` and `{#let id = 'Foo'}` are equivalents where the name of the parameter is `id` and the value is `Foo`. +Values can be grouped using parentheses, e.g. `{#let id=(item.id ?: 42)}` where the name is `id` and the value is `item.id ?: 42`. +Sections can interpret parameter values in any way, e.g. take the value as is. +However, in most cases the parameter value is registered as an <> and evaluated before use. A section may contain several content *blocks*. The "main" block is always present. @@ -903,14 +907,15 @@ This section allows you to define named local variables: [source,html] ---- -{#let myParent=order.item.parent isActive=false age=10} <1> +{#let myParent=order.item.parent isActive=false age=10 price=(order.price + 10)} <1><2>

{myParent.name}

Is active: {isActive} Age: {age} -{/let} <2> +{/let} <3> ---- -<1> The local variable is initialized with an expression that can also represent a <>. -<2> Keep in mind that the variable is not available outside the `let` section that defines it. +<1> The local variable is initialized with an expression that can also represent a <>, i.e. `isActive=false` and `age=10`. +<2> The infix notation is only supported if parentheses are used for grouping, e.g. `price=(order.price + 10)` is equivalent to `price=order.price.plus(10)`. +<3> Keep in mind that the variable is not available outside the `let` section that defines it. If a key of a section parameter (aka the name of the local variable) ends with a `?` then the local variable is only set if the key without the `?` suffix resolves to `null` or _"not found"_: @@ -1547,7 +1552,7 @@ On the other hand, if the value is set (e.g. via `TemplateInstance.data("foo", " The type of a default value must be assignable to the type of the parameter declaration, i.e. the following parameter declaration is incorrect and results in a build failure: `{@org.acme.Foo foo=1}`. -TIP: The default value is actually an <>. So the default value does not have to be a literal (such as `42` or `true`). For example, you can leverage the `@TemplateEnum` and specify an enum constant as a default value of a parameter declaration: `{@org.acme.MyEnum myEnum=MyEnum:FOO}`. However, the infix notation is not supported in default values. +TIP: The default value is actually an <>. So the default value does not have to be a literal (such as `42` or `true`). For example, you can leverage the `@TemplateEnum` and specify an enum constant as a default value of a parameter declaration: `{@org.acme.MyEnum myEnum=MyEnum:FOO}`. However, the infix notation is not supported in default values unless the parentheses are used for grouping, e.g. `{@org.acme.Foo foo=(foo1 ?: foo2)}``. IMPORTANT: The type of a default value is not validated in <>. diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java index 2ba80fc8d4206..7f8d58a8bbbd6 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java @@ -907,6 +907,10 @@ static ExpressionImpl parseExpression(Supplier idGenerator, String valu if (value == null || value.isEmpty()) { return ExpressionImpl.EMPTY; } + // (foo ?: bar) -> foo ?: bar + if (value.charAt(0) == START_COMPOSITE_PARAM && value.charAt(value.length() - 1) == END_COMPOSITE_PARAM) { + value = value.substring(1, value.length() - 1); + } String namespace = null; int namespaceIdx = value.indexOf(NAMESPACE_SEPARATOR); int spaceIdx; diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionHelperFactory.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionHelperFactory.java index c9ed767968f4d..f2f062e9018f5 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionHelperFactory.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionHelperFactory.java @@ -95,6 +95,7 @@ default boolean treatUnknownSectionsAsBlocks() { } /** + * Initialize a new helper instance for a specific section node in a template. * * @param context * @return a new helper instance @@ -103,6 +104,12 @@ default boolean treatUnknownSectionsAsBlocks() { /** * Initialize a section block. + *

+ * All section blocks are initialized before {@link #initialize(SectionInitContext)} is called. + *

+ * The factory is responsible to register all expression via {@link BlockInfo#addExpression(String, String)}. The expression + * can be then used during {@link #initialize(SectionInitContext)} via {@link SectionInitContext#getExpression(String)} and + * {@link SectionBlock#expressions}. * * @return a new scope if this section introduces a new scope, or the outer scope * @see BlockInfo#addExpression(String, String) @@ -200,7 +207,7 @@ default public String getParameterOrDefault(String name, String defaultValue) { * first. * * @param parameterName - * @return an expression registered for the specified param name, or {@code null} + * @return the expression registered for the main block under the specified param name, or {@code null} * @see BlockInfo#addExpression(String, String) */ public Expression getExpression(String parameterName); diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/ParamDeclarationDefaultValueTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/ParamDeclarationDefaultValueTest.java index 9c413fbe8da5e..cfcc3149aa1cf 100644 --- a/independent-projects/qute/core/src/test/java/io/quarkus/qute/ParamDeclarationDefaultValueTest.java +++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/ParamDeclarationDefaultValueTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import java.util.List; @@ -20,6 +21,16 @@ public void testDefaultValue() { assertDefaultValue(engine.parse("{@java.lang.String val= 'foo and bar'}\n{val.toUpperCase}"), "FOO AND BAR"); } + @Test + public void testDefaultValueWithComposite() { + Engine engine = Engine.builder().addDefaults().addValueResolver(new ReflectionValueResolver()).build(); + Template template = engine.parse("{@java.lang.String val=(foo or bar)}{val}"); + assertEquals("barbar", template.data("bar", "barbar").render()); + Expression fooExpr = template.getExpressions().stream().filter(e -> !e.isLiteral()).findFirst().orElse(null); + assertNotNull(fooExpr); + assertNull(fooExpr.collectTypeInfo()); + } + @Test public void testMultipleDefaultValues() { Engine engine = Engine.builder().addDefaults().addValueResolver(new ReflectionValueResolver()).build(); diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/SetSectionTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/SetSectionTest.java index b4a8c4059e8df..bc1b70a000c61 100644 --- a/independent-projects/qute/core/src/test/java/io/quarkus/qute/SetSectionTest.java +++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/SetSectionTest.java @@ -62,4 +62,16 @@ public void testParameterOrigin() { } } + @Test + public void testCompositeParams() { + Engine engine = Engine.builder().addDefaults().addValueResolver(new ReflectionValueResolver()).build(); + assertEquals("1x2x::false", + engine.parse( + "{#let foo=(baz + 1) bar=(name ? true : false)}" + + "{#for i in foo}{i_count}x{/for}::{bar}" + + "{/let}") + .data("baz", 1) + .render()); + } + }