Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

issue #520: provide option to customize stock extensions #552

Merged
merged 4 commits into from
Dec 18, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 24 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,27 @@ public Builder greedyMatchMethod(boolean greedyMatchMethod) {
return this;
}

/**
* Registeres a customizer which is called before the core functionality provided by default is registered
* into the {@link PebbleEngine}.
*
* @param customizer The customizer which is called before registering the target extension
* @param <T>
* @return This build object
*/
public <T extends Extension> Builder registerExtensionCustomizer(Function<Extension, ExtensionCustomizer> customizer) {
this.factory.registerExtensionCustomizer(customizer);
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 +621,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,67 @@
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 Function<Extension, Extension> customizer = Function.identity();

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

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

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

extensionRegistry.addExtension(customizer.apply(new AttributeResolverExtension()));

return extensionRegistry;
}

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 void registerExtensionCustomizer(Function<Extension, ExtensionCustomizer> customizer) {
this.customizer = customizer::apply;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
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.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

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

class ExtensionCustomizerTest {

PebbleEngine pebble;

@BeforeEach
void setUp() {
pebble = new PebbleEngine.Builder()
.registerExtensionCustomizer(RemoveUpperCustomizer::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 RemoveUpperCustomizer extends ExtensionCustomizer {

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

@Override
public Map<String, Filter> getFilters() {
Map<String, Filter> filters = Optional.ofNullable(super.getFilters()).map(HashMap::new)
Copy link
Contributor Author

@pepperbob pepperbob Nov 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just curious: is there a reason why null is acceptable over an empty map/list if filters and others are not provided?

.orElseGet(HashMap::new);
filters.remove("upper");
return filters;
}

}

}