Skip to content

Commit

Permalink
Qute user tags - use defaulted keys if appropriate
Browse files Browse the repository at this point in the history
- i.e. for params that define no key and contain only single part
- also use Mapper instead of Map for the data passed to a template
- Mapper value resolver has priority 15, i.e. is called before a
generated value resolver
- resolves #21855
- related to #21861
  • Loading branch information
mkouba committed Dec 7, 2021
1 parent 32c2277 commit 8c65262
Show file tree
Hide file tree
Showing 11 changed files with 109 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,21 @@

import java.util.Map;
import java.util.concurrent.CompletionStage;
import java.util.function.Function;

class ResolutionContextImpl implements ResolutionContext {

private final Object data;
private final Evaluator evaluator;
private final Map<String, SectionBlock> extendingBlocks;
private final TemplateInstance templateInstance;
private final Function<String, Object> attributeFun;

ResolutionContextImpl(Object data,
Evaluator evaluator, Map<String, SectionBlock> extendingBlocks, TemplateInstance templateInstance) {
Evaluator evaluator, Map<String, SectionBlock> extendingBlocks, Function<String, Object> attributeFun) {
this.data = data;
this.evaluator = evaluator;
this.extendingBlocks = extendingBlocks;
this.templateInstance = templateInstance;
this.attributeFun = attributeFun;
}

@Override
Expand Down Expand Up @@ -53,7 +54,7 @@ public SectionBlock getExtendingBlock(String name) {

@Override
public Object getAttribute(String key) {
return templateInstance.getAttribute(key);
return attributeFun.apply(key);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ public String asMessage() {
if (name != null) {
Object base = getBase().orElse(null);
List<Expression> params = getParams();
boolean isDataMap = (base instanceof Map) && ((Map<?, ?>) base).containsKey(TemplateInstanceBase.DATA_MAP_KEY);
boolean isDataMap = isDataMap(base);
// Entry "foo" not found in the data map
// Property "foo" not found on base object "org.acme.Bar"
// Method "getDiscount(value)" not found on base object "org.acme.Item"
Expand Down Expand Up @@ -190,6 +190,15 @@ public String asMessage() {
}
}

private boolean isDataMap(Object base) {
if (base instanceof Map) {
return ((Map<?, ?>) base).containsKey(TemplateInstanceBase.DATA_MAP_KEY);
} else if (base instanceof Mapper) {
return ((Mapper) base).get(TemplateInstanceBase.DATA_MAP_KEY) != null;
}
return false;
}

@Override
public String toString() {
return "NOT_FOUND";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkus.qute;

import java.util.Map;
import java.util.concurrent.CompletionStage;

/**
Expand Down Expand Up @@ -28,6 +29,14 @@ public interface SectionResolutionContext {
*/
ResolutionContext resolutionContext();

/**
*
* @param data
* @param extendingBlocks
* @return a new resolution context
*/
ResolutionContext newResolutionContext(Object data, Map<String, SectionBlock> extendingBlocks);

/**
* Execute the main block with the current resolution context.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.quarkus.qute.SectionHelper.SectionResolutionContext;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletionStage;
import java.util.function.Function;
Expand Down Expand Up @@ -160,6 +161,12 @@ public ResolutionContext resolutionContext() {
return resolutionContext;
}

@Override
public ResolutionContext newResolutionContext(Object data, Map<String, SectionBlock> extendingBlocks) {
return new ResolutionContextImpl(data, resolutionContext.getEvaluator(), extendingBlocks,
resolutionContext::getAttribute);
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public CompletionStage<Void> consume(Consumer<String> resultConsumer) {
private CompletionStage<Void> renderData(Object data, Consumer<String> consumer) {
CompletableFuture<Void> result = new CompletableFuture<>();
ResolutionContext rootContext = new ResolutionContextImpl(data,
engine.getEvaluator(), null, this);
engine.getEvaluator(), null, this::getAttribute);
setAttribute(DataNamespaceResolver.ROOT_CONTEXT, rootContext);
// Async resolution
root.resolve(rootContext).whenComplete((r, t) -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ protected Object data() {
return data;
}
if (dataMap != null) {
return dataMap;
return Mapper.wrap(dataMap);
}
return EMPTY_DATA_MAP;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,19 @@

public class UserTagSectionHelper implements SectionHelper {

private static final String IT = "it";
private static final String NESTED_CONTENT = "nested-content";

private final Supplier<Template> templateSupplier;
private final Map<String, Expression> parameters;
private final boolean isEmpty;
private final boolean isIsolated;

UserTagSectionHelper(Supplier<Template> templateSupplier, Map<String, Expression> parameters, boolean isEmpty) {
UserTagSectionHelper(Supplier<Template> templateSupplier, Map<String, Expression> parameters, boolean isEmpty,
boolean isIsolated) {
this.templateSupplier = templateSupplier;
this.parameters = parameters;
this.isEmpty = isEmpty;
this.isIsolated = isIsolated;
}

@Override
Expand All @@ -39,9 +41,16 @@ public CompletionStage<ResultNode> resolve(SectionResolutionContext context) {
context.resolutionContext().createChild(Mapper.wrap(evaluatedParams), null)));
}
try {
ResolutionContext resolutionContext;
// Execute the template with the params as the root context object
Object data = Mapper.wrap(evaluatedParams);
if (isIsolated) {
resolutionContext = context.newResolutionContext(data, null);
} else {
resolutionContext = context.resolutionContext().createChild(data, null);
}
TemplateImpl tagTemplate = (TemplateImpl) templateSupplier.get();
tagTemplate.root.resolve(context.resolutionContext().createChild(Mapper.wrap(evaluatedParams), null))
tagTemplate.root.resolve(resolutionContext)
.whenComplete((resultNode, t2) -> {
if (t2 != null) {
result.completeExceptionally(t2);
Expand All @@ -59,6 +68,10 @@ public CompletionStage<ResultNode> resolve(SectionResolutionContext context) {

public static class Factory implements SectionHelperFactory<UserTagSectionHelper> {

private static final String IT = "it";
private static final String ISOLATED = "_isolated";
private static final String ISOLATED_DEFAULT_VALUE = "false";

private final String name;
private final String templateId;

Expand All @@ -79,17 +92,27 @@ public List<String> getDefaultAliases() {

@Override
public ParametersInfo getParameters() {
return ParametersInfo.builder().addParameter(new Parameter(IT, IT, false)).build();
return ParametersInfo.builder()
.addParameter(new Parameter(IT, IT, false))
.addParameter(ISOLATED, ISOLATED_DEFAULT_VALUE)
.build();
}

@Override
public Scope initializeBlock(Scope outerScope, BlockInfo block) {
if (block.getLabel().equals(MAIN_BLOCK_NAME)) {
for (Entry<String, String> entry : block.getParameters().entrySet()) {
if (entry.getKey().equals(IT) && entry.getValue().equals(IT)) {
String key = entry.getKey();
String value = entry.getValue();
if (key.equals(ISOLATED) || (key.equals(IT) && value.equals(IT))) {
// {#myTag _isolated=true /}
// {#myTag foo=bar /}
continue;
} else if (useDefaultedKey(key, value)) {
// As "order" in {#myTag user order /}
key = value;
}
block.addExpression(entry.getKey(), entry.getValue());
block.addExpression(key, value);
}
return outerScope;
} else {
Expand All @@ -101,10 +124,21 @@ public Scope initializeBlock(Scope outerScope, BlockInfo block) {
public UserTagSectionHelper initialize(SectionInitContext context) {
Map<String, Expression> params = new HashMap<>();
for (Entry<String, String> entry : context.getParameters().entrySet()) {
if (entry.getKey().equals(IT) && entry.getValue().equals(IT)) {
String key = entry.getKey();
String value = entry.getValue();
if (key.equals(ISOLATED)) {
continue;
} else if (key.equals(IT)) {
if (value.equals(IT)) {
continue;
} else if (isSinglePart(value)) {
// Also register the param with a defaulted key
params.put(value, context.getExpression(key));
}
} else if (useDefaultedKey(key, value)) {
key = value;
}
params.put(entry.getKey(), context.getExpression(entry.getKey()));
params.put(key, context.getExpression(key));
}
boolean isEmpty = context.getBlocks().size() == 1 && context.getBlocks().get(0).isEmpty();
final Engine engine = context.getEngine();
Expand All @@ -119,7 +153,20 @@ public Template get() {
}
return template;
}
}, params, isEmpty);
}, params, isEmpty, Boolean.parseBoolean(context.getParameterOrDefault(ISOLATED, ISOLATED_DEFAULT_VALUE)));
}

private boolean useDefaultedKey(String key, String value) {
// Use the defaulted key if:
// 1. The key is a generated id
// 2. The value is a single identifier
// {#myTag user} should be equivalent to {#myTag user=user}
return LiteralSupport.INTEGER_LITERAL_PATTERN.matcher(key).matches()
&& isSinglePart(value);
}

private boolean isSinglePart(String value) {
return Expressions.splitParts(value).size() == 1;
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ public boolean appliesTo(EvalContext context) {
@Override
public int getPriority() {
// mapper is used in loops so we use a higher priority to jump the queue
return 5;
return 15;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public void testEscaping() throws IOException {
public void testRawStringRevolver() {

Escaper escaper = Escaper.builder().add('a', "A").build();
Engine engine = Engine.builder().addValueResolver(ValueResolvers.mapResolver())
Engine engine = Engine.builder().addValueResolver(ValueResolvers.mapperResolver())
.addValueResolver(ValueResolvers.rawResolver()).addResultMapper(new ResultMapper() {

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public void testOrElseResolver() {
@Test
public void testTernaryOperator() {
Engine engine = Engine.builder()
.addValueResolvers(ValueResolvers.mapResolver(), ValueResolvers.trueResolver(),
.addValueResolvers(ValueResolvers.mapperResolver(), ValueResolvers.trueResolver(),
ValueResolvers.orResolver())
.build();

Expand Down Expand Up @@ -255,7 +255,7 @@ public ParametersInfo getParameters() {
return super.getParameters();
}
};
Engine engine = Engine.builder().addSectionHelper(customIfFactory).addValueResolver(ValueResolvers.mapResolver())
Engine engine = Engine.builder().addSectionHelper(customIfFactory).addValueResolver(ValueResolvers.mapperResolver())
.build();
assertEquals(":1:1",
engine.parse("{#if foo}:1{/if}{#if bar}:0{/if}{#if foo}:1{/if}")
Expand All @@ -278,7 +278,7 @@ public boolean cacheFactoryConfig() {
return false;
}
};
Engine engine = Engine.builder().addSectionHelper(customIfFactory).addValueResolver(ValueResolvers.mapResolver())
Engine engine = Engine.builder().addSectionHelper(customIfFactory).addValueResolver(ValueResolvers.mapperResolver())
.build();
assertEquals(":1:1",
engine.parse("{#if foo}:1{/if}{#if bar}:0{/if}{#if foo}:1{/if}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,19 @@ public void testEval() {
.data("items", Map.of(1, Map.of("quantity", 10, "unit", "kg"))).render());
}

@Test
public void testDefaultedKey() {
Engine engine = Engine.builder()
.addDefaults()
.addSectionHelper(new UserTagSectionHelper.Factory("myTag", "my-tag-id"))
.strictRendering(false)
.build();

Template tag = engine.parse("{it}:{name}:{isCool}:{age}:{foo.bar}:{foo}");
engine.putTemplate("my-tag-id", tag);
assertEquals("Ondrej:Ondrej:true:2:NOT_FOUND:NOT_FOUND",
engine.parse("{#myTag name age=2 isCool foo.length _isolated=true/}")
.data("name", "Ondrej", "isCool", true, "foo", "bzzz").render());
}

}

0 comments on commit 8c65262

Please sign in to comment.