From 96acd9659f38a9b11de210e24eecfafe305272b7 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Thu, 13 Feb 2025 11:48:37 +0100 Subject: [PATCH] Qute: add some more built-in string extensions - str:concat, str:join, str:builder, str:eval - also slightly optimize EvalSectionHelper for the cases where a template literal with no params is used (cherry picked from commit aae84205405656a6fea25a6e65f82a2a660ad1de) --- docs/src/main/asciidoc/qute-reference.adoc | 28 +++++- .../StringTemplateExtensionsTest.java | 37 ++++++++ .../quarkus/qute/runtime/EngineProducer.java | 7 ++ .../extensions/StringTemplateExtensions.java | 63 ++++++++++++ .../java/io/quarkus/qute/EvalContext.java | 6 ++ .../io/quarkus/qute/EvalSectionHelper.java | 92 ++++++++++-------- .../java/io/quarkus/qute/EvaluatorImpl.java | 10 ++ .../java/io/quarkus/qute/LiteralSupport.java | 6 +- .../io/quarkus/qute/ResolutionContext.java | 5 + .../quarkus/qute/ResolutionContextImpl.java | 32 ++++++- .../java/io/quarkus/qute/SectionNode.java | 11 ++- .../qute/StrEvalNamespaceResolver.java | 95 +++++++++++++++++++ .../java/io/quarkus/qute/TemplateImpl.java | 2 +- .../test/java/io/quarkus/qute/EvalTest.java | 17 ++++ .../io/quarkus/qute/EvaluatedParamsTest.java | 5 + .../io/quarkus/qute/NotFoundResultTest.java | 5 + .../java/io/quarkus/qute/TestEvalContext.java | 5 + 17 files changed, 377 insertions(+), 49 deletions(-) create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/StrEvalNamespaceResolver.java diff --git a/docs/src/main/asciidoc/qute-reference.adoc b/docs/src/main/asciidoc/qute-reference.adoc index 00ef1522e495d..3b4bfc72a95ab 100644 --- a/docs/src/main/asciidoc/qute-reference.adoc +++ b/docs/src/main/asciidoc/qute-reference.adoc @@ -2367,12 +2367,34 @@ TIP: A list element can be accessed directly via an index: `{list.10}` or even ` * `fmt` or `format`: Formats the string instance via `java.lang.String.format()` ** `{myStr.fmt("arg1","arg2")}` ** `{myStr.format(locale,arg1)}` + +* `+`: Infix notation for concatenation, works with `String` and `StringBuilder` base objects +** `{item.name + '_' + mySuffix}` +** `{name + 10}` + +* `str:['']`: Returns the string value, e.g. to easily concatenate another string value +** `{str:['/path/to/'] + fileName}` + * `str:fmt` or `str:format`: Formats the supplied string value via `java.lang.String.format()` ** `{str:format("Hello %s!",name)}` ** `{str:fmt(locale,'%tA',now)}` -* `+`: Concatenation -** `{item.name + '_' + mySuffix}` -** `{name + 10}` +** `{str:fmt('/path/to/%s', fileName)}` + +* `str:concat`: Concatenates the string representations of the specified arguments. +** `{str:concat("Hello ",name,"!")}` yields `Hello Foo!` if `name` resolves to `Foo` +** `{str:concat('/path/to/', fileName)}` + +* `str:join`: Joins the string representations of the specified arguments together with a delimiter. +** `{str:join('_','Qute','is','cool')}` yields `Qute_is_cool` + +* `str:builder`: Returns a new string builder. +** `{str:builder('Qute').append("is").append("cool!")}` yields `Qute is cool!` +** `{str:builder('Qute') + "is" + whatisqute + "!"}` yields `Qute is cool!` if `whatisqute` resolves to `cool` + +* `str:eval`: Evaluates the string representation of the first argument as a template in the <>. +** `{str:eval('Hello {name}!')` yields `Hello lovely!` if `name` resolves to `lovely` +** `{str:eval(myTemplate)}` yields `Hello lovely!` if `myTemplate` resolves to `Hello {name}!` and `name` resolves to `lovely` +** `{str:eval('/path/to/{fileName}')}` yields `/path/to/file.txt` if `fileName` resolves to `file.txt` ===== Config diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/extensions/StringTemplateExtensionsTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/extensions/StringTemplateExtensionsTest.java index dfae6097eb53f..d4c5fe60eab8a 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/extensions/StringTemplateExtensionsTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/extensions/StringTemplateExtensionsTest.java @@ -46,6 +46,43 @@ public void testTemplateExtensions() { engine.parse("{foo + 'bar' + 1}") .data("foo", "bar") .render()); + assertEquals("barbar1", + engine.parse("{str:concat(foo, 'bar', 1)}") + .data("foo", "bar") + .render()); + assertEquals("barbar1", + engine.parse("{str:builder(foo).append('bar').append(1)}") + .data("foo", "bar") + .render()); + assertEquals("barbar1", + engine.parse("{str:builder.append(foo).append('bar').append(1)}") + .data("foo", "bar") + .render()); + assertEquals("barbar1", + engine.parse("{str:builder(foo) + 'bar' + 1}") + .data("foo", "bar") + .render()); + assertEquals("barbar1", + engine.parse("{str:builder + foo + 'bar' + 1}") + .data("foo", "bar") + .render()); + assertEquals("Qute-is-cool", + engine.parse("{str:join('-', 'Qute', 'is', foo)}") + .data("foo", "cool") + .render()); + assertEquals("Qute is cool!", + engine.parse("{str:Qute + ' is ' + foo + '!'}") + .data("foo", "cool") + .render()); + assertEquals("Qute is cool!", + engine.parse("{str:['Qute'] + ' is ' + foo + '!'}") + .data("foo", "cool") + .render()); + // note that this is not implemented as a template extension but a ns resolver + assertEquals("Hello fool!", + engine.parse("{str:eval('Hello {name}!')}") + .data("name", "fool") + .render()); } } diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/EngineProducer.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/EngineProducer.java index a65aa33209bfd..33e012db322bc 100644 --- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/EngineProducer.java +++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/EngineProducer.java @@ -49,6 +49,7 @@ import io.quarkus.qute.Resolver; import io.quarkus.qute.Results; import io.quarkus.qute.SectionHelperFactory; +import io.quarkus.qute.StrEvalNamespaceResolver; import io.quarkus.qute.Template; import io.quarkus.qute.TemplateGlobalProvider; import io.quarkus.qute.TemplateInstance; @@ -194,6 +195,9 @@ public EngineProducer(QuteContext context, QuteConfig config, QuteRuntimeConfig for (NamespaceResolver namespaceResolver : namespaceResolvers) { builder.addNamespaceResolver(namespaceResolver); } + // str:eval + StrEvalNamespaceResolver strEvalNamespaceResolver = new StrEvalNamespaceResolver(); + builder.addNamespaceResolver(strEvalNamespaceResolver); // Add generated resolvers for (String resolverClass : context.getResolverClasses()) { @@ -269,6 +273,9 @@ public void run() { engine = builder.build(); + // Init resolver for str:eval + strEvalNamespaceResolver.setEngine(engine); + // Load discovered template files Map> discovered = new HashMap<>(); for (String path : context.getTemplatePaths()) { diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/extensions/StringTemplateExtensions.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/extensions/StringTemplateExtensions.java index 0d2866196da3c..4e39f47a3ecb8 100644 --- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/extensions/StringTemplateExtensions.java +++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/extensions/StringTemplateExtensions.java @@ -1,6 +1,9 @@ package io.quarkus.qute.runtime.extensions; +import static io.quarkus.qute.TemplateExtension.ANY; + import java.util.Locale; +import java.util.Objects; import jakarta.enterprise.inject.Vetoed; @@ -72,4 +75,64 @@ static String plus(String str, Object val) { return str + val; } + /** + * E.g. {@code str:concat("Hello ",name)}. The priority must be lower than {@link #fmt(String, String, Object...)}. + * + * @param args + */ + @TemplateExtension(namespace = STR, priority = 1) + static String concat(Object... args) { + StringBuilder b = new StringBuilder(args.length * 10); + for (Object obj : args) { + b.append(obj.toString()); + } + return b.toString(); + } + + /** + * E.g. {@code str:join("_", "Hello",name)}. The priority must be lower than {@link #concat(Object...)}. + * + * @param delimiter + * @param args + */ + @TemplateExtension(namespace = STR, priority = 0) + static String join(String delimiter, Object... args) { + CharSequence[] elements = new CharSequence[args.length]; + for (int i = 0; i < args.length; i++) { + elements[i] = args[i].toString(); + } + return String.join(delimiter, elements); + } + + /** + * E.g. {@code str:builder}. The priority must be lower than {@link #join(String, Object...)}. + */ + @TemplateExtension(namespace = STR, priority = -1) + static StringBuilder builder() { + return new StringBuilder(); + } + + /** + * E.g. {@code str:builder('Hello')}. The priority must be lower than {@link #builder()}. + */ + @TemplateExtension(namespace = STR, priority = -2) + static StringBuilder builder(Object val) { + return new StringBuilder(Objects.toString(val)); + } + + /** + * E.g. {@code str:['Foo and bar']}. The priority must be lower than any other {@code str:} resolver. + * + * @param name + */ + @TemplateExtension(namespace = STR, priority = -10, matchName = ANY) + static String self(String name) { + return name; + } + + @TemplateExtension(matchName = "+") + static StringBuilder plus(StringBuilder builder, Object val) { + return builder.append(val); + } + } diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvalContext.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvalContext.java index 803f4cbd1c0d8..2b7b515c5d944 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvalContext.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvalContext.java @@ -57,4 +57,10 @@ public interface EvalContext { */ Object getAttribute(String key); + /** + * + * @return the current resolution context + */ + ResolutionContext resolutionContext(); + } \ No newline at end of file diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvalSectionHelper.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvalSectionHelper.java index bd76d4401a6f6..fa23c592a8b79 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvalSectionHelper.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvalSectionHelper.java @@ -11,6 +11,7 @@ public class EvalSectionHelper implements SectionHelper { + public static final String EVAL = "eval"; private static final String TEMPLATE = "template"; private final Map parameters; @@ -23,52 +24,69 @@ public EvalSectionHelper(Map parameters, Engine engine) { @Override public CompletionStage resolve(SectionResolutionContext context) { - CompletableFuture result = new CompletableFuture<>(); - context.evaluate(parameters).whenComplete((evaluatedParams, t1) -> { - if (t1 != null) { - result.completeExceptionally(t1); - } else { - try { + CompletableFuture ret = new CompletableFuture<>(); + if (parameters.size() > 1) { + context.evaluate(parameters).whenComplete((evaluatedParams, t1) -> { + if (t1 != null) { + ret.completeExceptionally(t1); + } else { // Parse the template and execute with the params as the root context object - String templateStr = evaluatedParams.get(TEMPLATE).toString(); - TemplateImpl template; - try { - template = (TemplateImpl) engine.parse(templateStr); - } catch (TemplateException e) { - Origin origin = parameters.get(TEMPLATE).getOrigin(); - throw TemplateException.builder() - .message( - "Parser error in the evaluated template: {templateId} line {line}:\\n\\t{originalMessage}") - .code(Code.ERROR_IN_EVALUATED_TEMPLATE) - .argument("templateId", - origin.hasNonGeneratedTemplateId() ? " template [" + origin.getTemplateId() + "]" - : "") - .argument("line", origin.getLine()) - .argument("originalMessage", e.getMessage()) - .build(); - } - template.root - .resolve(context.resolutionContext().createChild(Mapper.wrap(evaluatedParams), null)) - .whenComplete((resultNode, t2) -> { - if (t2 != null) { - result.completeExceptionally(t2); - } else { - result.complete(resultNode); - } - }); - } catch (Throwable e) { - result.completeExceptionally(e); + String contents = evaluatedParams.get(TEMPLATE).toString(); + parseAndResolve(ret, contents, + context.resolutionContext().createChild(Mapper.wrap(evaluatedParams), null)); } + }); + } else { + Expression contents = parameters.get(TEMPLATE); + if (contents.isLiteral()) { + parseAndResolve(ret, contents.getLiteral().toString(), context.resolutionContext()); + } else { + context.evaluate(contents).whenComplete((r, t) -> { + if (t != null) { + ret.completeExceptionally(t); + } else { + parseAndResolve(ret, r.toString(), context.resolutionContext()); + } + }); } - }); - return result; + } + + return ret; + } + + private void parseAndResolve(CompletableFuture ret, String contents, ResolutionContext resolutionContext) { + TemplateImpl template; + try { + template = (TemplateImpl) engine.parse(contents); + template.root + .resolve(resolutionContext) + .whenComplete((resultNode, t2) -> { + if (t2 != null) { + ret.completeExceptionally(t2); + } else { + ret.complete(resultNode); + } + }); + } catch (TemplateException e) { + Origin origin = parameters.get(TEMPLATE).getOrigin(); + ret.completeExceptionally(TemplateException.builder() + .message( + "Parser error in the evaluated template: {templateId} line {line}:\\n\\t{originalMessage}") + .code(Code.ERROR_IN_EVALUATED_TEMPLATE) + .argument("templateId", + origin.hasNonGeneratedTemplateId() ? " template [" + origin.getTemplateId() + "]" + : "") + .argument("line", origin.getLine()) + .argument("originalMessage", e.getMessage()) + .build()); + } } public static class Factory implements SectionHelperFactory { @Override public List getDefaultAliases() { - return ImmutableList.of("eval"); + return ImmutableList.of(EVAL); } @Override diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvaluatorImpl.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvaluatorImpl.java index 590f9617a5fe9..d4dcd011b3e60 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvaluatorImpl.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvaluatorImpl.java @@ -291,6 +291,11 @@ public Object getAttribute(String key) { return resolutionContext.getAttribute(key); } + @Override + public ResolutionContext resolutionContext() { + return resolutionContext; + } + @Override public String toString() { StringBuilder builder = new StringBuilder(); @@ -405,6 +410,11 @@ boolean tryParent() { return true; } + @Override + public ResolutionContext resolutionContext() { + return resolutionContext; + } + @Override public String toString() { StringBuilder builder = new StringBuilder(); diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/LiteralSupport.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/LiteralSupport.java index 1dd41b1ed927d..8bfcc9a482e93 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/LiteralSupport.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/LiteralSupport.java @@ -24,7 +24,7 @@ static Object getLiteralValue(String literal) { return value; } if (isStringLiteral(literal)) { - value = literal.substring(1, literal.length() - 1); + value = extractStringValue(literal); } else if (literal.equals("true")) { value = Boolean.TRUE; } else if (literal.equals("false")) { @@ -85,6 +85,10 @@ static boolean isStringLiteralSeparatorDouble(char character) { return character == '"'; } + static String extractStringValue(String strLiteral) { + return strLiteral.substring(1, strLiteral.length() - 1); + } + static boolean isStringLiteral(String value) { if (value == null || value.isEmpty()) { return false; diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ResolutionContext.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ResolutionContext.java index 7fd6d0a2d5ed3..0d55a0e2a2136 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ResolutionContext.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ResolutionContext.java @@ -69,6 +69,11 @@ public interface ResolutionContext { */ Object getAttribute(String key); + /** + * @return the current template + */ + Template getTemplate(); + /** * * @return the evaluator diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ResolutionContextImpl.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ResolutionContextImpl.java index d8714b8cda827..84cc89c78ba30 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ResolutionContextImpl.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ResolutionContextImpl.java @@ -2,21 +2,20 @@ import java.util.Map; import java.util.concurrent.CompletionStage; -import java.util.function.Function; class ResolutionContextImpl implements ResolutionContext { private final Object data; private final Evaluator evaluator; private final Map extendingBlocks; - private final Function attributeFun; + private final TemplateInstance templateInstance; ResolutionContextImpl(Object data, - Evaluator evaluator, Map extendingBlocks, Function attributeFun) { + Evaluator evaluator, Map extendingBlocks, TemplateInstance templateInstance) { this.data = data; this.evaluator = evaluator; this.extendingBlocks = extendingBlocks; - this.attributeFun = attributeFun; + this.templateInstance = templateInstance; } @Override @@ -59,7 +58,12 @@ public SectionBlock getCurrentExtendingBlock(String name) { @Override public Object getAttribute(String key) { - return attributeFun.apply(key); + return templateInstance.getAttribute(key); + } + + @Override + public Template getTemplate() { + return templateInstance.getTemplate(); } @Override @@ -67,6 +71,10 @@ public Evaluator getEvaluator() { return evaluator; } + TemplateInstance getTemplateInstance() { + return templateInstance; + } + static class ChildResolutionContext implements ResolutionContext { private final ResolutionContext parent; @@ -134,11 +142,25 @@ public Object getAttribute(String key) { return parent.getAttribute(key); } + @Override + public Template getTemplate() { + return parent.getTemplate(); + } + @Override public Evaluator getEvaluator() { return evaluator; } + TemplateInstance getTemplateInstance() { + if (parent instanceof ResolutionContextImpl rc) { + return rc.getTemplateInstance(); + } else if (parent instanceof ChildResolutionContext child) { + return child.getTemplateInstance(); + } + throw new IllegalStateException(); + } + } } diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionNode.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionNode.java index e8af2672d4fdd..a815dfe5b241f 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionNode.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionNode.java @@ -11,6 +11,7 @@ import org.jboss.logging.Logger; +import io.quarkus.qute.ResolutionContextImpl.ChildResolutionContext; import io.quarkus.qute.SectionHelper.SectionResolutionContext; /** @@ -245,8 +246,14 @@ public ResolutionContext resolutionContext() { @Override public ResolutionContext newResolutionContext(Object data, Map extendingBlocks) { - return new ResolutionContextImpl(data, resolutionContext.getEvaluator(), extendingBlocks, - resolutionContext::getAttribute); + if (resolutionContext instanceof ResolutionContextImpl rc) { + return new ResolutionContextImpl(data, resolutionContext.getEvaluator(), extendingBlocks, + rc.getTemplateInstance()); + } else if (resolutionContext instanceof ChildResolutionContext child) { + return new ResolutionContextImpl(data, resolutionContext.getEvaluator(), extendingBlocks, + child.getTemplateInstance()); + } + throw new IllegalStateException(); } @Override diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/StrEvalNamespaceResolver.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/StrEvalNamespaceResolver.java new file mode 100644 index 0000000000000..18eead758fe5a --- /dev/null +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/StrEvalNamespaceResolver.java @@ -0,0 +1,95 @@ +package io.quarkus.qute; + +import static io.quarkus.qute.EvalSectionHelper.EVAL; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Function; + +/** + * Evaluates the string representation of the the first parameter as a template, e.g. {@code str:eval('Hello {name}!')}. + * + * @see EvalSectionHelper + */ +public class StrEvalNamespaceResolver implements NamespaceResolver { + + private volatile Engine engine; + + private final int priority; + + private final ConcurrentMap templates = new ConcurrentHashMap<>(); + + public StrEvalNamespaceResolver() { + this(-3); + } + + public StrEvalNamespaceResolver(int priority) { + this.priority = priority; + } + + public void setEngine(Engine engine) { + this.engine = engine; + } + + @Override + public CompletionStage resolve(EvalContext context) { + if (!EVAL.equals(context.getName()) || context.getParams().size() != 1) { + return Results.notFound(context); + } + CompletableFuture ret = new CompletableFuture<>(); + Expression p = context.getParams().get(0); + if (p.isLiteral()) { + // We can optimize the case where a literal is used + String contents = p.getLiteral().toString(); + resolve(ret, context, templates.computeIfAbsent(contents, new Function() { + @Override + public Template apply(String contents) { + return parse(contents, context.resolutionContext().getTemplate().getVariant().orElse(null)); + } + })); + } else { + context.evaluate(p).whenComplete((r, t) -> { + if (t != null) { + ret.completeExceptionally(t); + } else { + resolve(ret, context, + parse(r.toString(), context.resolutionContext().getTemplate().getVariant().orElse(null))); + } + }); + } + return ret; + } + + @Override + public int getPriority() { + return priority; + } + + @Override + public String getNamespace() { + return "str"; + } + + private Template parse(String contents, Variant variant) { + Engine e = engine; + if (e == null) { + throw new IllegalStateException("Engine not set"); + } + return e.parse(contents, variant); + } + + private void resolve(CompletableFuture ret, EvalContext context, Template template) { + ((TemplateImpl) template).root.resolve(context.resolutionContext()).whenComplete((r, t) -> { + if (t != null) { + ret.completeExceptionally(t); + } else { + StringBuilder sb = new StringBuilder(); + r.process(sb::append); + ret.complete(new RawString(sb.toString())); + } + }); + } + +} diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateImpl.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateImpl.java index 0396b248d2393..255b63abbffc4 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateImpl.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateImpl.java @@ -266,7 +266,7 @@ private int getCapacityAttributeValue() { private CompletionStage renderData(Object data, Consumer consumer) { CompletableFuture result = new CompletableFuture<>(); ResolutionContext rootContext = new ResolutionContextImpl(data, - engine.getEvaluator(), null, this::getAttribute); + engine.getEvaluator(), null, this); setAttribute(DataNamespaceResolver.ROOT_CONTEXT, rootContext); // Async resolution root.resolve(rootContext).whenComplete((r, t) -> { diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/EvalTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/EvalTest.java index e1ef1b4f59c44..47097adccf203 100644 --- a/independent-projects/qute/core/src/test/java/io/quarkus/qute/EvalTest.java +++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/EvalTest.java @@ -40,4 +40,21 @@ public void testInvalidTemplateContents() { .withMessageContainingAll("Parser error in the evaluated template", "unterminated expression"); } + @Test + public void testStrEvalNamespace() { + StrEvalNamespaceResolver eval = new StrEvalNamespaceResolver(); + Engine engine = Engine.builder() + .addDefaults() + .addResultMapper(new HtmlEscaper(ImmutableList.of("text/html"))) + .addNamespaceResolver(eval) + .build(); + eval.setEngine(engine); + assertEquals("Hello world!", + engine.parse("{str:eval('Hello {name}!')}").data("name", "world").render()); + assertEquals("Hello world!", + engine.parse("{str:eval(t1)}").data("t1", "Hello {name}!", "name", "world").render()); + assertEquals("<p>", + engine.parse("{str:eval('{foo}')}", Variant.forContentType(Variant.TEXT_HTML)).data("foo", "

").render()); + } + } diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/EvaluatedParamsTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/EvaluatedParamsTest.java index 7de584fdbf341..768eb623fbfc6 100644 --- a/independent-projects/qute/core/src/test/java/io/quarkus/qute/EvaluatedParamsTest.java +++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/EvaluatedParamsTest.java @@ -64,6 +64,11 @@ public Object getAttribute(String key) { return null; } + @Override + public ResolutionContext resolutionContext() { + return null; + } + @Override public CompletionStage evaluate(Expression expression) { if (expression.toOriginalString().equals("foo.bar")) { diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/NotFoundResultTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/NotFoundResultTest.java index 24b14c90b4d3e..0c706ae76d666 100644 --- a/independent-projects/qute/core/src/test/java/io/quarkus/qute/NotFoundResultTest.java +++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/NotFoundResultTest.java @@ -51,6 +51,11 @@ public Object getAttribute(String key) { return null; } + @Override + public ResolutionContext resolutionContext() { + return null; + } + @Override public CompletionStage evaluate(Expression expression) { return null; diff --git a/independent-projects/qute/generator/src/test/java/io/quarkus/qute/TestEvalContext.java b/independent-projects/qute/generator/src/test/java/io/quarkus/qute/TestEvalContext.java index 9deeb6e977238..9e92e8899f2b3 100644 --- a/independent-projects/qute/generator/src/test/java/io/quarkus/qute/TestEvalContext.java +++ b/independent-projects/qute/generator/src/test/java/io/quarkus/qute/TestEvalContext.java @@ -52,4 +52,9 @@ public Object getAttribute(String key) { return null; } + @Override + public ResolutionContext resolutionContext() { + return null; + } + }