diff --git a/src/main/java/com/hubspot/jinjava/JinjavaConfig.java b/src/main/java/com/hubspot/jinjava/JinjavaConfig.java index ccfaf7ca5..0ef3c53d6 100644 --- a/src/main/java/com/hubspot/jinjava/JinjavaConfig.java +++ b/src/main/java/com/hubspot/jinjava/JinjavaConfig.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.google.common.collect.ImmutableMap; import com.hubspot.jinjava.el.JinjavaInterpreterResolver; import com.hubspot.jinjava.el.JinjavaObjectUnwrapper; import com.hubspot.jinjava.el.JinjavaProcessors; @@ -53,158 +54,170 @@ @Value.Immutable(singleton = true) @JinjavaImmutableStyle.WithStyle -public interface JinjavaConfig { +@Value.Style( + init = "with*", + get = { "is*", "get*" }, // Detect 'get' and 'is' prefixes in accessor methods + build = "buildImpl", // This is an alias for keeping binary compatibility on the "build" method. + visibility = Value.Style.ImplementationVisibility.PACKAGE +) +public class JinjavaConfig { + + public JinjavaConfig() {} + @Value.Default - default Charset getCharset() { + public Charset getCharset() { return StandardCharsets.UTF_8; } @Value.Default - default Locale getLocale() { + public Locale getLocale() { return Locale.ENGLISH; } @Value.Default - default ZoneId getTimeZone() { + public ZoneId getTimeZone() { return ZoneOffset.UTC; } @Value.Default - default int getMaxRenderDepth() { + public int getMaxRenderDepth() { return 10; } @Value.Default - default long getMaxOutputSize() { + public long getMaxOutputSize() { return 0; } @Value.Default - default boolean isTrimBlocks() { + public boolean isTrimBlocks() { return false; } @Value.Default - default boolean isLstripBlocks() { + public boolean isLstripBlocks() { return false; } @Value.Default - default boolean isEnableRecursiveMacroCalls() { + public boolean isEnableRecursiveMacroCalls() { return false; } @Value.Default - default int getMaxMacroRecursionDepth() { + public int getMaxMacroRecursionDepth() { return 0; } - Map> getDisabled(); + @Value.Default + public Map> getDisabled() { + return ImmutableMap.of(); + } @Value.Default - default boolean isFailOnUnknownTokens() { + public boolean isFailOnUnknownTokens() { return false; } @Value.Default - default boolean isNestedInterpretationEnabled() { + public boolean isNestedInterpretationEnabled() { return false; // Default changed to false in 3.0 } @Value.Default - default RandomNumberGeneratorStrategy getRandomNumberGeneratorStrategy() { + public RandomNumberGeneratorStrategy getRandomNumberGeneratorStrategy() { return RandomNumberGeneratorStrategy.THREAD_LOCAL; } @Value.Default - default boolean isValidationMode() { + public boolean isValidationMode() { return false; } @Value.Default - default long getMaxStringLength() { + public long getMaxStringLength() { return getMaxOutputSize(); } @Value.Default - default int getMaxListSize() { + public int getMaxListSize() { return Integer.MAX_VALUE; } @Value.Default - default int getMaxMapSize() { + public int getMaxMapSize() { return Integer.MAX_VALUE; } @Value.Default - default int getRangeLimit() { + public int getRangeLimit() { return DEFAULT_RANGE_LIMIT; } @Value.Default - default int getMaxNumDeferredTokens() { + public int getMaxNumDeferredTokens() { return 1000; } @Value.Default - default InterpreterFactory getInterpreterFactory() { + public InterpreterFactory getInterpreterFactory() { return new JinjavaInterpreterFactory(); } @Value.Default - default DateTimeProvider getDateTimeProvider() { + public DateTimeProvider getDateTimeProvider() { return new CurrentDateTimeProvider(); } @Value.Default - default TokenScannerSymbols getTokenScannerSymbols() { + public TokenScannerSymbols getTokenScannerSymbols() { return new DefaultTokenScannerSymbols(); } @Value.Default - default AllowlistMethodValidator getMethodValidator() { + public AllowlistMethodValidator getMethodValidator() { return AllowlistMethodValidator.DEFAULT; } @Value.Default - default AllowlistReturnTypeValidator getReturnTypeValidator() { + public AllowlistReturnTypeValidator getReturnTypeValidator() { return AllowlistReturnTypeValidator.DEFAULT; } @Value.Default - default ELResolver getElResolver() { + public ELResolver getElResolver() { return isDefaultReadOnlyResolver() ? JinjavaInterpreterResolver.DEFAULT_RESOLVER_READ_ONLY : JinjavaInterpreterResolver.DEFAULT_RESOLVER_READ_WRITE; } @Value.Default - default boolean isDefaultReadOnlyResolver() { + public boolean isDefaultReadOnlyResolver() { return true; } @Value.Default - default ExecutionMode getExecutionMode() { + public ExecutionMode getExecutionMode() { return DefaultExecutionMode.instance(); } @Value.Default - default LegacyOverrides getLegacyOverrides() { + public LegacyOverrides getLegacyOverrides() { return LegacyOverrides.THREE_POINT_0; } @Value.Default - default boolean getEnablePreciseDivideFilter() { + public boolean getEnablePreciseDivideFilter() { return false; } @Value.Default - default boolean isEnableFilterChainOptimization() { + public boolean isEnableFilterChainOptimization() { return false; } @Value.Default - default ObjectMapper getObjectMapper() { + public ObjectMapper getObjectMapper() { ObjectMapper objectMapper = new ObjectMapper().registerModule(new Jdk8Module()); if (getLegacyOverrides().isUseSnakeCasePropertyNaming()) { objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); @@ -213,42 +226,47 @@ default ObjectMapper getObjectMapper() { } @Value.Default - default ObjectUnwrapper getObjectUnwrapper() { + public ObjectUnwrapper getObjectUnwrapper() { return new JinjavaObjectUnwrapper(); } @Value.Derived - default Features getFeatures() { + public Features getFeatures() { return new Features(getFeatureConfig()); } @Value.Default - default FeatureConfig getFeatureConfig() { + public FeatureConfig getFeatureConfig() { return FeatureConfig.newBuilder().build(); } @Value.Default - default JinjavaProcessors getProcessors() { + public JinjavaProcessors getProcessors() { return JinjavaProcessors.newBuilder().build(); } @Deprecated - default BiConsumer getNodePreProcessor() { + public BiConsumer getNodePreProcessor() { return getProcessors().getNodePreProcessor(); } @Deprecated - default boolean isIterateOverMapKeys() { + public boolean isIterateOverMapKeys() { return getLegacyOverrides().isIterateOverMapKeys(); } - class Builder extends ImmutableJinjavaConfig.Builder {} + public static class Builder extends ImmutableJinjavaConfig.Builder { + + public JinjavaConfig build() { + return super.buildImpl(); + } + } - static Builder builder() { + public static Builder builder() { return new Builder(); } - static Builder newBuilder() { + public static Builder newBuilder() { return builder(); } } diff --git a/src/main/java/com/hubspot/jinjava/LegacyOverrides.java b/src/main/java/com/hubspot/jinjava/LegacyOverrides.java index bd3732455..5192aa866 100644 --- a/src/main/java/com/hubspot/jinjava/LegacyOverrides.java +++ b/src/main/java/com/hubspot/jinjava/LegacyOverrides.java @@ -1,17 +1,14 @@ package com.hubspot.jinjava; -import org.immutables.value.Value; - /** * This class allows Jinjava to be configured to override legacy behaviour. * LegacyOverrides.NONE signifies that none of the legacy functionality will be overridden. * LegacyOverrides.ALL signifies that all new functionality will be used; avoid legacy "bugs". */ -@Value.Immutable(singleton = true) -@JinjavaImmutableStyle.WithStyle -public interface LegacyOverrides extends WithLegacyOverrides { - LegacyOverrides NONE = new Builder().build(); - LegacyOverrides THREE_POINT_0 = new Builder() +public class LegacyOverrides { + + public static final LegacyOverrides NONE = new LegacyOverrides.Builder().build(); + public static final LegacyOverrides THREE_POINT_0 = new Builder() .withEvaluateMapKeys(true) .withIterateOverMapKeys(true) .withUsePyishObjectMapper(true) @@ -22,7 +19,7 @@ public interface LegacyOverrides extends WithLegacyOverrides { .withUseTrimmingForNotesAndExpressions(true) .withKeepNullableLoopValues(true) .build(); - LegacyOverrides ALL = new Builder() + public static final LegacyOverrides ALL = new LegacyOverrides.Builder() .withEvaluateMapKeys(true) .withIterateOverMapKeys(true) .withUsePyishObjectMapper(true) @@ -33,59 +30,151 @@ public interface LegacyOverrides extends WithLegacyOverrides { .withUseTrimmingForNotesAndExpressions(true) .withKeepNullableLoopValues(true) .build(); + private final boolean evaluateMapKeys; + private final boolean iterateOverMapKeys; + private final boolean usePyishObjectMapper; + private final boolean useSnakeCasePropertyNaming; + private final boolean useNaturalOperatorPrecedence; + private final boolean parseWhitespaceControlStrictly; + private final boolean allowAdjacentTextNodes; + private final boolean useTrimmingForNotesAndExpressions; + private final boolean keepNullableLoopValues; - @Value.Default - default boolean isEvaluateMapKeys() { - return false; + private LegacyOverrides(Builder builder) { + evaluateMapKeys = builder.evaluateMapKeys; + iterateOverMapKeys = builder.iterateOverMapKeys; + usePyishObjectMapper = builder.usePyishObjectMapper; + useSnakeCasePropertyNaming = builder.useSnakeCasePropertyNaming; + useNaturalOperatorPrecedence = builder.useNaturalOperatorPrecedence; + parseWhitespaceControlStrictly = builder.parseWhitespaceControlStrictly; + allowAdjacentTextNodes = builder.allowAdjacentTextNodes; + useTrimmingForNotesAndExpressions = builder.useTrimmingForNotesAndExpressions; + keepNullableLoopValues = builder.keepNullableLoopValues; } - @Value.Default - default boolean isIterateOverMapKeys() { - return false; + public static Builder newBuilder() { + return new Builder(); } - @Value.Default - default boolean isUsePyishObjectMapper() { - return false; + public boolean isEvaluateMapKeys() { + return evaluateMapKeys; } - @Value.Default - default boolean isUseSnakeCasePropertyNaming() { - return false; + public boolean isIterateOverMapKeys() { + return iterateOverMapKeys; } - @Value.Default - default boolean isUseNaturalOperatorPrecedence() { - return false; + public boolean isUsePyishObjectMapper() { + return usePyishObjectMapper; } - @Value.Default - default boolean isParseWhitespaceControlStrictly() { - return false; + public boolean isUseSnakeCasePropertyNaming() { + return useSnakeCasePropertyNaming; } - @Value.Default - default boolean isAllowAdjacentTextNodes() { - return false; + public boolean isUseNaturalOperatorPrecedence() { + return useNaturalOperatorPrecedence; } - @Value.Default - default boolean isUseTrimmingForNotesAndExpressions() { - return false; + public boolean isParseWhitespaceControlStrictly() { + return parseWhitespaceControlStrictly; } - @Value.Default - default boolean isKeepNullableLoopValues() { - return false; + public boolean isAllowAdjacentTextNodes() { + return allowAdjacentTextNodes; } - class Builder extends ImmutableLegacyOverrides.Builder {} + public boolean isUseTrimmingForNotesAndExpressions() { + return useTrimmingForNotesAndExpressions; + } - static Builder newBuilder() { - return builder(); + public boolean isKeepNullableLoopValues() { + return keepNullableLoopValues; } - static Builder builder() { - return new Builder(); + public static class Builder { + + private boolean evaluateMapKeys = false; + private boolean iterateOverMapKeys = false; + private boolean usePyishObjectMapper = false; + private boolean useSnakeCasePropertyNaming = false; + private boolean useNaturalOperatorPrecedence = false; + private boolean parseWhitespaceControlStrictly = false; + private boolean allowAdjacentTextNodes = false; + private boolean useTrimmingForNotesAndExpressions = false; + private boolean keepNullableLoopValues = false; + + private Builder() {} + + public LegacyOverrides build() { + return new LegacyOverrides(this); + } + + public static Builder from(LegacyOverrides legacyOverrides) { + return new Builder() + .withEvaluateMapKeys(legacyOverrides.evaluateMapKeys) + .withIterateOverMapKeys(legacyOverrides.iterateOverMapKeys) + .withUsePyishObjectMapper(legacyOverrides.usePyishObjectMapper) + .withUseSnakeCasePropertyNaming(legacyOverrides.useSnakeCasePropertyNaming) + .withUseNaturalOperatorPrecedence(legacyOverrides.useNaturalOperatorPrecedence) + .withParseWhitespaceControlStrictly( + legacyOverrides.parseWhitespaceControlStrictly + ) + .withAllowAdjacentTextNodes(legacyOverrides.allowAdjacentTextNodes) + .withUseTrimmingForNotesAndExpressions( + legacyOverrides.useTrimmingForNotesAndExpressions + ); + } + + public Builder withEvaluateMapKeys(boolean evaluateMapKeys) { + this.evaluateMapKeys = evaluateMapKeys; + return this; + } + + public Builder withIterateOverMapKeys(boolean iterateOverMapKeys) { + this.iterateOverMapKeys = iterateOverMapKeys; + return this; + } + + public Builder withUsePyishObjectMapper(boolean usePyishObjectMapper) { + this.usePyishObjectMapper = usePyishObjectMapper; + return this; + } + + public Builder withUseSnakeCasePropertyNaming(boolean useSnakeCasePropertyNaming) { + this.useSnakeCasePropertyNaming = useSnakeCasePropertyNaming; + return this; + } + + public Builder withUseNaturalOperatorPrecedence( + boolean useNaturalOperatorPrecedence + ) { + this.useNaturalOperatorPrecedence = useNaturalOperatorPrecedence; + return this; + } + + public Builder withParseWhitespaceControlStrictly( + boolean parseWhitespaceControlStrictly + ) { + this.parseWhitespaceControlStrictly = parseWhitespaceControlStrictly; + return this; + } + + public Builder withAllowAdjacentTextNodes(boolean allowAdjacentTextNodes) { + this.allowAdjacentTextNodes = allowAdjacentTextNodes; + return this; + } + + public Builder withUseTrimmingForNotesAndExpressions( + boolean useTrimmingForNotesAndExpressions + ) { + this.useTrimmingForNotesAndExpressions = useTrimmingForNotesAndExpressions; + return this; + } + + public Builder withKeepNullableLoopValues(boolean keepNullableLoopValues) { + this.keepNullableLoopValues = keepNullableLoopValues; + return this; + } } } diff --git a/src/main/java/com/hubspot/jinjava/el/ext/ExtendedParser.java b/src/main/java/com/hubspot/jinjava/el/ext/ExtendedParser.java index 54726af70..543726a7b 100644 --- a/src/main/java/com/hubspot/jinjava/el/ext/ExtendedParser.java +++ b/src/main/java/com/hubspot/jinjava/el/ext/ExtendedParser.java @@ -129,7 +129,7 @@ public AstNode createAstNode(AstNode... children) { } protected AstNode interpreter() { - return new AstNull(); + return identifier(INTERPRETER); } @Override diff --git a/src/main/java/com/hubspot/jinjava/el/ext/eager/EvalResultHolder.java b/src/main/java/com/hubspot/jinjava/el/ext/eager/EvalResultHolder.java index b2bda607e..f58d05d38 100644 --- a/src/main/java/com/hubspot/jinjava/el/ext/eager/EvalResultHolder.java +++ b/src/main/java/com/hubspot/jinjava/el/ext/eager/EvalResultHolder.java @@ -76,12 +76,14 @@ static String reconstructNode( if (astNode == null) { return ""; } + if ( + astNode instanceof AstIdentifier && + ExtendedParser.INTERPRETER.equals(((AstIdentifier) astNode).getName()) + ) { + return ExtendedParser.INTERPRETER; + } preserveIdentifier = - IdentifierPreservationStrategy.preserving( - preserveIdentifier.isPreserving() || - (astNode instanceof AstIdentifier && - ExtendedParser.INTERPRETER.equals(((AstIdentifier) astNode).getName())) - ); + IdentifierPreservationStrategy.preserving(preserveIdentifier.isPreserving()); if ( preserveIdentifier.isPreserving() && !astNode.hasEvalResult() && diff --git a/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerCycleTag.java b/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerCycleTag.java index 0592ec3ad..4dc0917bf 100644 --- a/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerCycleTag.java +++ b/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerCycleTag.java @@ -245,7 +245,7 @@ private static String getIsIterable(String var, int forIndex, TagToken tagToken) ) + // modulo indexing String.format( - "{{ %s[%d %% filter:length.filter(%s, null)] }}", + "{{ %s[%d %% filter:length.filter(%s, ____int3rpr3t3r____)] }}", var, forIndex, var diff --git a/src/test/java/com/hubspot/jinjava/EagerTest.java b/src/test/java/com/hubspot/jinjava/EagerTest.java index 78d196aa0..e408d2cc2 100644 --- a/src/test/java/com/hubspot/jinjava/EagerTest.java +++ b/src/test/java/com/hubspot/jinjava/EagerTest.java @@ -216,7 +216,7 @@ public void itPreserveDeferredVariableResolvingEqualToInOrCondition() { assertThat(output) .isEqualTo( - "{% if false || exptest:equalto.evaluate('a', null, deferred) %}preserved{% endif %}" + "{% if false || exptest:equalto.evaluate('a', ____int3rpr3t3r____, deferred) %}preserved{% endif %}" ); assertThat(interpreter.getErrors()).isEmpty(); localContext.put("deferred", "a"); @@ -307,7 +307,8 @@ public void itPreservesForTag() { @Test public void itPreservesFilters() { String output = interpreter.render("{{ deferred|capitalize }}"); - assertThat(output).isEqualTo("{{ filter:capitalize.filter(deferred, null) }}"); + assertThat(output) + .isEqualTo("{{ filter:capitalize.filter(deferred, ____int3rpr3t3r____) }}"); assertThat(interpreter.getErrors()).isEmpty(); localContext.put("deferred", "foo"); assertThat(interpreter.render(output)).isEqualTo("Foo"); @@ -317,14 +318,17 @@ public void itPreservesFilters() { public void itPreservesFunctions() { String output = interpreter.render("{{ deferred|datetimeformat('%B %e, %Y') }}"); assertThat(output) - .isEqualTo("{{ filter:datetimeformat.filter(deferred, null, '%B %e, %Y') }}"); + .isEqualTo( + "{{ filter:datetimeformat.filter(deferred, ____int3rpr3t3r____, '%B %e, %Y') }}" + ); assertThat(interpreter.getErrors()).isEmpty(); } @Test public void itPreservesRandomness() { String output = interpreter.render("{{ [1, 2, 3]|shuffle }}"); - assertThat(output).isEqualTo("{{ filter:shuffle.filter([1, 2, 3], null) }}"); + assertThat(output) + .isEqualTo("{{ filter:shuffle.filter([1, 2, 3], ____int3rpr3t3r____) }}"); assertThat(interpreter.getErrors()).isEmpty(); } diff --git a/src/test/java/com/hubspot/jinjava/el/ExtendedSyntaxBuilderTest.java b/src/test/java/com/hubspot/jinjava/el/ExtendedSyntaxBuilderTest.java index 009fab1bd..ac7e6d85f 100644 --- a/src/test/java/com/hubspot/jinjava/el/ExtendedSyntaxBuilderTest.java +++ b/src/test/java/com/hubspot/jinjava/el/ExtendedSyntaxBuilderTest.java @@ -196,7 +196,12 @@ public void mapLiteral() { BaseJinjavaTest .newConfigBuilder() .withMaxOutputSize(MAX_STRING_LENGTH) - .withLegacyOverrides(LegacyOverrides.THREE_POINT_0.withEvaluateMapKeys(false)) + .withLegacyOverrides( + LegacyOverrides.Builder + .from(LegacyOverrides.THREE_POINT_0) + .withEvaluateMapKeys(false) + .build() + ) .build() ) .newInterpreter(); diff --git a/src/test/java/com/hubspot/jinjava/el/ext/ExtendedParserTest.java b/src/test/java/com/hubspot/jinjava/el/ext/ExtendedParserTest.java index 228128248..39cdc6124 100644 --- a/src/test/java/com/hubspot/jinjava/el/ext/ExtendedParserTest.java +++ b/src/test/java/com/hubspot/jinjava/el/ext/ExtendedParserTest.java @@ -11,7 +11,6 @@ import de.odysseus.el.tree.impl.ast.AstMethod; import de.odysseus.el.tree.impl.ast.AstNested; import de.odysseus.el.tree.impl.ast.AstNode; -import de.odysseus.el.tree.impl.ast.AstNull; import de.odysseus.el.tree.impl.ast.AstParameters; import de.odysseus.el.tree.impl.ast.AstString; import org.assertj.core.api.Assertions; @@ -176,10 +175,12 @@ private void assertForExpression( AstParameters astParameters = (AstParameters) astNode.getChild(1); assertThat(astParameters.getChild(0)).isInstanceOf(AstString.class); - assertThat(astParameters.getChild(1)).isInstanceOf(AstNull.class); + assertThat(astParameters.getChild(1)).isInstanceOf(AstIdentifier.class); assertThat(astParameters.getChild(2)).isInstanceOf(AstString.class); assertThat(astParameters.getChild(0).eval(null, null)).isEqualTo(leftExpected); + assertThat(((AstIdentifier) astParameters.getChild(1)).getName()) + .isEqualTo("____int3rpr3t3r____"); assertThat(astParameters.getChild(2).eval(null, null)).isEqualTo(rightExpected); } diff --git a/src/test/java/com/hubspot/jinjava/el/ext/eager/EagerAstMethodTest.java b/src/test/java/com/hubspot/jinjava/el/ext/eager/EagerAstMethodTest.java index c60117f6c..d794f6c8e 100644 --- a/src/test/java/com/hubspot/jinjava/el/ext/eager/EagerAstMethodTest.java +++ b/src/test/java/com/hubspot/jinjava/el/ext/eager/EagerAstMethodTest.java @@ -235,7 +235,7 @@ public void itPreservesUnresolvable() { fail("Should throw DeferredParsingException"); } catch (DeferredParsingException e) { assertThat(e.getDeferredEvalResult()) - .isEqualTo("filter:upper.filter(foo_object.deferred, null)"); + .isEqualTo("filter:upper.filter(foo_object.deferred, ____int3rpr3t3r____)"); } } } diff --git a/src/test/java/com/hubspot/jinjava/interpret/JinjavaInterpreterTest.java b/src/test/java/com/hubspot/jinjava/interpret/JinjavaInterpreterTest.java index 1d099dcdf..b6f1524fe 100644 --- a/src/test/java/com/hubspot/jinjava/interpret/JinjavaInterpreterTest.java +++ b/src/test/java/com/hubspot/jinjava/interpret/JinjavaInterpreterTest.java @@ -387,7 +387,10 @@ public void itInterpretsEmptyExpressions() { .newConfigBuilder() .withTimeZone(ZoneId.of("America/New_York")) .withLegacyOverrides( - LegacyOverrides.THREE_POINT_0.withParseWhitespaceControlStrictly(false) + LegacyOverrides.Builder + .from(LegacyOverrides.THREE_POINT_0) + .withParseWhitespaceControlStrictly(false) + .build() ) .build() ); diff --git a/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerImportTagTest.java b/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerImportTagTest.java index ee05c7748..58de257ed 100644 --- a/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerImportTagTest.java +++ b/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerImportTagTest.java @@ -423,7 +423,7 @@ public void itCorrectlySetsAliasedPathForSecondPass() { assertThat(firstPassResult) .isEqualTo( "{% set deferred_import_resource_path = 'import-macro.jinja' %}{% macro m.print_path_macro(var) %}\n" + - "{{ filter:print_path.filter(var, null) }}\n" + + "{{ filter:print_path.filter(var, ____int3rpr3t3r____) }}\n" + "{{ var }}\n" + "{% endmacro %}{% set deferred_import_resource_path = null %}{{ m.print_path_macro(foo) }}" ); @@ -442,7 +442,7 @@ public void itCorrectlySetsPathForSecondPass() { assertThat(firstPassResult) .isEqualTo( "{% set deferred_import_resource_path = 'import-macro.jinja' %}{% macro print_path_macro(var) %}\n" + - "{{ filter:print_path.filter(var, null) }}\n" + + "{{ filter:print_path.filter(var, ____int3rpr3t3r____) }}\n" + "{{ var }}\n" + "{% endmacro %}{% set deferred_import_resource_path = null %}{{ print_path_macro(foo) }}" ); @@ -460,9 +460,9 @@ public void itCorrectlySetsNestedPathsForSecondPass() { ); assertThat(firstPassResult) .isEqualTo( - "{% set deferred_import_resource_path = 'double-import-macro.jinja' %}{% macro print_path_macro2(var) %}{{ filter:print_path.filter(var, null) }}\n" + + "{% set deferred_import_resource_path = 'double-import-macro.jinja' %}{% macro print_path_macro2(var) %}{{ filter:print_path.filter(var, ____int3rpr3t3r____) }}\n" + "{% set deferred_import_resource_path = 'import-macro.jinja' %}{% macro print_path_macro(var) %}\n" + - "{{ filter:print_path.filter(var, null) }}\n" + + "{{ filter:print_path.filter(var, ____int3rpr3t3r____) }}\n" + "{{ var }}\n" + "{% endmacro %}{% set deferred_import_resource_path = 'double-import-macro.jinja' %}{{ print_path_macro(var) }}{% endmacro %}{% set deferred_import_resource_path = null %}{{ print_path_macro2(foo) }}" ); diff --git a/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerSetTagTest.java b/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerSetTagTest.java index f80628e69..73f484d4d 100644 --- a/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerSetTagTest.java +++ b/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerSetTagTest.java @@ -142,7 +142,9 @@ public void itDefersBlockWithFilter() { final String result = interpreter.render(template); assertThat(result) - .isEqualTo("{% set foo = filter:add.filter(2, null, deferred) %}{{ foo }}"); + .isEqualTo( + "{% set foo = filter:add.filter(2, ____int3rpr3t3r____, deferred) %}{{ foo }}" + ); assertThat( context .getDeferredTokens() @@ -177,7 +179,7 @@ public void itDefersDeferredBlockWithDeferredFilter() { assertThat(result) .isEqualTo( - "{% set foo %}{{ 1 + deferred }}{% endset %}{% set foo = filter:add.filter(foo, null, filter:int.filter(deferred, null)) %}{{ foo }}" + "{% set foo %}{{ 1 + deferred }}{% endset %}{% set foo = filter:add.filter(foo, ____int3rpr3t3r____, filter:int.filter(deferred, ____int3rpr3t3r____)) %}{{ foo }}" ); context.remove("foo"); context.put("deferred", 2); @@ -201,7 +203,7 @@ public void itDefersInDeferredExecutionModeWithFilter() { assertThat(result) .isEqualTo( - "{% set foo %}1{% endset %}{% set foo = filter:add.filter(1, null, deferred) %}{{ foo }}" + "{% set foo %}1{% endset %}{% set foo = filter:add.filter(1, ____int3rpr3t3r____, deferred) %}{{ foo }}" ); assertThat( context diff --git a/src/test/java/com/hubspot/jinjava/objects/collections/PyMapTest.java b/src/test/java/com/hubspot/jinjava/objects/collections/PyMapTest.java index 0d0a897af..0bb4bfa33 100644 --- a/src/test/java/com/hubspot/jinjava/objects/collections/PyMapTest.java +++ b/src/test/java/com/hubspot/jinjava/objects/collections/PyMapTest.java @@ -359,7 +359,12 @@ public void itDoesntUpdateKeysWithVariableNameWhenLegacy() { new Jinjava( BaseJinjavaTest .newConfigBuilder() - .withLegacyOverrides(LegacyOverrides.THREE_POINT_0.withEvaluateMapKeys(false)) + .withLegacyOverrides( + LegacyOverrides.Builder + .from(LegacyOverrides.THREE_POINT_0) + .withEvaluateMapKeys(false) + .build() + ) .build() ); assertThat( diff --git a/src/test/java/com/hubspot/jinjava/tree/TreeParserTest.java b/src/test/java/com/hubspot/jinjava/tree/TreeParserTest.java index 80fc04a27..7beba0733 100644 --- a/src/test/java/com/hubspot/jinjava/tree/TreeParserTest.java +++ b/src/test/java/com/hubspot/jinjava/tree/TreeParserTest.java @@ -257,7 +257,10 @@ public void itTrimsNotes() { BaseJinjavaTest .newConfigBuilder() .withLegacyOverrides( - LegacyOverrides.THREE_POINT_0.withUseTrimmingForNotesAndExpressions(false) + LegacyOverrides.Builder + .from(LegacyOverrides.THREE_POINT_0) + .withUseTrimmingForNotesAndExpressions(false) + .build() ) .build() ) @@ -349,7 +352,10 @@ public void itTrimsExpressions() { BaseJinjavaTest .newConfigBuilder() .withLegacyOverrides( - LegacyOverrides.THREE_POINT_0.withUseTrimmingForNotesAndExpressions(false) + LegacyOverrides.Builder + .from(LegacyOverrides.THREE_POINT_0) + .withUseTrimmingForNotesAndExpressions(false) + .build() ) .build() ) @@ -368,7 +374,10 @@ public void itDoesNotMergeAdjacentTextNodesWhenLegacyOverrideIsApplied() { BaseJinjavaTest .newConfigBuilder() .withLegacyOverrides( - LegacyOverrides.THREE_POINT_0.withAllowAdjacentTextNodes(false) + LegacyOverrides.Builder + .from(LegacyOverrides.THREE_POINT_0) + .withAllowAdjacentTextNodes(false) + .build() ) .build() ) diff --git a/src/test/java/com/hubspot/jinjava/util/EagerExpressionResolverTest.java b/src/test/java/com/hubspot/jinjava/util/EagerExpressionResolverTest.java index d3458e3bb..1065349cb 100644 --- a/src/test/java/com/hubspot/jinjava/util/EagerExpressionResolverTest.java +++ b/src/test/java/com/hubspot/jinjava/util/EagerExpressionResolverTest.java @@ -641,7 +641,7 @@ public void itHandlesDeferredExpTests() { interpreter.getContext().setThrowInterpreterErrors(true); String partiallyResolved = eagerExpressionResult.toString(); assertThat(partiallyResolved) - .isEqualTo("exptest:equalto.evaluateNegated(4, null, deferred)"); + .isEqualTo("exptest:equalto.evaluateNegated(4, ____int3rpr3t3r____, deferred)"); assertThat(eagerExpressionResult.getDeferredWords()) .containsExactlyInAnyOrder("deferred", "equalto.evaluateNegated"); context.put("deferred", 4); @@ -756,7 +756,7 @@ public void itHandlesDeferredMethod() { assertThat(eagerResolveExpression("deferred.append(foo)").toString()) .isEqualTo("deferred.append('foo')"); assertThat(eagerResolveExpression("deferred[1 + 1] | length").toString()) - .isEqualTo("filter:length.filter(deferred[2], null)"); + .isEqualTo("filter:length.filter(deferred[2], ____int3rpr3t3r____)"); } @Test @@ -823,7 +823,7 @@ public void itDoesNotSplitJsonInArrayResolvedExpression() { @Test public void itHandlesRandom() { assertThat(eagerResolveExpression("range(1)|random").toString()) - .isEqualTo("filter:random.filter(range(1), null)"); + .isEqualTo("filter:random.filter(range(1), ____int3rpr3t3r____)"); } @Test diff --git a/src/test/java/com/hubspot/jinjava/util/ObjectIteratorTest.java b/src/test/java/com/hubspot/jinjava/util/ObjectIteratorTest.java index 9c8d9f76c..f77786b50 100644 --- a/src/test/java/com/hubspot/jinjava/util/ObjectIteratorTest.java +++ b/src/test/java/com/hubspot/jinjava/util/ObjectIteratorTest.java @@ -103,7 +103,9 @@ public void testItIteratesOverValues() { public void testItIteratesOverKeys() throws Exception { JinjavaConfig config = BaseJinjavaTest .newConfigBuilder() - .withLegacyOverrides(LegacyOverrides.builder().withIterateOverMapKeys(true).build()) + .withLegacyOverrides( + LegacyOverrides.newBuilder().withIterateOverMapKeys(true).build() + ) .build(); JinjavaInterpreter.pushCurrent( new JinjavaInterpreter(new Jinjava(), new Context(), config) diff --git a/src/test/resources/eager/defers-macro-in-expression/test.expected.jinja b/src/test/resources/eager/defers-macro-in-expression/test.expected.jinja index 18ad7e65b..7443a94eb 100644 --- a/src/test/resources/eager/defers-macro-in-expression/test.expected.jinja +++ b/src/test/resources/eager/defers-macro-in-expression/test.expected.jinja @@ -1,10 +1,10 @@ 2 {% macro plus(foo, add) %}\ -{{ foo + (filter:int.filter(add, null)) }}\ +{{ foo + (filter:int.filter(add, ____int3rpr3t3r____)) }}\ {% endmacro %}\ {{ plus(deferred, 1.1) }}\ {% set deferred = deferred + 2 %} {% macro plus(foo, add) %}\ -{{ foo + (filter:int.filter(add, null)) }}\ +{{ foo + (filter:int.filter(add, ____int3rpr3t3r____)) }}\ {% endmacro %}\ {{ plus(deferred, 3.1) }} \ No newline at end of file diff --git a/src/test/resources/eager/defers-macro-in-for/test.expected.jinja b/src/test/resources/eager/defers-macro-in-for/test.expected.jinja index e9540a513..3311b8714 100644 --- a/src/test/resources/eager/defers-macro-in-for/test.expected.jinja +++ b/src/test/resources/eager/defers-macro-in-for/test.expected.jinja @@ -3,6 +3,6 @@ {% do my_list.append(num) %}\ {{ my_list }}\ {% endmacro %}\ -{% for item in filter:split.filter(macro_append(deferred), null, ',', 2) %} +{% for item in filter:split.filter(macro_append(deferred), ____int3rpr3t3r____, ',', 2) %} {{ item }} {% endfor %} diff --git a/src/test/resources/eager/defers-macro-in-if/test.expected.jinja b/src/test/resources/eager/defers-macro-in-if/test.expected.jinja index 9837ae87b..67f28d9e4 100644 --- a/src/test/resources/eager/defers-macro-in-if/test.expected.jinja +++ b/src/test/resources/eager/defers-macro-in-if/test.expected.jinja @@ -3,6 +3,6 @@ {% do my_list.append(num) %}\ {{ my_list }}\ {% endmacro %}\ -{% if [] == filter:split.filter(macro_append(deferred), null, ',', 2) %} +{% if [] == filter:split.filter(macro_append(deferred), ____int3rpr3t3r____, ',', 2) %} {{ my_list }} {% endif %} diff --git a/src/test/resources/eager/does-not-override-import-modification-in-for/test.expected.jinja b/src/test/resources/eager/does-not-override-import-modification-in-for/test.expected.jinja index e37dfc69f..0e1e4a8a2 100644 --- a/src/test/resources/eager/does-not-override-import-modification-in-for/test.expected.jinja +++ b/src/test/resources/eager/does-not-override-import-modification-in-for/test.expected.jinja @@ -11,7 +11,7 @@ {% endif %} -{% set foo = filter:join.filter([foo, 'b'], null, '') %}\ +{% set foo = filter:join.filter([foo, 'b'], ____int3rpr3t3r____, '') %}\ {% do __temp_meta_import_alias_3016318__.update({'foo': foo}) %} {% do __temp_meta_import_alias_3016318__.update({'foo': foo,'import_resource_path': 'eager/supplements/deferred-modification.jinja'}) %}\ {% endfor %}\ @@ -25,12 +25,12 @@ {% for __ignored__ in [0] %}\ {% if deferred %} -{% set foo = filter:join.filter([foo, 'a'], null, '') %}\ +{% set foo = filter:join.filter([foo, 'a'], ____int3rpr3t3r____, '') %}\ {% do __temp_meta_import_alias_3016319__.update({'foo': foo}) %} {% endif %} -{% set foo = filter:join.filter([foo, 'b'], null, '') %}\ +{% set foo = filter:join.filter([foo, 'b'], ____int3rpr3t3r____, '') %}\ {% do __temp_meta_import_alias_3016319__.update({'foo': foo}) %} {% do __temp_meta_import_alias_3016319__.update({'import_resource_path': 'eager/supplements/deferred-modification.jinja'}) %}\ {% endfor %}\ @@ -45,12 +45,12 @@ {% for __ignored__ in [0] %}\ {% if deferred %} -{% set foo = filter:join.filter([foo, 'a'], null, '') %}\ +{% set foo = filter:join.filter([foo, 'a'], ____int3rpr3t3r____, '') %}\ {% do __temp_meta_import_alias_3016318__.update({'foo': foo}) %} {% endif %} -{% set foo = filter:join.filter([foo, 'b'], null, '') %}\ +{% set foo = filter:join.filter([foo, 'b'], ____int3rpr3t3r____, '') %}\ {% do __temp_meta_import_alias_3016318__.update({'foo': foo}) %} {% do __temp_meta_import_alias_3016318__.update({'import_resource_path': 'eager/supplements/deferred-modification.jinja'}) %}\ {% endfor %}\ @@ -64,12 +64,12 @@ {% for __ignored__ in [0] %}\ {% if deferred %} -{% set foo = filter:join.filter([foo, 'a'], null, '') %}\ +{% set foo = filter:join.filter([foo, 'a'], ____int3rpr3t3r____, '') %}\ {% do __temp_meta_import_alias_3016319__.update({'foo': foo}) %} {% endif %} -{% set foo = filter:join.filter([foo, 'b'], null, '') %}\ +{% set foo = filter:join.filter([foo, 'b'], ____int3rpr3t3r____, '') %}\ {% do __temp_meta_import_alias_3016319__.update({'foo': foo}) %} {% do __temp_meta_import_alias_3016319__.update({'import_resource_path': 'eager/supplements/deferred-modification.jinja'}) %}\ {% endfor %}\ diff --git a/src/test/resources/eager/fully-defers-filtered-macro/test.expected.jinja b/src/test/resources/eager/fully-defers-filtered-macro/test.expected.jinja index 76727bbcc..4938403bd 100644 --- a/src/test/resources/eager/fully-defers-filtered-macro/test.expected.jinja +++ b/src/test/resources/eager/fully-defers-filtered-macro/test.expected.jinja @@ -1,5 +1,5 @@ {% macro flashy(foo) %}\ -{{ filter:upper.filter(foo, null) }} +{{ filter:upper.filter(foo, ____int3rpr3t3r____) }} A flashy {{ deferred }}\ .{% endmacro %}\ {% set __macro_flashy_1625622909_temp_variable_0__ %}\ @@ -12,4 +12,4 @@ BAR {% set __macro_silly_2092874071_temp_variable_0__ %}\ A silly {{ deferred }}\ .{% endset %}\ -{{ filter:upper.filter(__macro_silly_2092874071_temp_variable_0__, null) }} \ No newline at end of file +{{ filter:upper.filter(__macro_silly_2092874071_temp_variable_0__, ____int3rpr3t3r____) }} \ No newline at end of file diff --git a/src/test/resources/eager/handles-deferred-cycle-as/test.expected.jinja b/src/test/resources/eager/handles-deferred-cycle-as/test.expected.jinja index 48b005b8c..6c84f4a4f 100644 --- a/src/test/resources/eager/handles-deferred-cycle-as/test.expected.jinja +++ b/src/test/resources/eager/handles-deferred-cycle-as/test.expected.jinja @@ -1,19 +1,19 @@ {% for __ignored__ in [0] %} {% set c = [1, deferred] %} {% if exptest:iterable.evaluate(c, null) %}\ -{{ c[0 % filter:length.filter(c, null)] }}\ +{{ c[0 % filter:length.filter(c, ____int3rpr3t3r____)] }}\ {% else %}\ {{ c }}\ {% endif %} {% set c = [2, deferred] %} {% if exptest:iterable.evaluate(c, null) %}\ -{{ c[1 % filter:length.filter(c, null)] }}\ +{{ c[1 % filter:length.filter(c, ____int3rpr3t3r____)] }}\ {% else %}\ {{ c }}\ {% endif %} {% set c = [3, deferred] %} {% if exptest:iterable.evaluate(c, null) %}\ -{{ c[2 % filter:length.filter(c, null)] }}\ +{{ c[2 % filter:length.filter(c, ____int3rpr3t3r____)] }}\ {% else %}\ {{ c }}\ {% endif %}\ diff --git a/src/test/resources/eager/handles-deferred-for-loop-var-from-macro/test.expected.jinja b/src/test/resources/eager/handles-deferred-for-loop-var-from-macro/test.expected.jinja index d7b09f4d6..54d9fc69a 100644 --- a/src/test/resources/eager/handles-deferred-for-loop-var-from-macro/test.expected.jinja +++ b/src/test/resources/eager/handles-deferred-for-loop-var-from-macro/test.expected.jinja @@ -5,12 +5,12 @@ {% set __macro_doIt_1327224118_temp_variable_0__ %} {{ deferred ~ '{\"a\":\"a\"}' }} {% endset %}\ -{{ filter:upper.filter(__macro_doIt_1327224118_temp_variable_0__, null) }} +{{ filter:upper.filter(__macro_doIt_1327224118_temp_variable_0__, ____int3rpr3t3r____) }} {% set __macro_doIt_1327224118_temp_variable_1__ %} {{ deferred ~ '{\"b\":\"b\"}' }} {% endset %}\ -{{ filter:upper.filter(__macro_doIt_1327224118_temp_variable_1__, null) }} +{{ filter:upper.filter(__macro_doIt_1327224118_temp_variable_1__, ____int3rpr3t3r____) }} {% endfor %} {% endset %}\ -{{ filter:upper.filter(__macro_getData_357124436_temp_variable_0__, null) }} +{{ filter:upper.filter(__macro_getData_357124436_temp_variable_0__, ____int3rpr3t3r____) }} diff --git a/src/test/resources/eager/handles-deferred-value-in-render-filter/test.expected.jinja b/src/test/resources/eager/handles-deferred-value-in-render-filter/test.expected.jinja index 5070d0d93..21a51dcc1 100644 --- a/src/test/resources/eager/handles-deferred-value-in-render-filter/test.expected.jinja +++ b/src/test/resources/eager/handles-deferred-value-in-render-filter/test.expected.jinja @@ -1,9 +1,9 @@ -{% set __render_572171194_temp_variable__ %}\ -Hi {{ filter:escape.filter(deferred, null) }}\ +{% set __render_524436216_temp_variable__ %}\ +Hi {{ filter:escape.filter(deferred, ____int3rpr3t3r____) }}\ {% endset %}\ -{{ filter:escape_jinjava.filter(__render_572171194_temp_variable__, null) }} +{{ filter:escape_jinjava.filter(__render_524436216_temp_variable__, ____int3rpr3t3r____) }} -{% set __render_572171194_temp_variable__ %}\ -Hi {{ filter:escape.filter(deferred, null) }}\ +{% set __render_524436216_temp_variable__ %}\ +Hi {{ filter:escape.filter(deferred, ____int3rpr3t3r____) }}\ {% endset %}\ -{{ __render_572171194_temp_variable__ }} \ No newline at end of file +{{ __render_524436216_temp_variable__ }} \ No newline at end of file diff --git a/src/test/resources/eager/handles-double-import-modification/test.expected.jinja b/src/test/resources/eager/handles-double-import-modification/test.expected.jinja index 7790ece96..66acdde36 100644 --- a/src/test/resources/eager/handles-double-import-modification/test.expected.jinja +++ b/src/test/resources/eager/handles-double-import-modification/test.expected.jinja @@ -9,7 +9,7 @@ {% endif %} -{% set foo = filter:join.filter([foo, 'b'], null, '') %}\ +{% set foo = filter:join.filter([foo, 'b'], ____int3rpr3t3r____, '') %}\ {% do __temp_meta_import_alias_3016318__.update({'foo': foo}) %} {% do __temp_meta_import_alias_3016318__.update({'foo': foo,'import_resource_path': 'eager/supplements/deferred-modification.jinja'}) %}\ {% endfor %}\ @@ -28,7 +28,7 @@ {% endif %} -{% set foo = filter:join.filter([foo, 'b'], null, '') %}\ +{% set foo = filter:join.filter([foo, 'b'], ____int3rpr3t3r____, '') %}\ {% do __temp_meta_import_alias_3016319__.update({'foo': foo}) %} {% do __temp_meta_import_alias_3016319__.update({'foo': foo,'import_resource_path': 'eager/supplements/deferred-modification.jinja'}) %}\ {% endfor %}\ diff --git a/src/test/resources/eager/handles-same-name-import-var/test.expected.jinja b/src/test/resources/eager/handles-same-name-import-var/test.expected.jinja index 7059c551c..66cfa99f0 100644 --- a/src/test/resources/eager/handles-same-name-import-var/test.expected.jinja +++ b/src/test/resources/eager/handles-same-name-import-var/test.expected.jinja @@ -35,5 +35,5 @@ {% set my_var = __temp_meta_import_alias_1059697132__ %}\ {% set current_path,__temp_meta_current_path_944750549__ = __temp_meta_current_path_944750549__,null %}\ {% enddo %} -{{ filter:dictsort.filter(my_var, null, false, 'key') }} +{{ filter:dictsort.filter(my_var, ____int3rpr3t3r____, false, 'key') }} {% endif %} diff --git a/src/test/resources/eager/reconstructs-block-set-variables-in-for-loop/test.expected.jinja b/src/test/resources/eager/reconstructs-block-set-variables-in-for-loop/test.expected.jinja index 4340b7b25..d0152a7f9 100644 --- a/src/test/resources/eager/reconstructs-block-set-variables-in-for-loop/test.expected.jinja +++ b/src/test/resources/eager/reconstructs-block-set-variables-in-for-loop/test.expected.jinja @@ -2,5 +2,5 @@ {% set __macro_foo_97643642_temp_variable_0__ %} {{ deferred }} {% endset %}\ -{{ filter:int.filter(__macro_foo_97643642_temp_variable_0__, null) + 3 }} +{{ filter:int.filter(__macro_foo_97643642_temp_variable_0__, ____int3rpr3t3r____) + 3 }} {% endfor %} diff --git a/src/test/resources/eager/reconstructs-fromed-macro/test.expected.jinja b/src/test/resources/eager/reconstructs-fromed-macro/test.expected.jinja index 8e02146cf..c7213b008 100644 --- a/src/test/resources/eager/reconstructs-fromed-macro/test.expected.jinja +++ b/src/test/resources/eager/reconstructs-fromed-macro/test.expected.jinja @@ -1,6 +1,6 @@ {% set deferred_import_resource_path = 'eager/reconstructs-fromed-macro/has-macro.jinja' %}\ {% macro to_upper(param) %} - {{ filter:upper.filter(param, null) }} + {{ filter:upper.filter(param, ____int3rpr3t3r____) }} {% endmacro %}\ {% set deferred_import_resource_path = null %}\ {{ to_upper(deferred) }} \ No newline at end of file diff --git a/src/test/resources/eager/uses-unique-macro-names/test.expected.jinja b/src/test/resources/eager/uses-unique-macro-names/test.expected.jinja index e3585f49e..54c40b809 100644 --- a/src/test/resources/eager/uses-unique-macro-names/test.expected.jinja +++ b/src/test/resources/eager/uses-unique-macro-names/test.expected.jinja @@ -3,13 +3,13 @@ {% set __macro_foo_97643642_temp_variable_0__ %} Goodbye {{ myname }} {% endset %}\ -{% set a = filter:upper.filter(__macro_foo_97643642_temp_variable_0__, null) %} +{% set a = filter:upper.filter(__macro_foo_97643642_temp_variable_0__, ____int3rpr3t3r____) %} {% do %}\ {% set __temp_meta_current_path_203114534__,current_path = current_path,'eager/uses-unique-macro-names/macro-with-filter.jinja' %} {% set __macro_foo_1717337666_temp_variable_0__ %}\ Hello {{ myname }}\ {% endset %}\ -{% set b = filter:upper.filter(__macro_foo_1717337666_temp_variable_0__, null) %} +{% set b = filter:upper.filter(__macro_foo_1717337666_temp_variable_0__, ____int3rpr3t3r____) %} {% set current_path,__temp_meta_current_path_203114534__ = __temp_meta_current_path_203114534__,null %}\ {% enddo %} {{ a }}