pebble
pebble copied to clipboard
Closures in filters
I'd like to have filters that support closures in general or specifically map(code)
and filter(code)
.
That would allow me to write:
{{ listOfStrings | filter(not it.isEmpty()) | map('['+it+']') | join(',') | stringify }}
where stringify
is one of my own filters that turns the input into a valid Java string.
For filter and map as above code
always uses the implicit argument it
.
This could be implemented by duplicating ArgumentsNode::getArgumentMap
where every .getValueExpression().evaluate(self, context)
is replaced with just .getValueExpression()
. Call it getUnevaluatedArgumentMap
.
Add an empty interface like Unevaluated
and in FilterExpression::evaluate
replace:
Map<String, Object> namedArguments = args.getArgumentMap(self, context, filter);
with
Map<String, Object> namedArguments;
if (filter instanceof Unevaluated)
namedArguments = args.getUnevaluatedArgumentMap(self, context, filter);
else
namedArguments = args.getArgumentMap(self, context, filter);
The user would get the Expression
instead of the value in his filter, but can use the implicit _self
and _context
to evaluate the expression multiple times with different values.
Sample implementation of filter
:
class FilterFilter implements Filter, Unevaluated {
private List<String> args=Arrays.asList("code");
public List<String> getArgumentNames() { return args; }
public Object apply(Object input, Map<String, Object> args) {
if (!(input instanceof Iterable)) return input;
EvaluationContext context=(EvaluationContext) args.get("_context");
PebbleTemplateImpl self=(PebbleTemplateImpl) args.get("_self");
Expression<Boolean> code=(Expression<Boolean>) args.get("code");
ScopeChain sc=context.getScopeChain();
sc.pushScope();
Iterator iter=((Iterable)input).iterator();
ArrayList<Object> ret=new ArrayList<>();
while(iter.hasNext()) {
Object it=iter.next();
sc.put("it", it);
Boolean result=code.evaluate(self,context);
if (result!=null && result) ret.add(it);
}
sc.popScope();
return ret;
}
}
Alternatively one could add a lazy
operator with precedence just above ,
, whose evaluate
method will just return the inner expression, without evaluating it. With that no other changes need to be made.
Now that I wrote all this, I think this can already be implemented using an Extension by adding exactly that unary lazy
operator.
Anyway, the first solution would allow to use filter
and map
without the extra operator and looks better. It is also fully compatible as the user has to opt in to get unevaluated arguments.