diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Engine.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Engine.java index b7dd4173af523..8018514e85190 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Engine.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Engine.java @@ -176,4 +176,18 @@ default Template parse(String content, Variant variant) { */ Optional locate(String id); + /** + * @return {@code true} if the parser should remove standalone lines from the output, {@code false} otherwise + */ + boolean removeStandaloneLines(); + + /** + * Initializes a new {@link EngineBuilder} instance from this engine. + *

+ * The {@link EngineBuilder#iterationMetadataPrefix(String) is not set but if a + * {@link io.quarkus.qute.LoopSectionHelper.Factory} is registered then the original prefix should be honored. + * + * @return a new builder instance initialized from this engine + */ + EngineBuilder newBuilder(); } diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineBuilder.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineBuilder.java index ae7c5dc065e94..eee8b5b5595eb 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineBuilder.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineBuilder.java @@ -53,16 +53,28 @@ public final class EngineBuilder { this.useAsyncTimeout = true; } + /** + * Register the factory for all default aliases. + * + * @param factory + * @return self + * @see SectionHelperFactory#getDefaultAliases() + */ public EngineBuilder addSectionHelper(SectionHelperFactory factory) { - if (factory.cacheFactoryConfig()) { - factory = new CachedConfigSectionHelperFactory<>(factory); - } + factory = cachedFactory(factory); for (String alias : factory.getDefaultAliases()) { sectionHelperFactories.put(alias, factory); } return this; } + /** + * Register the factories for all default aliases. + * + * @param factory + * @return self + * @see SectionHelperFactory#getDefaultAliases() + */ public EngineBuilder addSectionHelpers(SectionHelperFactory... factories) { for (SectionHelperFactory factory : factories) { addSectionHelper(factory); @@ -70,7 +82,15 @@ public EngineBuilder addSectionHelpers(SectionHelperFactory... factories) { return this; } + /** + * Register the factory for all default aliases and the specified name. + * + * @param factory + * @return self + * @see SectionHelperFactory#getDefaultAliases() + */ public EngineBuilder addSectionHelper(String name, SectionHelperFactory factory) { + factory = cachedFactory(factory); addSectionHelper(factory); sectionHelperFactories.put(name, factory); return this; @@ -235,6 +255,10 @@ public EngineBuilder strictRendering(boolean value) { * {@link #addSectionHelper(SectionHelperFactory)}. *

* A valid prefix consists of alphanumeric characters and underscores. + *

+ * Keep in mind that the prefix must be set before the {@link LoopSectionHelper.Factory} is registered, for example before + * the {@link #addDefaultSectionHelpers()} method is called. In other words, the {@link LoopSectionHelper.Factory} must be + * re-registered after the prefix is set. * * @param prefix * @return self @@ -283,6 +307,13 @@ public Engine build() { return new EngineImpl(this); } + private SectionHelperFactory cachedFactory(SectionHelperFactory factory) { + if (factory instanceof CachedConfigSectionHelperFactory || !factory.cacheFactoryConfig()) { + return factory; + } + return new CachedConfigSectionHelperFactory<>(factory); + } + static class CachedConfigSectionHelperFactory implements SectionHelperFactory { private final SectionHelperFactory delegate; diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineImpl.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineImpl.java index 48e2116d1849f..7d57aae676ed8 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineImpl.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineImpl.java @@ -8,6 +8,7 @@ import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; @@ -164,6 +165,43 @@ public Optional locate(String id) { return Optional.empty(); } + @Override + public boolean removeStandaloneLines() { + return removeStandaloneLines; + } + + @Override + public EngineBuilder newBuilder() { + EngineBuilder builder = Engine.builder(); + builder.timeout(getTimeout()); + builder.useAsyncTimeout(useAsyncTimeout()); + builder.removeStandaloneLines(removeStandaloneLines()); + builder.strictRendering(getEvaluator().strictRendering()); + for (Entry> e : sectionHelperFactories.entrySet()) { + builder.addSectionHelper(e.getKey(), e.getValue()); + } + for (ValueResolver valueResolver : valueResolvers) { + builder.addValueResolver(valueResolver); + } + for (NamespaceResolver namespaceResolver : namespaceResolvers) { + builder.addNamespaceResolver(namespaceResolver); + } + for (TemplateLocator locator : locators) { + builder.addLocator(locator); + } + for (ResultMapper resultMapper : resultMappers) { + builder.addResultMapper(resultMapper); + } + for (Initializer initializer : initializers) { + builder.addTemplateInstanceInitializer(initializer); + } + builder.computeSectionHelper(sectionHelperFunc); + for (ParserHook parserHook : parserHooks) { + builder.addParserHook(parserHook); + } + return builder; + } + String generateId() { return "" + idGenerator.incrementAndGet(); } diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Evaluator.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Evaluator.java index 112aaf7e96ca4..23d112c2d6830 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Evaluator.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Evaluator.java @@ -8,11 +8,15 @@ public interface Evaluator { /** - * * @param expression * @param resolutionContext * @return the result */ CompletionStage evaluate(Expression expression, ResolutionContext resolutionContext); + /** + * @return {@code true} if strict rendering is enforced, {@code false} otherwise + */ + boolean strictRendering(); + } 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 5171f7372476e..f848b986cd7be 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 @@ -87,6 +87,11 @@ public CompletionStage evaluate(Expression expression, ResolutionContext } } + @Override + public boolean strictRendering() { + return strictRendering; + } + private CompletionStage resolveNamespace(EvalContext context, ResolutionContext resolutionContext, List parts, NamespaceResolver[] resolvers, int resolverIndex, Expression expression) { // Use the next matching namespace resolver diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/EngineTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/EngineTest.java index d85e905cd02be..90e8f085501e4 100644 --- a/independent-projects/qute/core/src/test/java/io/quarkus/qute/EngineTest.java +++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/EngineTest.java @@ -1,6 +1,7 @@ package io.quarkus.qute; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -11,6 +12,7 @@ import org.junit.jupiter.api.Test; +import io.quarkus.qute.TemplateInstance.Initializer; import io.quarkus.qute.TemplateLocator.TemplateLocation; import io.quarkus.qute.TemplateNode.Origin; @@ -74,4 +76,44 @@ public Optional getVariant() { } } + @Test + public void testNewBuilder() { + Engine engine1 = Engine.builder() + .addNamespaceResolver(NamespaceResolver.builder("foo").resolve(ec -> "baz").build()) + .addDefaults() + .strictRendering(false) + .useAsyncTimeout(false) + .timeout(20_000) + .addParserHook(new ParserHook() { + @Override + public void beforeParsing(ParserHelper parserHelper) { + parserHelper.addContentFilter(s -> s + "::{cool}"); + } + }) + .addTemplateInstanceInitializer(new Initializer() { + @Override + public void accept(TemplateInstance templateInstance) { + templateInstance.data("cool", true); + } + }) + .build(); + assertEquals("foo::baz::true", engine1.parse("{ping}::{foo:whatever}").data("ping", "foo").render()); + assertFalse(engine1.getEvaluator().strictRendering()); + assertFalse(engine1.useAsyncTimeout()); + assertEquals(20_000, engine1.getTimeout()); + + Engine engine2 = engine1.newBuilder() + .useAsyncTimeout(true) + .addValueResolver( + // This value resolver has the highest priority + ValueResolver.builder().applyToName("ping").priority(Integer.MAX_VALUE).resolveWith("pong").build()) + .build(); + + assertEquals("pong::baz::true", engine2.parse("{ping}::{foo:whatever}").data("ping", "foo").render()); + assertEquals(20_000, engine2.getTimeout()); + assertFalse(engine2.getEvaluator().strictRendering()); + assertTrue(engine2.useAsyncTimeout()); + assertEquals(engine1.getSectionHelperFactories().size(), engine2.getSectionHelperFactories().size()); + } + }