From 389f6c0a0b9e6d24d6ac3a9432545bcebf25ee05 Mon Sep 17 00:00:00 2001 From: "marcin.szymura" Date: Fri, 24 Jan 2020 09:26:25 +0100 Subject: [PATCH] #20 Pebble Engine options. Unit tests for different settings and syntax. --- pebble/README.md | 2 +- pebble/build.gradle.kts | 2 +- pebble/docs/asciidoc/dataobjects.adoc | 33 ++-- .../knotx/te/pebble/PebbleTemplateEngine.java | 94 ++++++++---- .../pebble/PebbleTemplateEngineFactory.java | 7 +- .../{ => options}/PebbleEngineOptions.java | 81 +++++----- .../options/PebbleEngineSyntaxComposer.java | 40 +++++ .../options/PebbleEngineSyntaxOptions.java | 143 ++++++++++++++++++ .../java/io/knotx/te/pebble/package-info.java | 2 +- .../te/pebble/PebbleTemplateEngineTest.java | 97 +++++++++--- .../test/resources/data/sampleContext.json | 22 ++- .../data/sampleContextWithMissingField.json | 15 ++ .../test/resources/data/serviceContext.json | 24 +++ pebble/src/test/resources/results/service | 42 +++++ .../test/resources/results/undefinedHelper | 3 - .../templates/{empty.hbs => empty.peb} | 0 .../templates/handlebars-template.hbs | 3 - .../src/test/resources/templates/service.peb | 21 +++ .../templates/serviceCustomSyntax.peb | 21 +++ .../templates/simple-customDelimiter.hbs | 4 - .../src/test/resources/templates/simple.hbs | 3 - .../src/test/resources/templates/simple.peb | 3 + .../resources/templates/undefinedHelper.hbs | 3 - .../resources/templates/undefinedHelper.peb | 3 + 24 files changed, 538 insertions(+), 130 deletions(-) rename pebble/src/main/java/io/knotx/te/pebble/{ => options}/PebbleEngineOptions.java (59%) create mode 100644 pebble/src/main/java/io/knotx/te/pebble/options/PebbleEngineSyntaxComposer.java create mode 100644 pebble/src/main/java/io/knotx/te/pebble/options/PebbleEngineSyntaxOptions.java create mode 100644 pebble/src/test/resources/data/sampleContextWithMissingField.json create mode 100644 pebble/src/test/resources/data/serviceContext.json create mode 100644 pebble/src/test/resources/results/service delete mode 100644 pebble/src/test/resources/results/undefinedHelper rename pebble/src/test/resources/templates/{empty.hbs => empty.peb} (100%) delete mode 100644 pebble/src/test/resources/templates/handlebars-template.hbs create mode 100644 pebble/src/test/resources/templates/service.peb create mode 100644 pebble/src/test/resources/templates/serviceCustomSyntax.peb delete mode 100644 pebble/src/test/resources/templates/simple-customDelimiter.hbs delete mode 100644 pebble/src/test/resources/templates/simple.hbs create mode 100644 pebble/src/test/resources/templates/simple.peb delete mode 100644 pebble/src/test/resources/templates/undefinedHelper.hbs create mode 100644 pebble/src/test/resources/templates/undefinedHelper.peb diff --git a/pebble/README.md b/pebble/README.md index a71ccc0..0606514 100644 --- a/pebble/README.md +++ b/pebble/README.md @@ -12,7 +12,7 @@ Templates. It's key is computed basing on the `cacheKeyAlgorithm` defined in the (the default is `MD5` of the Fragment's `body`). ## How to configure -For all configuration fields and their defaults consult [io.knotx.te.pebble.PebbleEngineOptions](https://github.com/Knotx/knotx-template-engine/blob/master/handlebars/docs/asciidoc/dataobjects.adoc) +For all configuration fields and their defaults consult [io.knotx.te.pebble.options.PebbleEngineOptions](https://github.com/Knotx/knotx-template-engine/blob/master/handlebars/docs/asciidoc/dataobjects.adoc) ### Interpolation symbol By default, the Handlebars engine uses `{{` and `}}` symbols as tag delimiters. diff --git a/pebble/build.gradle.kts b/pebble/build.gradle.kts index e58bb9d..8b3760d 100644 --- a/pebble/build.gradle.kts +++ b/pebble/build.gradle.kts @@ -33,7 +33,7 @@ dependencies { implementation(group = "io.vertx", name = "vertx-service-proxy") implementation(group = "io.vertx", name = "vertx-rx-java2") implementation(group = "com.google.guava", name = "guava") - implementation(group = "io.pebbletemplates", name= "pebble", version = "3.0.0") + implementation(group = "io.pebbletemplates", name= "pebble", version = "3.1.2") testImplementation("io.knotx:knotx-junit5:${project.version}") testImplementation(group = "org.mockito", name = "mockito-core") diff --git a/pebble/docs/asciidoc/dataobjects.adoc b/pebble/docs/asciidoc/dataobjects.adoc index 6c879a2..4d4e1dc 100644 --- a/pebble/docs/asciidoc/dataobjects.adoc +++ b/pebble/docs/asciidoc/dataobjects.adoc @@ -4,7 +4,7 @@ == PebbleEngineOptions ++++ - Describes Peeble Knot configuration + Describes Pebble Knot configuration ++++ ''' @@ -22,13 +22,28 @@ Sets the algorithm used to build a hash from the handlebars snippet. The hash is Sets the size of the cache. After reaching the max size, new elements will replace the oldest one. +++ -|[[endDelimiter]]`@endDelimiter`|`String`|+++ -Sets the end delimiter for the Handlebars engine to recognize en of placeholders. By default, - the Handlebars engine uses `}}` symbols as end delimiter. -+++ -|[[startDelimiter]]`@startDelimiter`|`String`|+++ -Sets the start delimiter for the Handlebars engine to recognize start of placeholders. By - default, the Handlebars engine uses `{{` symbols as start delimiter. -+++ +|[[literalDecimalTreatedAsInteger]]`@literalDecimalTreatedAsInteger`|`Boolean`|- +|[[newLineTrimming]]`@newLineTrimming`|`Boolean`|- +|[[strictVariables]]`@strictVariables`|`Boolean`|- +|[[syntax]]`@syntax`|`link:dataobjects.html#PebbleEngineSyntaxOptions[PebbleEngineSyntaxOptions]`|- +|=== + +[[PebbleEngineSyntaxOptions]] +== PebbleEngineSyntaxOptions + + +[cols=">25%,25%,50%"] +[frame="topbot"] +|=== +^|Name | Type ^| Description +|[[delimiterCommentClose]]`@delimiterCommentClose`|`String`|- +|[[delimiterCommentOpen]]`@delimiterCommentOpen`|`String`|- +|[[delimiterExecuteClose]]`@delimiterExecuteClose`|`String`|- +|[[delimiterExecuteOpen]]`@delimiterExecuteOpen`|`String`|- +|[[delimiterInterpolationClose]]`@delimiterInterpolationClose`|`String`|- +|[[delimiterInterpolationOpen]]`@delimiterInterpolationOpen`|`String`|- +|[[delimiterPrintClose]]`@delimiterPrintClose`|`String`|- +|[[delimiterPrintOpen]]`@delimiterPrintOpen`|`String`|- +|[[whitespaceTrim]]`@whitespaceTrim`|`String`|- |=== diff --git a/pebble/src/main/java/io/knotx/te/pebble/PebbleTemplateEngine.java b/pebble/src/main/java/io/knotx/te/pebble/PebbleTemplateEngine.java index 46f5f98..dead88b 100644 --- a/pebble/src/main/java/io/knotx/te/pebble/PebbleTemplateEngine.java +++ b/pebble/src/main/java/io/knotx/te/pebble/PebbleTemplateEngine.java @@ -22,9 +22,10 @@ import com.mitchellbosecke.pebble.template.PebbleTemplate; import io.knotx.fragments.api.Fragment; import io.knotx.te.api.TemplateEngine; +import io.knotx.te.pebble.options.PebbleEngineOptions; +import io.knotx.te.pebble.options.PebbleEngineSyntaxComposer; import io.vertx.core.logging.Logger; import io.vertx.core.logging.LoggerFactory; -import io.vertx.reactivex.core.Vertx; import java.io.IOException; import java.io.StringWriter; import java.nio.charset.StandardCharsets; @@ -40,47 +41,25 @@ class PebbleTemplateEngine implements TemplateEngine { private final Cache cache; private final MessageDigest digest; - PebbleTemplateEngine(Vertx vertx, PebbleEngineOptions options) { + PebbleTemplateEngine(PebbleEngineOptions options) { LOGGER.info("<{}> instance created", this.getClass().getSimpleName()); - this.pebbleEngine = new PebbleEngine.Builder().loader(new StringLoader()).cacheActive(false) - .build(); - this.cache = CacheBuilder.newBuilder() - .maximumSize(options.getCacheSize()) - .removalListener(listener -> LOGGER.warn( - "Cache limit exceeded. Revisit 'cacheSize' setting")) - .build(); - try { - this.digest = MessageDigest.getInstance(options.getCacheKeyAlgorithm()); - } catch (NoSuchAlgorithmException e) { - LOGGER.error("No such algorithm available {}.", options.getCacheKeyAlgorithm(), e); - throw new IllegalArgumentException(e); - } + this.pebbleEngine = createPebbleEngine(options); + this.cache = createCache(options); + this.digest = tryToCreateDigest(options); } @Override public String process(Fragment fragment) { - PebbleTemplate template = template(fragment); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Processing with Peeble: {}!", fragment); - } - try { - StringWriter writer = new StringWriter(); - template.evaluate(writer, fragment.getPayload().getMap()); - return writer.toString(); - } catch (IOException e) { - LOGGER.error("Could not apply context to fragment [{}]", fragment.abbreviate(), e); - throw new IllegalStateException(e); - } + PebbleTemplate template = getTemplate(fragment); + traceProcessingFragment(fragment); + return tryToProcessOnEngine(template, fragment); } - private PebbleTemplate template(Fragment fragment) { + private PebbleTemplate getTemplate(Fragment fragment) { try { String cacheKey = getCacheKey(fragment); - return cache.get(cacheKey, () -> { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Compiled Pebble fragment [{}]", fragment); - } + traceCompilingFragment(fragment); return pebbleEngine.getTemplate(fragment.getBody()); }); } catch (ExecutionException e) { @@ -89,9 +68,60 @@ private PebbleTemplate template(Fragment fragment) { } } + private String tryToProcessOnEngine(PebbleTemplate template, Fragment fragment) { + try { + StringWriter writer = new StringWriter(); + template.evaluate(writer, fragment.getPayload().getMap()); + return writer.toString(); + } catch (IOException e) { + LOGGER.error("Could not apply context to fragment [{}]", fragment.abbreviate(), e); + throw new IllegalStateException(e); + } + } + private String getCacheKey(Fragment fragment) { byte[] cacheKeyBytes = digest.digest(fragment.getBody().getBytes(StandardCharsets.UTF_8)); return new String(cacheKeyBytes); } + private PebbleEngine createPebbleEngine(PebbleEngineOptions options) { + return new PebbleEngine.Builder() + .loader(new StringLoader()) + .cacheActive(false) + .strictVariables(options.isStrictVariables()) + .newLineTrimming(options.isNewLineTrimming()) + .syntax(PebbleEngineSyntaxComposer.compose(options.getSyntax())) + .literalDecimalTreatedAsInteger(options.isLiteralDecimalTreatedAsInteger()) + .build(); + } + + private Cache createCache(PebbleEngineOptions options) { + return CacheBuilder.newBuilder() + .maximumSize(options.getCacheSize()) + .removalListener(listener -> LOGGER.warn( + "Cache limit exceeded. Revisit 'cacheSize' setting")) + .build(); + } + + private MessageDigest tryToCreateDigest(PebbleEngineOptions options) { + try { + return MessageDigest.getInstance(options.getCacheKeyAlgorithm()); + } catch (NoSuchAlgorithmException e) { + LOGGER.error("No such algorithm available {}.", options.getCacheKeyAlgorithm(), e); + throw new IllegalArgumentException(e); + } + } + + private static void traceProcessingFragment(Fragment fragment) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Processing with Pebble: {}!", fragment); + } + } + + private static void traceCompilingFragment(Fragment fragment) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Compiled Pebble fragment [{}]", fragment); + } + } + } diff --git a/pebble/src/main/java/io/knotx/te/pebble/PebbleTemplateEngineFactory.java b/pebble/src/main/java/io/knotx/te/pebble/PebbleTemplateEngineFactory.java index 666972f..36872b8 100644 --- a/pebble/src/main/java/io/knotx/te/pebble/PebbleTemplateEngineFactory.java +++ b/pebble/src/main/java/io/knotx/te/pebble/PebbleTemplateEngineFactory.java @@ -1,4 +1,3 @@ -package io.knotx.te.pebble; /* * Copyright (C) 2019 Knot.x Project * @@ -14,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package io.knotx.te.pebble; import io.knotx.te.api.TemplateEngine; import io.knotx.te.api.TemplateEngineFactory; +import io.knotx.te.pebble.options.PebbleEngineOptions; import io.vertx.core.json.JsonObject; import io.vertx.reactivex.core.Vertx; @@ -24,11 +25,11 @@ public class PebbleTemplateEngineFactory implements TemplateEngineFactory { @Override public String getName() { - return "handlebars"; + return "pebble"; } @Override public TemplateEngine create(Vertx vertx, JsonObject config) { - return new PebbleTemplateEngine(vertx, new PebbleEngineOptions(config)); + return new PebbleTemplateEngine(new PebbleEngineOptions(config)); } } diff --git a/pebble/src/main/java/io/knotx/te/pebble/PebbleEngineOptions.java b/pebble/src/main/java/io/knotx/te/pebble/options/PebbleEngineOptions.java similarity index 59% rename from pebble/src/main/java/io/knotx/te/pebble/PebbleEngineOptions.java rename to pebble/src/main/java/io/knotx/te/pebble/options/PebbleEngineOptions.java index 41dc878..f2e7ff5 100644 --- a/pebble/src/main/java/io/knotx/te/pebble/PebbleEngineOptions.java +++ b/pebble/src/main/java/io/knotx/te/pebble/options/PebbleEngineOptions.java @@ -1,4 +1,4 @@ -package io.knotx.te.pebble;/* +/* * Copyright (C) 2019 Knot.x Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,38 +13,33 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package io.knotx.te.pebble.options; import io.vertx.codegen.annotations.DataObject; import io.vertx.core.json.JsonObject; /** - * Describes Peeble Knot configuration + * Describes Pebble Knot configuration */ @DataObject(generateConverter = true, publicConverter = false) public class PebbleEngineOptions { private static final String DEFAULT_CACHE_KEY_ALGORITHM = "MD5"; - - private static final String DEFAULT_START_DELIMITER = "{{"; - - private static final String DEFAULT_END_DELIMITER = "}}"; + private static final boolean DEFAULT_STRICT_VARIABLES = false; + private static final boolean DEFAULT_NEW_LINE_TRIMMING = true; + private static final boolean DEFAULT_LITERAL_DECIMAL_TREATED_AS_INTEGER = false; private String cacheKeyAlgorithm; private Long cacheSize; - private String startDelimiter; - private String endDelimiter; + private boolean strictVariables; + private boolean newLineTrimming; + private boolean literalDecimalTreatedAsInteger; + private PebbleEngineSyntaxOptions syntax; public PebbleEngineOptions() { init(); } - public PebbleEngineOptions(PebbleEngineOptions other) { - this.cacheKeyAlgorithm = other.cacheKeyAlgorithm; - this.cacheSize = other.cacheSize; - this.startDelimiter = other.startDelimiter; - this.endDelimiter = other.endDelimiter; - } - public PebbleEngineOptions(JsonObject json) { init(); PebbleEngineOptionsConverter.fromJson(json, this); @@ -58,8 +53,10 @@ public JsonObject toJson() { private void init() { cacheKeyAlgorithm = DEFAULT_CACHE_KEY_ALGORITHM; - startDelimiter = DEFAULT_START_DELIMITER; - endDelimiter = DEFAULT_END_DELIMITER; + strictVariables = DEFAULT_STRICT_VARIABLES; + newLineTrimming = DEFAULT_NEW_LINE_TRIMMING; + literalDecimalTreatedAsInteger = DEFAULT_LITERAL_DECIMAL_TREATED_AS_INTEGER; + syntax = new PebbleEngineSyntaxOptions(); } /** @@ -102,36 +99,36 @@ public PebbleEngineOptions setCacheKeyAlgorithm(String cacheKeyAlgorithm) { return this; } - public String getStartDelimiter() { - return startDelimiter; + public boolean isStrictVariables() { + return strictVariables; } - /** - * Sets the start delimiter for the Handlebars engine to recognize start of placeholders. By - * default, the Handlebars engine uses `{{` symbols as start delimiter. - * - * @param startDelimiter - the delimiter that will distinguish beginning of the handlebars - * expression - * @return a reference to this, so the API can be used fluently - */ - public PebbleEngineOptions setStartDelimiter(String startDelimiter) { - this.startDelimiter = startDelimiter; - return this; + public void setStrictVariables(boolean strictVariables) { + this.strictVariables = strictVariables; } - public String getEndDelimiter() { - return endDelimiter; + public PebbleEngineSyntaxOptions getSyntax() { + return syntax; } - /** - * Sets the end delimiter for the Handlebars engine to recognize en of placeholders. By default, - * the Handlebars engine uses `}}` symbols as end delimiter. - * - * @param endDelimiter - the delimiter that will distinguish end of the handlebars expression - * @return a reference to this, so the API can be used fluently - */ - public PebbleEngineOptions setEndDelimiter(String endDelimiter) { - this.endDelimiter = endDelimiter; - return this; + public void setSyntax( + PebbleEngineSyntaxOptions syntax) { + this.syntax = syntax; + } + + public boolean isNewLineTrimming() { + return newLineTrimming; + } + + public void setNewLineTrimming(boolean newLineTrimming) { + this.newLineTrimming = newLineTrimming; + } + + public boolean isLiteralDecimalTreatedAsInteger() { + return literalDecimalTreatedAsInteger; + } + + public void setLiteralDecimalTreatedAsInteger(boolean literalDecimalTreatedAsInteger) { + this.literalDecimalTreatedAsInteger = literalDecimalTreatedAsInteger; } } diff --git a/pebble/src/main/java/io/knotx/te/pebble/options/PebbleEngineSyntaxComposer.java b/pebble/src/main/java/io/knotx/te/pebble/options/PebbleEngineSyntaxComposer.java new file mode 100644 index 0000000..388c90d --- /dev/null +++ b/pebble/src/main/java/io/knotx/te/pebble/options/PebbleEngineSyntaxComposer.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2019 Knot.x Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.knotx.te.pebble.options; + +import com.mitchellbosecke.pebble.lexer.Syntax; + +public final class PebbleEngineSyntaxComposer { + + private PebbleEngineSyntaxComposer() { + // utility class + } + + public static Syntax compose(PebbleEngineSyntaxOptions options) { + Syntax.Builder builder = new Syntax.Builder() + .setCommentOpenDelimiter(options.getDelimiterCommentOpen()) + .setCommentCloseDelimiter(options.getDelimiterCommentClose()) + .setExecuteOpenDelimiter(options.getDelimiterExecuteOpen()) + .setExecuteCloseDelimiter(options.getDelimiterExecuteClose()) + .setPrintOpenDelimiter(options.getDelimiterPrintOpen()) + .setPrintCloseDelimiter(options.getDelimiterPrintClose()) + .setWhitespaceTrim(options.getWhitespaceTrim()); + builder.setInterpolationOpenDelimiter(options.getDelimiterInterpolationOpen()); + builder.setInterpolationCloseDelimiter(options.getDelimiterInterpolationOpen()); + return builder.build(); + } + +} diff --git a/pebble/src/main/java/io/knotx/te/pebble/options/PebbleEngineSyntaxOptions.java b/pebble/src/main/java/io/knotx/te/pebble/options/PebbleEngineSyntaxOptions.java new file mode 100644 index 0000000..bb5fa65 --- /dev/null +++ b/pebble/src/main/java/io/knotx/te/pebble/options/PebbleEngineSyntaxOptions.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2019 Knot.x Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.knotx.te.pebble.options; + +import io.vertx.codegen.annotations.DataObject; +import io.vertx.core.json.JsonObject; + +@DataObject(generateConverter = true, publicConverter = false) +public class PebbleEngineSyntaxOptions { + + private static final String DEFAULT_DELIMITER_COMMENT_OPEN = "{#"; + private static final String DEFAULT_DELIMITER_COMMENT_CLOSE = "#}"; + private static final String DEFAULT_DELIMITER_EXECUTE_OPEN = "{%"; + private static final String DEFAULT_DELIMITER_EXECUTE_CLOSE = "%}"; + private static final String DEFAULT_DELIMITER_PRINT_OPEN = "{{"; + private static final String DEFAULT_DELIMITER_PRINT_CLOSE = "}}"; + private static final String DEFAULT_DELIMITER_INTERPOLATION_OPEN = "#{"; + private static final String DEFAULT_DELIMITER_INTERPOLATION_CLOSE = "}"; + private static final String DEFAULT_WHITESPACE_TRIM = "-"; + + private String delimiterCommentOpen; + private String delimiterCommentClose; + private String delimiterExecuteOpen; + private String delimiterExecuteClose; + private String delimiterPrintOpen; + private String delimiterPrintClose; + private String delimiterInterpolationOpen; + private String delimiterInterpolationClose; + private String whitespaceTrim; + + public PebbleEngineSyntaxOptions() { + init(); + } + + public PebbleEngineSyntaxOptions(JsonObject json) { + init(); + PebbleEngineSyntaxOptionsConverter.fromJson(json, this); + } + + public JsonObject toJson() { + JsonObject json = new JsonObject(); + PebbleEngineSyntaxOptionsConverter.toJson(this, json); + return json; + } + + private void init() { + delimiterCommentOpen = DEFAULT_DELIMITER_COMMENT_OPEN; + delimiterCommentClose = DEFAULT_DELIMITER_COMMENT_CLOSE; + delimiterExecuteOpen = DEFAULT_DELIMITER_EXECUTE_OPEN; + delimiterExecuteClose = DEFAULT_DELIMITER_EXECUTE_CLOSE; + delimiterPrintOpen = DEFAULT_DELIMITER_PRINT_OPEN; + delimiterPrintClose = DEFAULT_DELIMITER_PRINT_CLOSE; + delimiterInterpolationOpen = DEFAULT_DELIMITER_INTERPOLATION_OPEN; + delimiterInterpolationClose = DEFAULT_DELIMITER_INTERPOLATION_CLOSE; + whitespaceTrim = DEFAULT_WHITESPACE_TRIM; + } + + public String getDelimiterCommentOpen() { + return delimiterCommentOpen; + } + + public void setDelimiterCommentOpen(String delimiterCommentOpen) { + this.delimiterCommentOpen = delimiterCommentOpen; + } + + public String getDelimiterCommentClose() { + return delimiterCommentClose; + } + + public void setDelimiterCommentClose(String delimiterCommentClose) { + this.delimiterCommentClose = delimiterCommentClose; + } + + public String getDelimiterExecuteOpen() { + return delimiterExecuteOpen; + } + + public void setDelimiterExecuteOpen(String delimiterExecuteOpen) { + this.delimiterExecuteOpen = delimiterExecuteOpen; + } + + public String getDelimiterExecuteClose() { + return delimiterExecuteClose; + } + + public void setDelimiterExecuteClose(String delimiterExecuteClose) { + this.delimiterExecuteClose = delimiterExecuteClose; + } + + public String getDelimiterPrintOpen() { + return delimiterPrintOpen; + } + + public void setDelimiterPrintOpen(String delimiterPrintOpen) { + this.delimiterPrintOpen = delimiterPrintOpen; + } + + public String getDelimiterPrintClose() { + return delimiterPrintClose; + } + + public void setDelimiterPrintClose(String delimiterPrintClose) { + this.delimiterPrintClose = delimiterPrintClose; + } + + public String getDelimiterInterpolationOpen() { + return delimiterInterpolationOpen; + } + + public void setDelimiterInterpolationOpen(String delimiterInterpolationOpen) { + this.delimiterInterpolationOpen = delimiterInterpolationOpen; + } + + public String getDelimiterInterpolationClose() { + return delimiterInterpolationClose; + } + + public void setDelimiterInterpolationClose(String delimiterInterpolationClose) { + this.delimiterInterpolationClose = delimiterInterpolationClose; + } + + public String getWhitespaceTrim() { + return whitespaceTrim; + } + + public void setWhitespaceTrim(String whitespaceTrim) { + this.whitespaceTrim = whitespaceTrim; + } + +} diff --git a/pebble/src/main/java/io/knotx/te/pebble/package-info.java b/pebble/src/main/java/io/knotx/te/pebble/package-info.java index c9c6892..572e1ca 100644 --- a/pebble/src/main/java/io/knotx/te/pebble/package-info.java +++ b/pebble/src/main/java/io/knotx/te/pebble/package-info.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@ModuleGen(name = "knotx-te-peeble", groupPackage = "io.knotx") +@ModuleGen(name = "knotx-te-pebble", groupPackage = "io.knotx") package io.knotx.te.pebble; import io.vertx.codegen.annotations.ModuleGen; diff --git a/pebble/src/test/java/io/knotx/te/pebble/PebbleTemplateEngineTest.java b/pebble/src/test/java/io/knotx/te/pebble/PebbleTemplateEngineTest.java index 7257500..4f767fd 100644 --- a/pebble/src/test/java/io/knotx/te/pebble/PebbleTemplateEngineTest.java +++ b/pebble/src/test/java/io/knotx/te/pebble/PebbleTemplateEngineTest.java @@ -16,23 +16,27 @@ package io.knotx.te.pebble; import static io.knotx.junit5.assertions.KnotxAssertions.assertEqualsIgnoreWhitespace; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.when; +import com.google.common.util.concurrent.UncheckedExecutionException; +import com.mitchellbosecke.pebble.error.AttributeNotFoundException; +import com.mitchellbosecke.pebble.error.ParserException; +import com.mitchellbosecke.pebble.error.RootAttributeNotFoundException; import io.knotx.fragments.api.Fragment; import io.knotx.junit5.util.FileReader; +import io.knotx.te.pebble.options.PebbleEngineOptions; +import io.knotx.te.pebble.options.PebbleEngineSyntaxOptions; import io.vertx.core.json.JsonObject; -import io.vertx.reactivex.core.Vertx; import java.io.IOException; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.Mockito; class PebbleTemplateEngineTest { - /** - * null, HandlebarsTemplateEngine does not use Vert.x - */ - private Vertx vertx; private PebbleEngineOptions options; @BeforeEach @@ -40,9 +44,52 @@ void setUp() { options = new PebbleEngineOptions().setCacheSize(100L); } + @Test + @DisplayName("Expect template with custom syntax to be filled properly") + void renderTemplateWithCustomDelimiters() throws IOException { + options.setSyntax(getCustomSyntaxOptions()); + final PebbleTemplateEngine templateEngine = new PebbleTemplateEngine(options); + final Fragment fragment = mockFragmentFromFile("templates/serviceCustomSyntax.peb", + "data/serviceContext.json"); + final String result = templateEngine.process(fragment).trim(); + final String expected = FileReader.readText("results/service").trim(); + assertEqualsIgnoreWhitespace(expected, result); + } + + @Test + @DisplayName("Expect exception to be thrown when context is empty and strict mode is enabled") + void process_whenStrictMode_emptyContext_expectException() throws IOException { + options.setStrictVariables(true); + final PebbleTemplateEngine templateEngine = new PebbleTemplateEngine(options); + final Fragment fragment = mockFragmentFromFile("templates/simple.peb", + "data/emptyContext.json"); + assertThrows(RootAttributeNotFoundException.class, () -> templateEngine.process(fragment)); + } + + @Test + @DisplayName("Expect exception to be thrown when context does not contain referenced argument and strict mode is enabled") + void process_whenStrictMode_expectException() throws IOException { + options.setStrictVariables(true); + final PebbleTemplateEngine templateEngine = new PebbleTemplateEngine(options); + final Fragment fragment = mockFragmentFromFile("templates/simple.peb", + "data/sampleContextWithMissingField.json"); + assertThrows(AttributeNotFoundException.class, () -> templateEngine.process(fragment)); + } + + @Test + @DisplayName("Expect exception to be thrown when an not existent tag is present") + void renderTemplateWithNotExistentTag() throws IOException { + options.setStrictVariables(true); + final PebbleTemplateEngine templateEngine = new PebbleTemplateEngine(options); + final Fragment fragment = mockFragmentFromFile("templates/undefinedHelper.peb", + "data/sampleContext.json"); + Exception exception = assertThrows(UncheckedExecutionException.class, () -> templateEngine.process(fragment)); + assertEquals(ParserException.class, exception.getCause().getClass()); + } + @Test void process_whenDefaultOptions_expectMarkup() throws IOException { - final PebbleTemplateEngine templateEngine = new PebbleTemplateEngine(vertx, options); + final PebbleTemplateEngine templateEngine = new PebbleTemplateEngine(options); final Fragment fragment = mockFragmentFromFile("templates/simple.peb", "data/sampleContext.json"); @@ -52,20 +99,19 @@ void process_whenDefaultOptions_expectMarkup() throws IOException { } @Test - void process_whenCustomDelimiter_expectMarkup() throws IOException { - options.setStartDelimiter("<&").setEndDelimiter("&>"); - final PebbleTemplateEngine templateEngine = new PebbleTemplateEngine(vertx, options); + void process_whenServiceExample_expectMarkup() throws IOException { + final PebbleTemplateEngine templateEngine = new PebbleTemplateEngine(options); - final Fragment fragment = mockFragmentFromFile("templates/simple-customDelimiter.peb", - "data/sampleContext.json"); + final Fragment fragment = mockFragmentFromFile("templates/service.peb", + "data/serviceContext.json"); final String result = templateEngine.process(fragment).trim(); - final String expected = FileReader.readText("results/customDelimiter").trim(); + final String expected = FileReader.readText("results/service").trim(); assertEqualsIgnoreWhitespace(expected, result); } @Test void process_whenEmptyContext_expectMarkup() throws IOException { - final PebbleTemplateEngine templateEngine = new PebbleTemplateEngine(vertx, options); + final PebbleTemplateEngine templateEngine = new PebbleTemplateEngine(options); final Fragment fragment = mockFragmentFromFile("templates/simple.peb", "data/emptyContext.json"); final String result = templateEngine.process(fragment).trim(); @@ -75,7 +121,7 @@ void process_whenEmptyContext_expectMarkup() throws IOException { @Test void process_whenEmptyContent_expectMarkup() throws IOException { - final PebbleTemplateEngine templateEngine = new PebbleTemplateEngine(vertx, options); + final PebbleTemplateEngine templateEngine = new PebbleTemplateEngine(options); final Fragment fragment = mockFragmentFromFile("templates/empty.peb", "data/sampleContext.json"); final String result = templateEngine.process(fragment).trim(); @@ -83,16 +129,6 @@ void process_whenEmptyContent_expectMarkup() throws IOException { assertEqualsIgnoreWhitespace(expected, result); } - @Test - void process_whenUndefinedpebHelperSpotted_expectMarkup() throws IOException { - final PebbleTemplateEngine templateEngine = new PebbleTemplateEngine(vertx, options); - final Fragment fragment = mockFragmentFromFile("templates/undefinedHelper.peb", - "data/sampleContext.json"); - final String result = templateEngine.process(fragment).trim(); - final String expected = FileReader.readText("results/undefinedHelper").trim(); - assertEqualsIgnoreWhitespace(expected, result); - } - private Fragment mockFragmentFromFile(String bodyFilePath, String contextFilePath) throws IOException { final String body = FileReader.readText(bodyFilePath).trim(); @@ -105,4 +141,17 @@ private Fragment mockFragmentFromFile(String bodyFilePath, String contextFilePat return mockedFragment; } + private PebbleEngineSyntaxOptions getCustomSyntaxOptions() { + PebbleEngineSyntaxOptions customSyntaxOptions = new PebbleEngineSyntaxOptions(); + customSyntaxOptions.setDelimiterCommentOpen("/*"); + customSyntaxOptions.setDelimiterCommentClose("*/"); + customSyntaxOptions.setDelimiterExecuteOpen("<<"); + customSyntaxOptions.setDelimiterExecuteClose(">>"); + customSyntaxOptions.setDelimiterPrintOpen("<|"); + customSyntaxOptions.setDelimiterPrintClose("|>"); + customSyntaxOptions.setDelimiterInterpolationOpen("${"); + customSyntaxOptions.setDelimiterInterpolationClose("}"); + return customSyntaxOptions; + } + } diff --git a/pebble/src/test/resources/data/sampleContext.json b/pebble/src/test/resources/data/sampleContext.json index e9dd38c..ca9599d 100644 --- a/pebble/src/test/resources/data/sampleContext.json +++ b/pebble/src/test/resources/data/sampleContext.json @@ -14,5 +14,25 @@ "bar", true ] - } + }, + "items": [ + { + "title": "First item", + "content": "First item's content", + "category": "Category 1" + }, + { + "title": "Second item", + "content": "Second item's content", + "category": "Category 2" + }, + { + "title": "Third item", + "content": "Third item's content", + "category": "Undefined" + }, + { + "title": "Item without category or content" + } + ] } diff --git a/pebble/src/test/resources/data/sampleContextWithMissingField.json b/pebble/src/test/resources/data/sampleContextWithMissingField.json new file mode 100644 index 0000000..8fee83d --- /dev/null +++ b/pebble/src/test/resources/data/sampleContextWithMissingField.json @@ -0,0 +1,15 @@ +{ + "sample": { + "result": { + "first": "First Message" + }, + "arr": [ + 1, + 2, + 3, + "foo", + "bar", + true + ] + } +} diff --git a/pebble/src/test/resources/data/serviceContext.json b/pebble/src/test/resources/data/serviceContext.json new file mode 100644 index 0000000..3ba5105 --- /dev/null +++ b/pebble/src/test/resources/data/serviceContext.json @@ -0,0 +1,24 @@ +{ + "items": [ + { + "swatches": [ + { + "all_swatch_ids": "PX1|PC2|PG1", + "all_swatch_links": "http://my-website.com/products/1|http://my-website.com/products/2|http://my-website.com/products/3", + "all_swatch_images": "http://my-images.com/swatch1.jpg|http://my-images.com/swatch2.jpg|http://my-images.com/swatch3.jpg", + "all_swatch_names": "Product 1|Product 2|Product 3", + "description": "Sample product 1 description", + "title": "My product 1" + }, + { + "all_swatch_ids": "A1|A2|A3", + "all_swatch_links": "http://my-website.com/products/1|http://my-website.com/products/2|http://my-website.com/products/3", + "all_swatch_images": "http://my-images.com/swatch1.jpg|http://my-images.com/swatch2.jpg|http://my-images.com/swatch3.jpg", + "all_swatch_names": "Product 1|Product 2|Product 3", + "description": "Sample product 2 description", + "title": "My product 2" + } + ] + } + ] +} diff --git a/pebble/src/test/resources/results/service b/pebble/src/test/resources/results/service new file mode 100644 index 0000000..7fb0782 --- /dev/null +++ b/pebble/src/test/resources/results/service @@ -0,0 +1,42 @@ +
+ + +
diff --git a/pebble/src/test/resources/results/undefinedHelper b/pebble/src/test/resources/results/undefinedHelper deleted file mode 100644 index d5f8265..0000000 --- a/pebble/src/test/resources/results/undefinedHelper +++ /dev/null @@ -1,3 +0,0 @@ - First: First Message - Second: Very Long Second Foo Message !! 2 &&%^$ - Empty: diff --git a/pebble/src/test/resources/templates/empty.hbs b/pebble/src/test/resources/templates/empty.peb similarity index 100% rename from pebble/src/test/resources/templates/empty.hbs rename to pebble/src/test/resources/templates/empty.peb diff --git a/pebble/src/test/resources/templates/handlebars-template.hbs b/pebble/src/test/resources/templates/handlebars-template.hbs deleted file mode 100644 index 6b7395e..0000000 --- a/pebble/src/test/resources/templates/handlebars-template.hbs +++ /dev/null @@ -1,3 +0,0 @@ - First: {{sample.result.first}} - Second: {{sample.result.second.foo}} - Array: {{#each sample.arr}}{{this}} {{/each}} diff --git a/pebble/src/test/resources/templates/service.peb b/pebble/src/test/resources/templates/service.peb new file mode 100644 index 0000000..5248069 --- /dev/null +++ b/pebble/src/test/resources/templates/service.peb @@ -0,0 +1,21 @@ +{% for item in items %} +
+ {% for swatch in item.swatches %} + {% set links = swatch.all_swatch_links | split('\|') %} + {% set ids = swatch.all_swatch_ids | split('\|') %} + {% set images = swatch.all_swatch_images | split('\|') %} +
    + {% for link in links %} +
  • +

    {{ loop.index + 1 }}

    + + + +
  • + {% endfor %} +
+ {% endfor %} +
+{% endfor %} + +{# This is a comment and will not be printed #} diff --git a/pebble/src/test/resources/templates/serviceCustomSyntax.peb b/pebble/src/test/resources/templates/serviceCustomSyntax.peb new file mode 100644 index 0000000..1162100 --- /dev/null +++ b/pebble/src/test/resources/templates/serviceCustomSyntax.peb @@ -0,0 +1,21 @@ +<< for item in items >> +
+ << for swatch in item.swatches >> + << set links = swatch.all_swatch_links | split('\|') >> + << set ids = swatch.all_swatch_ids | split('\|') >> + << set images = swatch.all_swatch_images | split('\|') >> +
    + << for link in links >> +
  • +

    <| loop.index + 1 |>

    + + + +
  • + << endfor >> +
+ << endfor >> +
+<< endfor >> + +/* This is a comment and will not be printed */ diff --git a/pebble/src/test/resources/templates/simple-customDelimiter.hbs b/pebble/src/test/resources/templates/simple-customDelimiter.hbs deleted file mode 100644 index 8c3d88b..0000000 --- a/pebble/src/test/resources/templates/simple-customDelimiter.hbs +++ /dev/null @@ -1,4 +0,0 @@ - First: <&sample.result.first&> - Second: <&sample.result.second.foo&> - Array: <&#each sample.arr&><&this&> <&/each&> - {{sample.result.first}} diff --git a/pebble/src/test/resources/templates/simple.hbs b/pebble/src/test/resources/templates/simple.hbs deleted file mode 100644 index 6b7395e..0000000 --- a/pebble/src/test/resources/templates/simple.hbs +++ /dev/null @@ -1,3 +0,0 @@ - First: {{sample.result.first}} - Second: {{sample.result.second.foo}} - Array: {{#each sample.arr}}{{this}} {{/each}} diff --git a/pebble/src/test/resources/templates/simple.peb b/pebble/src/test/resources/templates/simple.peb new file mode 100644 index 0000000..7c54c20 --- /dev/null +++ b/pebble/src/test/resources/templates/simple.peb @@ -0,0 +1,3 @@ + First: {{ sample.result.first }} + Second: {{sample.result.second.foo }} + Array: {% for this in sample.arr %} {{this}} {% endfor %} diff --git a/pebble/src/test/resources/templates/undefinedHelper.hbs b/pebble/src/test/resources/templates/undefinedHelper.hbs deleted file mode 100644 index cb707b3..0000000 --- a/pebble/src/test/resources/templates/undefinedHelper.hbs +++ /dev/null @@ -1,3 +0,0 @@ - First: {{sample.result.first}} - Second: {{sample.result.second.foo}} - Empty: {{#notDefinedSymbol}}This will be not printed{{/notDefinedSymbol}} diff --git a/pebble/src/test/resources/templates/undefinedHelper.peb b/pebble/src/test/resources/templates/undefinedHelper.peb new file mode 100644 index 0000000..5cf870b --- /dev/null +++ b/pebble/src/test/resources/templates/undefinedHelper.peb @@ -0,0 +1,3 @@ + First: {{ sample.result.first}} + Second: {{sample.result.second.foo}} + Empty: {{#notDefinedSymbol}}This will cause exception{{/notDefinedSymbol}}