From c8551382f11bdd0744ad7fa45f667ccd75980b12 Mon Sep 17 00:00:00 2001 From: jasmith-hs Date: Mon, 9 Feb 2026 14:46:44 -0500 Subject: [PATCH] Introduce BuiltinFeatures --- .../com/hubspot/jinjava/LegacyOverrides.java | 4 ++++ .../jinjava/features/BuiltInFeatures.java | 11 ++++++++++ .../DateTimeFeatureActivationStrategy.java | 5 +++++ .../features/FeatureActivationStrategy.java | 6 +++++- .../jinjava/features/FeatureConfig.java | 21 +++++++++++++++++++ .../jinjava/features/FeatureStrategies.java | 4 ++-- .../hubspot/jinjava/features/Features.java | 4 ++++ .../jinjava/interpret/JinjavaInterpreter.java | 11 +++++----- .../expression/DefaultExpressionStrategy.java | 5 +++-- .../lib/filter/BetweenTimesFilter.java | 5 ++--- .../lib/filter/UnixTimestampFilter.java | 5 ++--- .../lib/filter/time/DateTimeFormatHelper.java | 5 +++-- .../com/hubspot/jinjava/lib/fn/Functions.java | 5 ++--- .../jinjava/tree/output/OutputList.java | 5 +++-- .../jinjava/tree/parse/TokenScanner.java | 8 ++++++- .../com/hubspot/jinjava/FeaturesTest.java | 2 +- 16 files changed, 80 insertions(+), 26 deletions(-) create mode 100644 src/main/java/com/hubspot/jinjava/features/BuiltInFeatures.java diff --git a/src/main/java/com/hubspot/jinjava/LegacyOverrides.java b/src/main/java/com/hubspot/jinjava/LegacyOverrides.java index c29fcd711..0cb6e661a 100644 --- a/src/main/java/com/hubspot/jinjava/LegacyOverrides.java +++ b/src/main/java/com/hubspot/jinjava/LegacyOverrides.java @@ -146,6 +146,10 @@ public Builder withUseSnakeCasePropertyNaming(boolean useSnakeCasePropertyNaming return this; } + /** + * Use {@link com.hubspot.jinjava.features.BuiltInFeatures#WHITESPACE_REQUIRED_WITHIN_TOKENS} instead + */ + @Deprecated public Builder withWhitespaceRequiredWithinTokens( boolean whitespaceRequiredWithinTokens ) { diff --git a/src/main/java/com/hubspot/jinjava/features/BuiltInFeatures.java b/src/main/java/com/hubspot/jinjava/features/BuiltInFeatures.java new file mode 100644 index 000000000..7722c601e --- /dev/null +++ b/src/main/java/com/hubspot/jinjava/features/BuiltInFeatures.java @@ -0,0 +1,11 @@ +package com.hubspot.jinjava.features; + +public interface BuiltInFeatures { + String WHITESPACE_REQUIRED_WITHIN_TOKENS = "whitespace_required_within_tokens"; + String FIXED_DATE_TIME_FILTER_NULL_ARG = "FIXED_DATE_TIME_FILTER_NULL_ARG"; + String ECHO_UNDEFINED = "echoUndefined"; + String PREVENT_ACCIDENTAL_EXPRESSIONS = "PREVENT_ACCIDENTAL_EXPRESSIONS"; + String IGNORE_NESTED_INTERPRETATION_PARSE_ERRORS = + "IGNORE_NESTED_INTERPRETATION_PARSE_ERRORS"; + String OUTPUT_UNDEFINED_VARIABLES_ERROR = "OUTPUT_UNDEFINED_VARIABLES_ERROR"; +} diff --git a/src/main/java/com/hubspot/jinjava/features/DateTimeFeatureActivationStrategy.java b/src/main/java/com/hubspot/jinjava/features/DateTimeFeatureActivationStrategy.java index 8fba08836..c50e4f679 100644 --- a/src/main/java/com/hubspot/jinjava/features/DateTimeFeatureActivationStrategy.java +++ b/src/main/java/com/hubspot/jinjava/features/DateTimeFeatureActivationStrategy.java @@ -20,6 +20,11 @@ public boolean isActive(Context context) { return ZonedDateTime.now().isAfter(activateAt); } + @Override + public boolean isActive() { + return false; // Not usable without context + } + public ZonedDateTime getActivateAt() { return activateAt; } diff --git a/src/main/java/com/hubspot/jinjava/features/FeatureActivationStrategy.java b/src/main/java/com/hubspot/jinjava/features/FeatureActivationStrategy.java index 2abdd59de..6ba27b206 100644 --- a/src/main/java/com/hubspot/jinjava/features/FeatureActivationStrategy.java +++ b/src/main/java/com/hubspot/jinjava/features/FeatureActivationStrategy.java @@ -3,5 +3,9 @@ import com.hubspot.jinjava.interpret.Context; public interface FeatureActivationStrategy { - boolean isActive(Context context); + default boolean isActive(Context context) { + return isActive(); + } + + boolean isActive(); } diff --git a/src/main/java/com/hubspot/jinjava/features/FeatureConfig.java b/src/main/java/com/hubspot/jinjava/features/FeatureConfig.java index cf3653d3d..7038592ce 100644 --- a/src/main/java/com/hubspot/jinjava/features/FeatureConfig.java +++ b/src/main/java/com/hubspot/jinjava/features/FeatureConfig.java @@ -1,8 +1,10 @@ package com.hubspot.jinjava.features; import com.google.common.collect.ImmutableMap; +import com.hubspot.jinjava.interpret.Context; import java.util.HashMap; import java.util.Map; +import java.util.function.Function; public class FeatureConfig { @@ -24,6 +26,25 @@ public static class Builder { private final Map features = new HashMap<>(); + @Deprecated + public Builder add(String name, Function strategyFunction) { + features.put( + name, + new FeatureActivationStrategy() { + @Override + public boolean isActive(Context context) { + return strategyFunction.apply(context); + } + + @Override + public boolean isActive() { + return false; + } + } + ); + return this; + } + public Builder add(String name, FeatureActivationStrategy strategy) { features.put(name, strategy); return this; diff --git a/src/main/java/com/hubspot/jinjava/features/FeatureStrategies.java b/src/main/java/com/hubspot/jinjava/features/FeatureStrategies.java index 9171c2e91..8ae4025f4 100644 --- a/src/main/java/com/hubspot/jinjava/features/FeatureStrategies.java +++ b/src/main/java/com/hubspot/jinjava/features/FeatureStrategies.java @@ -2,6 +2,6 @@ public class FeatureStrategies { - public static final FeatureActivationStrategy INACTIVE = c -> false; - public static final FeatureActivationStrategy ACTIVE = c -> true; + public static final FeatureActivationStrategy INACTIVE = () -> false; + public static final FeatureActivationStrategy ACTIVE = () -> true; } diff --git a/src/main/java/com/hubspot/jinjava/features/Features.java b/src/main/java/com/hubspot/jinjava/features/Features.java index 9cba433ed..5e311f322 100644 --- a/src/main/java/com/hubspot/jinjava/features/Features.java +++ b/src/main/java/com/hubspot/jinjava/features/Features.java @@ -14,6 +14,10 @@ public boolean isActive(String featureName, Context context) { return getActivationStrategy(featureName).isActive(context); } + public boolean isActive(String featureName) { + return getActivationStrategy(featureName).isActive(); + } + public FeatureActivationStrategy getActivationStrategy(String featureName) { return featureConfig.getFeature(featureName); } diff --git a/src/main/java/com/hubspot/jinjava/interpret/JinjavaInterpreter.java b/src/main/java/com/hubspot/jinjava/interpret/JinjavaInterpreter.java index ed643e06b..3577a8729 100644 --- a/src/main/java/com/hubspot/jinjava/interpret/JinjavaInterpreter.java +++ b/src/main/java/com/hubspot/jinjava/interpret/JinjavaInterpreter.java @@ -30,6 +30,7 @@ import com.hubspot.jinjava.el.ExpressionResolver; import com.hubspot.jinjava.el.ext.DeferredParsingException; import com.hubspot.jinjava.el.ext.ExtendedParser; +import com.hubspot.jinjava.features.BuiltInFeatures; import com.hubspot.jinjava.interpret.AutoCloseableSupplier.AutoCloseableImpl; import com.hubspot.jinjava.interpret.Context.TemporaryValueClosable; import com.hubspot.jinjava.interpret.ContextConfigurationIF.ErrorHandlingStrategyIF; @@ -87,9 +88,9 @@ public class JinjavaInterpreter implements PyishSerializable { "ignored_output_from_extends"; public static final String OUTPUT_UNDEFINED_VARIABLES_ERROR = - "OUTPUT_UNDEFINED_VARIABLES_ERROR"; + BuiltInFeatures.OUTPUT_UNDEFINED_VARIABLES_ERROR; public static final String IGNORE_NESTED_INTERPRETATION_PARSE_ERRORS = - "IGNORE_NESTED_INTERPRETATION_PARSE_ERRORS"; + BuiltInFeatures.IGNORE_NESTED_INTERPRETATION_PARSE_ERRORS; private final Multimap blocks = ArrayListMultimap.create(); private final LinkedList extendParentRoots = new LinkedList<>(); private final Map revertibleObjects = new HashMap<>(); @@ -288,9 +289,7 @@ public String renderFlat(String template, long renderLimit) { private TemporaryValueClosable ignoreParseErrorsIfActivated() { return config .getFeatures() - .getActivationStrategy( - JinjavaInterpreter.IGNORE_NESTED_INTERPRETATION_PARSE_ERRORS - ) + .getActivationStrategy(BuiltInFeatures.IGNORE_NESTED_INTERPRETATION_PARSE_ERRORS) .isActive(context) ? context.withErrorHandlingStrategy(ErrorHandlingStrategyIF.ignoreAll()) : TemporaryValueClosable.noOp(); @@ -664,7 +663,7 @@ public Object retraceVariable(String variable, int lineNumber, int startPosition if ( getConfig() .getFeatures() - .getActivationStrategy(OUTPUT_UNDEFINED_VARIABLES_ERROR) + .getActivationStrategy(BuiltInFeatures.OUTPUT_UNDEFINED_VARIABLES_ERROR) .isActive(context) ) { addError( diff --git a/src/main/java/com/hubspot/jinjava/lib/expression/DefaultExpressionStrategy.java b/src/main/java/com/hubspot/jinjava/lib/expression/DefaultExpressionStrategy.java index 1754f455f..96140b745 100644 --- a/src/main/java/com/hubspot/jinjava/lib/expression/DefaultExpressionStrategy.java +++ b/src/main/java/com/hubspot/jinjava/lib/expression/DefaultExpressionStrategy.java @@ -1,5 +1,6 @@ package com.hubspot.jinjava.lib.expression; +import com.hubspot.jinjava.features.BuiltInFeatures; import com.hubspot.jinjava.features.FeatureActivationStrategy; import com.hubspot.jinjava.interpret.JinjavaInterpreter; import com.hubspot.jinjava.lib.filter.EscapeFilter; @@ -12,7 +13,7 @@ public class DefaultExpressionStrategy implements ExpressionStrategy { private static final long serialVersionUID = 436239440273704843L; - public static final String ECHO_UNDEFINED = "echoUndefined"; + public static final String ECHO_UNDEFINED = BuiltInFeatures.ECHO_UNDEFINED; public RenderedOutputNode interpretOutput( ExpressionToken master, @@ -26,7 +27,7 @@ public RenderedOutputNode interpretOutput( final FeatureActivationStrategy feat = interpreter .getConfig() .getFeatures() - .getActivationStrategy(ECHO_UNDEFINED); + .getActivationStrategy(BuiltInFeatures.ECHO_UNDEFINED); if (var == null && feat.isActive(interpreter.getContext())) { return new RenderedOutputNode(master.getImage()); diff --git a/src/main/java/com/hubspot/jinjava/lib/filter/BetweenTimesFilter.java b/src/main/java/com/hubspot/jinjava/lib/filter/BetweenTimesFilter.java index 2154578ca..878fe2533 100644 --- a/src/main/java/com/hubspot/jinjava/lib/filter/BetweenTimesFilter.java +++ b/src/main/java/com/hubspot/jinjava/lib/filter/BetweenTimesFilter.java @@ -1,10 +1,9 @@ package com.hubspot.jinjava.lib.filter; -import static com.hubspot.jinjava.lib.filter.time.DateTimeFormatHelper.FIXED_DATE_TIME_FILTER_NULL_ARG; - import com.hubspot.jinjava.doc.annotations.JinjavaDoc; import com.hubspot.jinjava.doc.annotations.JinjavaParam; import com.hubspot.jinjava.doc.annotations.JinjavaSnippet; +import com.hubspot.jinjava.features.BuiltInFeatures; import com.hubspot.jinjava.features.DateTimeFeatureActivationStrategy; import com.hubspot.jinjava.features.FeatureActivationStrategy; import com.hubspot.jinjava.interpret.InvalidArgumentException; @@ -91,7 +90,7 @@ private ZonedDateTime getZonedDateTime(Object var, String position) { FeatureActivationStrategy feat = interpreter .getConfig() .getFeatures() - .getActivationStrategy(FIXED_DATE_TIME_FILTER_NULL_ARG); + .getActivationStrategy(BuiltInFeatures.FIXED_DATE_TIME_FILTER_NULL_ARG); if (feat.isActive(interpreter.getContext())) { var = ((DateTimeFeatureActivationStrategy) feat).getActivateAt(); diff --git a/src/main/java/com/hubspot/jinjava/lib/filter/UnixTimestampFilter.java b/src/main/java/com/hubspot/jinjava/lib/filter/UnixTimestampFilter.java index 930a7597e..e1fe8f298 100644 --- a/src/main/java/com/hubspot/jinjava/lib/filter/UnixTimestampFilter.java +++ b/src/main/java/com/hubspot/jinjava/lib/filter/UnixTimestampFilter.java @@ -1,10 +1,9 @@ package com.hubspot.jinjava.lib.filter; -import static com.hubspot.jinjava.lib.filter.time.DateTimeFormatHelper.FIXED_DATE_TIME_FILTER_NULL_ARG; - import com.hubspot.jinjava.doc.annotations.JinjavaDoc; import com.hubspot.jinjava.doc.annotations.JinjavaParam; import com.hubspot.jinjava.doc.annotations.JinjavaSnippet; +import com.hubspot.jinjava.features.BuiltInFeatures; import com.hubspot.jinjava.features.DateTimeFeatureActivationStrategy; import com.hubspot.jinjava.features.FeatureActivationStrategy; import com.hubspot.jinjava.interpret.InvalidArgumentException; @@ -40,7 +39,7 @@ public Object filter(Object var, JinjavaInterpreter interpreter, String... args) FeatureActivationStrategy feat = interpreter .getConfig() .getFeatures() - .getActivationStrategy(FIXED_DATE_TIME_FILTER_NULL_ARG); + .getActivationStrategy(BuiltInFeatures.FIXED_DATE_TIME_FILTER_NULL_ARG); if (feat.isActive(interpreter.getContext())) { var = ((DateTimeFeatureActivationStrategy) feat).getActivateAt(); diff --git a/src/main/java/com/hubspot/jinjava/lib/filter/time/DateTimeFormatHelper.java b/src/main/java/com/hubspot/jinjava/lib/filter/time/DateTimeFormatHelper.java index 6d65d3821..4fa1786db 100644 --- a/src/main/java/com/hubspot/jinjava/lib/filter/time/DateTimeFormatHelper.java +++ b/src/main/java/com/hubspot/jinjava/lib/filter/time/DateTimeFormatHelper.java @@ -1,6 +1,7 @@ package com.hubspot.jinjava.lib.filter.time; import com.hubspot.jinjava.JinjavaConfig; +import com.hubspot.jinjava.features.BuiltInFeatures; import com.hubspot.jinjava.features.DateTimeFeatureActivationStrategy; import com.hubspot.jinjava.features.FeatureActivationStrategy; import com.hubspot.jinjava.interpret.InvalidArgumentException; @@ -21,7 +22,7 @@ public final class DateTimeFormatHelper { public static final String FIXED_DATE_TIME_FILTER_NULL_ARG = - "FIXED_DATE_TIME_FILTER_NULL_ARG"; + BuiltInFeatures.FIXED_DATE_TIME_FILTER_NULL_ARG; private final String name; private final Function cannedFormatterFunction; @@ -118,7 +119,7 @@ public Object checkForNullVar(Object var, String name) { FeatureActivationStrategy feat = interpreter .getConfig() .getFeatures() - .getActivationStrategy(FIXED_DATE_TIME_FILTER_NULL_ARG); + .getActivationStrategy(BuiltInFeatures.FIXED_DATE_TIME_FILTER_NULL_ARG); return feat.isActive(interpreter.getContext()) ? ((DateTimeFeatureActivationStrategy) feat).getActivateAt() diff --git a/src/main/java/com/hubspot/jinjava/lib/fn/Functions.java b/src/main/java/com/hubspot/jinjava/lib/fn/Functions.java index fc41b6470..3bf154bfe 100644 --- a/src/main/java/com/hubspot/jinjava/lib/fn/Functions.java +++ b/src/main/java/com/hubspot/jinjava/lib/fn/Functions.java @@ -1,13 +1,12 @@ package com.hubspot.jinjava.lib.fn; -import static com.hubspot.jinjava.lib.filter.time.DateTimeFormatHelper.FIXED_DATE_TIME_FILTER_NULL_ARG; - import com.google.common.collect.Lists; import com.hubspot.jinjava.JinjavaConfig; import com.hubspot.jinjava.doc.annotations.JinjavaDoc; import com.hubspot.jinjava.doc.annotations.JinjavaParam; import com.hubspot.jinjava.doc.annotations.JinjavaSnippet; import com.hubspot.jinjava.el.ext.NamedParameter; +import com.hubspot.jinjava.features.BuiltInFeatures; import com.hubspot.jinjava.features.DateTimeFeatureActivationStrategy; import com.hubspot.jinjava.features.FeatureActivationStrategy; import com.hubspot.jinjava.interpret.DeferredValueException; @@ -229,7 +228,7 @@ public static String dateTimeFormat(Object var, String... format) { FeatureActivationStrategy feat = interpreter .getConfig() .getFeatures() - .getActivationStrategy(FIXED_DATE_TIME_FILTER_NULL_ARG); + .getActivationStrategy(BuiltInFeatures.FIXED_DATE_TIME_FILTER_NULL_ARG); if (feat.isActive(interpreter.getContext())) { var = ((DateTimeFeatureActivationStrategy) feat).getActivateAt(); diff --git a/src/main/java/com/hubspot/jinjava/tree/output/OutputList.java b/src/main/java/com/hubspot/jinjava/tree/output/OutputList.java index ef2967e96..1d3b84710 100644 --- a/src/main/java/com/hubspot/jinjava/tree/output/OutputList.java +++ b/src/main/java/com/hubspot/jinjava/tree/output/OutputList.java @@ -1,5 +1,6 @@ package com.hubspot.jinjava.tree.output; +import com.hubspot.jinjava.features.BuiltInFeatures; import com.hubspot.jinjava.interpret.JinjavaInterpreter; import com.hubspot.jinjava.interpret.OutputTooBigException; import com.hubspot.jinjava.interpret.TemplateError; @@ -11,7 +12,7 @@ public class OutputList { public static final String PREVENT_ACCIDENTAL_EXPRESSIONS = - "PREVENT_ACCIDENTAL_EXPRESSIONS"; + BuiltInFeatures.PREVENT_ACCIDENTAL_EXPRESSIONS; private final List nodes = new LinkedList<>(); private final List blocks = new LinkedList<>(); private final long maxOutputSize; @@ -58,7 +59,7 @@ public String getValue() { .filter(config -> config .getFeatures() - .getActivationStrategy(PREVENT_ACCIDENTAL_EXPRESSIONS) + .getActivationStrategy(BuiltInFeatures.PREVENT_ACCIDENTAL_EXPRESSIONS) .isActive(null) ) .map(config -> diff --git a/src/main/java/com/hubspot/jinjava/tree/parse/TokenScanner.java b/src/main/java/com/hubspot/jinjava/tree/parse/TokenScanner.java index e189e6df8..e8973d5d3 100644 --- a/src/main/java/com/hubspot/jinjava/tree/parse/TokenScanner.java +++ b/src/main/java/com/hubspot/jinjava/tree/parse/TokenScanner.java @@ -19,6 +19,7 @@ import com.google.common.collect.AbstractIterator; import com.hubspot.jinjava.JinjavaConfig; +import com.hubspot.jinjava.features.BuiltInFeatures; public class TokenScanner extends AbstractIterator { @@ -93,7 +94,12 @@ private Token getNextToken() { if (currPost < length) { c = is[currPost]; boolean startTokenFound = true; - if (config.getLegacyOverrides().isWhitespaceRequiredWithinTokens()) { + if ( + config + .getFeatures() + .isActive(BuiltInFeatures.WHITESPACE_REQUIRED_WITHIN_TOKENS) || + config.getLegacyOverrides().isWhitespaceRequiredWithinTokens() + ) { boolean hasNextChar = (currPost + 1) < length; boolean nextCharIsWhitespace = hasNextChar && (' ' == is[currPost + 1]); startTokenFound = nextCharIsWhitespace; diff --git a/src/test/java/com/hubspot/jinjava/FeaturesTest.java b/src/test/java/com/hubspot/jinjava/FeaturesTest.java index 19291da5a..eaaa63269 100644 --- a/src/test/java/com/hubspot/jinjava/FeaturesTest.java +++ b/src/test/java/com/hubspot/jinjava/FeaturesTest.java @@ -47,7 +47,7 @@ public void setUp() throws Exception { ZonedDateTime.of(LocalDateTime.MAX, ZoneId.systemDefault()) ) ) - .add(DELEGATING, d -> delegateActive) + .add(DELEGATING, () -> delegateActive) .build() ); }