Skip to content

Commit

Permalink
issue #520: provide option to customize stock extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
pepperbob committed Nov 29, 2020
1 parent 74fdbec commit c016ba9
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 40 deletions.
67 changes: 27 additions & 40 deletions pebble/src/main/java/com/mitchellbosecke/pebble/PebbleEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,8 @@
import com.mitchellbosecke.pebble.cache.template.ConcurrentMapTemplateCache;
import com.mitchellbosecke.pebble.cache.template.NoOpTemplateCache;
import com.mitchellbosecke.pebble.error.LoaderException;
import com.mitchellbosecke.pebble.extension.Extension;
import com.mitchellbosecke.pebble.extension.ExtensionRegistry;
import com.mitchellbosecke.pebble.extension.NodeVisitorFactory;
import com.mitchellbosecke.pebble.extension.core.AttributeResolverExtension;
import com.mitchellbosecke.pebble.extension.core.CoreExtension;
import com.mitchellbosecke.pebble.extension.escaper.EscaperExtension;
import com.mitchellbosecke.pebble.extension.*;
import com.mitchellbosecke.pebble.extension.escaper.EscapingStrategy;
import com.mitchellbosecke.pebble.extension.i18n.I18nExtension;
import com.mitchellbosecke.pebble.lexer.LexerImpl;
import com.mitchellbosecke.pebble.lexer.Syntax;
import com.mitchellbosecke.pebble.lexer.TokenStream;
Expand All @@ -44,10 +38,11 @@
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.function.Function;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -277,8 +272,6 @@ public static class Builder {

private Loader<?> loader;

private final List<Extension> userProvidedExtensions = new ArrayList<>();

private Syntax syntax;

private boolean strictVariables = false;
Expand All @@ -297,18 +290,16 @@ public static class Builder {

private PebbleCache<CacheKey, Object> tagCache;

private final EscaperExtension escaperExtension = new EscaperExtension();

private boolean literalDecimalTreatedAsInteger = false;

private boolean greedyMatchMethod = false;

private boolean allowOverrideCoreOperators = false;

private boolean literalNumbersAsBigDecimals = false;

private MethodAccessValidator methodAccessValidator = new BlacklistMethodAccessValidator();

private ExtensionRegistryFactory factory = new ExtensionRegistryFactory();

/**
* Creates the builder.
*/
Expand All @@ -334,7 +325,7 @@ public Builder loader(Loader<?> loader) {
* @return This builder object
*/
public Builder extension(Extension... extensions) {
Collections.addAll(this.userProvidedExtensions, extensions);
this.factory.extension(extensions);
return this;
}

Expand Down Expand Up @@ -455,7 +446,7 @@ public Builder tagCache(PebbleCache<CacheKey, Object> tagCache) {
* @return This builder object
*/
public Builder autoEscaping(boolean autoEscaping) {
this.escaperExtension.setAutoEscaping(autoEscaping);
this.factory.autoEscaping(autoEscaping);
return this;
}

Expand All @@ -466,7 +457,7 @@ public Builder autoEscaping(boolean autoEscaping) {
* @return This builder object
*/
public Builder allowOverrideCoreOperators(boolean allowOverrideCoreOperators) {
this.allowOverrideCoreOperators = allowOverrideCoreOperators;
this.factory.allowOverrideCoreOperators(allowOverrideCoreOperators);
return this;
}

Expand All @@ -477,7 +468,7 @@ public Builder allowOverrideCoreOperators(boolean allowOverrideCoreOperators) {
* @return This builder object
*/
public Builder defaultEscapingStrategy(String strategy) {
this.escaperExtension.setDefaultStrategy(strategy);
this.factory.defaultEscapingStrategy(strategy);
return this;
}

Expand All @@ -489,7 +480,7 @@ public Builder defaultEscapingStrategy(String strategy) {
* @return This builder object
*/
public Builder addEscapingStrategy(String name, EscapingStrategy strategy) {
this.escaperExtension.addEscapingStrategy(name, strategy);
this.factory.addEscapingStrategy(name, strategy);
return this;
}

Expand Down Expand Up @@ -566,14 +557,30 @@ public Builder greedyMatchMethod(boolean greedyMatchMethod) {
return this;
}

/**
* Registeres customizers per extension class which are called before the extension is registered
* into the {@link PebbleEngine}.
*
* Note that per extension class only the last customizer will be taken into account.
*
* @param clazz The Extension class the customizer should target
* @param customizer The customizer which is called before registering the target extension
* @param <T>
* @return This build object
*/
public <T extends Extension> Builder addExtensionCustomizer(Class<T> clazz, Function<Extension, ExtensionCustomizer> customizer) {
this.factory.addExtensionCustomizer(clazz, customizer::apply);
return this;
}

/**
* Creates the PebbleEngine instance.
*
* @return A PebbleEngine object that can be used to create PebbleTemplate objects.
*/
public PebbleEngine build() {

ExtensionRegistry extensionRegistry = this.buildExtensionRegistry();
ExtensionRegistry extensionRegistry = this.factory.buildExtensionRegistry();

// default loader
if (this.loader == null) {
Expand Down Expand Up @@ -617,26 +624,6 @@ public PebbleEngine build() {
this.tagCache, this.templateCache,
this.executorService, extensionRegistry, parserOptions, evaluationOptions);
}

private ExtensionRegistry buildExtensionRegistry() {
ExtensionRegistry extensionRegistry = new ExtensionRegistry();

extensionRegistry.addExtension(new CoreExtension());
extensionRegistry.addExtension(this.escaperExtension);
extensionRegistry.addExtension(new I18nExtension());

for (Extension userProvidedExtension : this.userProvidedExtensions) {
if (this.allowOverrideCoreOperators) {
extensionRegistry.addOperatorOverridingExtension(userProvidedExtension);
} else {
extensionRegistry.addExtension(userProvidedExtension);
}
}

extensionRegistry.addExtension(new AttributeResolverExtension());

return extensionRegistry;
}
}

public EvaluationOptions getEvaluationOptions() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.mitchellbosecke.pebble.extension;

import com.mitchellbosecke.pebble.attributes.AttributeResolver;
import com.mitchellbosecke.pebble.operator.BinaryOperator;
import com.mitchellbosecke.pebble.operator.UnaryOperator;
import com.mitchellbosecke.pebble.tokenParser.TokenParser;

import java.util.List;
import java.util.Map;

public abstract class ExtensionCustomizer implements Extension {

private final Extension delegate;

public ExtensionCustomizer(Extension delegate) {
this.delegate = delegate;
}

@Override
public Map<String, Filter> getFilters() {
return delegate.getFilters();
}

@Override
public Map<String, Test> getTests() {
return delegate.getTests();
}

@Override
public Map<String, Function> getFunctions() {
return delegate.getFunctions();
}

@Override
public List<TokenParser> getTokenParsers() {
return delegate.getTokenParsers();
}

@Override
public List<BinaryOperator> getBinaryOperators() {
return delegate.getBinaryOperators();
}

@Override
public List<UnaryOperator> getUnaryOperators() {
return delegate.getUnaryOperators();
}

@Override
public Map<String, Object> getGlobalVariables() {
return delegate.getGlobalVariables();
}

@Override
public List<NodeVisitorFactory> getNodeVisitors() {
return delegate.getNodeVisitors();
}

@Override
public List<AttributeResolver> getAttributeResolver() {
return delegate.getAttributeResolver();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.mitchellbosecke.pebble.extension;

import com.mitchellbosecke.pebble.extension.core.AttributeResolverExtension;
import com.mitchellbosecke.pebble.extension.core.CoreExtension;
import com.mitchellbosecke.pebble.extension.escaper.EscaperExtension;
import com.mitchellbosecke.pebble.extension.escaper.EscapingStrategy;
import com.mitchellbosecke.pebble.extension.i18n.I18nExtension;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Stream;

public class ExtensionRegistryFactory {

private final List<Extension> userProvidedExtensions = new ArrayList<>();

private final EscaperExtension escaperExtension = new EscaperExtension();

private boolean allowOverrideCoreOperators = false;

private Map<Class<? extends Extension>, Function<Extension, Extension>> customizers = new HashMap<>();

public ExtensionRegistry buildExtensionRegistry() {
ExtensionRegistry extensionRegistry = new ExtensionRegistry();

Stream.of(new CoreExtension(), this.escaperExtension, new I18nExtension())
.map(this::applyCustomizer)
.forEach(extensionRegistry::addExtension);

for (Extension userProvidedExtension : this.userProvidedExtensions) {
if (this.allowOverrideCoreOperators) {
extensionRegistry.addOperatorOverridingExtension(userProvidedExtension);
} else {
extensionRegistry.addExtension(userProvidedExtension);
}
}

extensionRegistry.addExtension(new AttributeResolverExtension());

return extensionRegistry;
}

private Extension applyCustomizer(Extension coreExtension) {
return customizers.getOrDefault(coreExtension.getClass(), Function.identity())
.apply(coreExtension);
}

public void autoEscaping(boolean autoEscaping) {
this.escaperExtension.setAutoEscaping(autoEscaping);
}

public void addEscapingStrategy(String name, EscapingStrategy strategy) {
this.escaperExtension.addEscapingStrategy(name, strategy);
}

public void extension(Extension... extensions) {
Collections.addAll(this.userProvidedExtensions, extensions);
}

public void allowOverrideCoreOperators(boolean allowOverrideCoreOperators) {
this.allowOverrideCoreOperators = allowOverrideCoreOperators;
}

public void defaultEscapingStrategy(String strategy) {
this.escaperExtension.setDefaultStrategy(strategy);
}

public <T extends Extension> void addExtensionCustomizer(Class<T> clazz, Function<Extension, Extension> customizer) {
this.customizers.put(clazz, customizer);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.mitchellbosecke.pebble.extension;

import com.mitchellbosecke.pebble.PebbleEngine;
import com.mitchellbosecke.pebble.error.PebbleException;
import com.mitchellbosecke.pebble.extension.core.CoreExtension;
import com.mitchellbosecke.pebble.template.PebbleTemplate;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

class ExtensionFactoryTest {

PebbleEngine pebble;

@BeforeEach
void setUp() {
pebble = new PebbleEngine.Builder()
.addExtensionCustomizer(CoreExtension.class, RemoveUpper::new)
.build();
}

@Test
void upperFilterCannotBeUsed() throws IOException {
Map<String, Object> obj = new HashMap<>();
obj.put("test", "abc");
PebbleTemplate template = pebble.getLiteralTemplate("{{ test | upper }}");

PebbleException exception = assertThrows(PebbleException.class, () -> template.evaluate(new StringWriter(), obj));
assertTrue(exception.getMessage().contains("upper"),
() -> "Expect upper-Filter to not exist, actual Problem: " + exception.getMessage());
}

private static class RemoveUpper extends ExtensionCustomizer {

public RemoveUpper(Extension core) {
super(core);
}

@Override
public Map<String, Filter> getFilters() {
Map<String, Filter> filters = new HashMap<>(super.getFilters());
filters.remove("upper");
return filters;
}

}

}

0 comments on commit c016ba9

Please sign in to comment.