From e0b23ea26f4a1b025c32fac6ec349df188b847ad Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Fri, 8 Sep 2023 13:29:14 +0200 Subject: [PATCH] Qute: add arguments metadata for user-defined tags - resolves #35765 --- docs/src/main/asciidoc/qute-reference.adoc | 39 ++++++++ .../deployment/MessageBundleProcessor.java | 4 +- .../qute/deployment/QuteProcessor.java | 7 +- .../deployment/tag/UserTagArgumentsTest.java | 32 +++++++ .../tag/UserTagArgumentsValidationTest.java | 38 ++++++++ .../io/quarkus/qute/IncludeSectionHelper.java | 4 +- .../io/quarkus/qute/LoopSectionHelper.java | 1 - .../io/quarkus/qute/SectionHelperFactory.java | 3 + .../io/quarkus/qute/UserTagSectionHelper.java | 90 ++++++++++++++++++- .../test/java/io/quarkus/qute/ParserTest.java | 2 +- .../java/io/quarkus/qute/UserTagTest.java | 31 +++++++ 11 files changed, 243 insertions(+), 8 deletions(-) create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/tag/UserTagArgumentsTest.java create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/tag/UserTagArgumentsValidationTest.java diff --git a/docs/src/main/asciidoc/qute-reference.adoc b/docs/src/main/asciidoc/qute-reference.adoc index fd9eabea895cb..a686e8a200115 100644 --- a/docs/src/main/asciidoc/qute-reference.adoc +++ b/docs/src/main/asciidoc/qute-reference.adoc @@ -1148,6 +1148,45 @@ Qute executes the tag as an _isolated_ template, i.e. without access to the cont However, sometimes it might be useful to change the default behavior and disable the isolation. In this case, just add `_isolated=false` or `_unisolated` argument to the call site, for example `{#itemDetail item showImage=true _isolated=false /}` or `{#itemDetail item showImage=true _unisolated /}`. +===== Arguments + +Named arguments can be accessed directly in a tag template. +The first argument does not have to define a name but can be accessed using the `it` alias. +Furthermore, arguments metadata are accessible in a tag using the `_args` alias. + +* `_args.size` - returns the actual number of arguments passed to a tag +* `_args.empty` - returns `true` if no arguments are passed +* `_args.get(String name)` - returns the argument value of the given name +* `_args.filter(String...)` - returns the arguments matching the given names +* `_args.skip(String...)` - returns only the arguments that do not match the given names +* `_args.asHtmlAttributes` - renders the arguments as HTML attributes; e.g. `foo="true" bar="false"` (the arguments are sorted by name in alphabetical order) + +`_args` is also iterable: `{#each _args}{it.key}={it.value}{/each}`. + +For example, we can call the user tag defined below with `{#test 'Martin' readonly=true /}`. + +.`tags/test.html` +[source] +---- +{it} <1> +{readonly} <2> +{_args.filter('readonly').asHtmlAttributes} <3> +---- +<1> `it` is replaced with the first unnamed parameter of the tag. +<2> `readonly` is a named parameter. +<3> `_args` represents arguments metadata. + +The result would be: + +[source] +---- +Martin +true +readonly="true" +---- + +===== Inheritance + User tags can also make use of the template inheritance in the same way as regular `{#include}` sections do. .Tag `myTag` diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java index 01f395576cf78..2a67ca6932137 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java @@ -82,9 +82,9 @@ import io.quarkus.qute.Expression; import io.quarkus.qute.Expression.Part; import io.quarkus.qute.Expressions; -import io.quarkus.qute.LoopSectionHelper; import io.quarkus.qute.Namespaces; import io.quarkus.qute.Resolver; +import io.quarkus.qute.SectionHelperFactory; import io.quarkus.qute.deployment.QuteProcessor.JavaMemberLookupConfig; import io.quarkus.qute.deployment.QuteProcessor.MatchResult; import io.quarkus.qute.deployment.TemplatesAnalysisBuildItem.TemplateAnalysis; @@ -394,7 +394,7 @@ private void validateExpression(BuildProducer inco String name = firstPart.getName(); String typeInfo = firstPart.getTypeInfo(); boolean isGlobal = globals.contains(name); - boolean isLoopMetadata = typeInfo != null && typeInfo.endsWith(LoopSectionHelper.Factory.HINT_METADATA); + boolean isLoopMetadata = typeInfo != null && typeInfo.endsWith(SectionHelperFactory.HINT_METADATA); // Type info derived from a parent section, e.g "it" and "foo" boolean hasDerivedTypeInfo = typeInfo != null && !typeInfo.startsWith("" + Expressions.TYPE_INFO_SEPARATOR); diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java index 3ac64089aff6e..54a1022511e6b 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java @@ -717,6 +717,11 @@ public void beforeParsing(ParserHelper parserHelper) { addMethodParamsToParserHelper(parserHelper, pathToPathWithoutSuffix.get(templateId), checkedTemplateIdToParamDecl); + + if (templateId.startsWith(TemplatePathBuildItem.TAGS)) { + parserHelper.addParameter(UserTagSectionHelper.Factory.ARGS, + UserTagSectionHelper.Arguments.class.getName()); + } } addMethodParamsToParserHelper(parserHelper, templateId, msgBundleTemplateIdToParamDecl); @@ -827,7 +832,7 @@ void validateCheckedFragments(List validatio continue; } String typeInfo = expression.getParts().get(0).getTypeInfo(); - if (typeInfo == null || (typeInfo != null && typeInfo.endsWith(LoopSectionHelper.Factory.HINT_METADATA))) { + if (typeInfo == null || (typeInfo != null && typeInfo.endsWith(SectionHelperFactory.HINT_METADATA))) { continue; } Info info = TypeInfos.create(expression, index, null).get(0); diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/tag/UserTagArgumentsTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/tag/UserTagArgumentsTest.java new file mode 100644 index 0000000000000..69677501028ca --- /dev/null +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/tag/UserTagArgumentsTest.java @@ -0,0 +1,32 @@ +package io.quarkus.qute.deployment.tag; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.qute.Template; +import io.quarkus.test.QuarkusUnitTest; + +public class UserTagArgumentsTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addAsResource(new StringAsset( + "{_args.size}::{_args.empty}::{_args.get('name')}::{_args.asHtmlAttributes}::{_args.skip('foo','baz').size}::{#each _args.filter('name')}{it.value}{/each}"), + "templates/tags/hello.txt") + .addAsResource(new StringAsset("{#hello name=val /}"), "templates/foo.txt")); + + @Inject + Template foo; + + @Test + public void testInjection() { + assertEquals("1::false::Lu::name=\"Lu\"::1::Lu", foo.data("val", "Lu").render()); + } + +} diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/tag/UserTagArgumentsValidationTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/tag/UserTagArgumentsValidationTest.java new file mode 100644 index 0000000000000..9df58e61c1c7b --- /dev/null +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/tag/UserTagArgumentsValidationTest.java @@ -0,0 +1,38 @@ +package io.quarkus.qute.deployment.tag; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; + +import org.assertj.core.util.Throwables; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.qute.TemplateException; +import io.quarkus.test.QuarkusUnitTest; + +public class UserTagArgumentsValidationTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addAsResource(new StringAsset( + "{_args.sizes}"), + "templates/tags/hello.txt") + .addAsResource(new StringAsset("{#hello name=val /}"), "templates/foo.txt")) + .assertException(t -> { + Throwable root = Throwables.getRootCause(t); + if (root == null) { + root = t; + } + assertThat(root) + .isInstanceOf(TemplateException.class) + .hasMessageContaining("Found incorrect expressions (1)").hasMessageContaining("{_args.sizes}"); + }); + + @Test + public void test() { + fail(); + } + +} diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/IncludeSectionHelper.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/IncludeSectionHelper.java index d42298f4e3bb9..84042ece5b1c8 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/IncludeSectionHelper.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/IncludeSectionHelper.java @@ -334,7 +334,9 @@ protected boolean isSinglePart(String value) { return Expressions.splitParts(value).size() == 1; } - protected abstract boolean ignoreParameterInit(String key, String value); + protected boolean ignoreParameterInit(String key, String value) { + return key.equals(IGNORE_FRAGMENTS); + } protected abstract T newHelper(Supplier