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

Implement EagerFromTag and EagerImportTag #560

Merged
merged 15 commits into from
Dec 18, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
13 changes: 6 additions & 7 deletions src/main/java/com/hubspot/jinjava/interpret/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
public class Context extends ScopeMap<String, Object> {
public static final String GLOBAL_MACROS_SCOPE_KEY = "__macros__";
public static final String IMPORT_RESOURCE_PATH_KEY = "import_resource_path";
public static final String IMPORT_RESOURCE_ALIAS = "import_resource_alias";
public static final String IMPORT_RESOURCE_ALIAS_KEY = "import_resource_alias";

private SetMultimap<String, String> dependencies = HashMultimap.create();
private Map<Library, Set<String>> disabled;
Expand Down Expand Up @@ -326,16 +326,11 @@ public Set<Node> getDeferredNodes() {

public void handleEagerToken(EagerToken eagerToken) {
eagerTokens.add(eagerToken);
Set<String> deferredProps = DeferredValueUtils.findAndMarkDeferredProperties(this);
DeferredValueUtils.findAndMarkDeferredProperties(this);
if (getParent() != null) {
Context parent = getParent();
//Ignore global context
if (parent.getParent() != null) {
//Place deferred values on the parent context
deferredProps
.stream()
.filter(key -> !parent.containsKey(key))
.forEach(key -> parent.put(key, this.get(key)));
parent.handleEagerToken(eagerToken);
}
}
Expand Down Expand Up @@ -512,6 +507,10 @@ public void setExpressionStrategy(ExpressionStrategy expressionStrategy) {
this.expressionStrategy = expressionStrategy;
}

public Optional<String> getImportResourceAlias() {
return Optional.ofNullable(get(IMPORT_RESOURCE_ALIAS_KEY)).map(Object::toString);
}

public CallStack getExtendPathStack() {
return extendPathStack;
}
Expand Down
15 changes: 5 additions & 10 deletions src/main/java/com/hubspot/jinjava/lib/tag/ImportTag.java
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,17 @@ public String interpret(TagNode tagNode, JinjavaInterpreter interpreter) {
// If the template depends on deferred values it should not be rendered and all defined variables and macros should be deferred too
if (!child.getContext().getDeferredNodes().isEmpty()) {
handleDeferredNodesDuringImport(
(TagToken) tagNode.getMaster(),
node,
contextVar,
templateFile,
childBindings,
child,
interpreter
);
throw new DeferredValueException(
templateFile,
tagNode.getLineNumber(),
tagNode.getStartPosition()
);
}

integrateChild(contextVar, childBindings, child, interpreter);
Expand Down Expand Up @@ -159,10 +162,8 @@ public static void integrateChild(
}

public static void handleDeferredNodesDuringImport(
TagToken tagToken,
Node node,
String contextVar,
String templateFile,
Map<String, Object> childBindings,
JinjavaInterpreter child,
JinjavaInterpreter interpreter
Expand Down Expand Up @@ -195,12 +196,6 @@ public static void handleDeferredNodesDuringImport(
childBindings.remove(Context.IMPORT_RESOURCE_PATH_KEY);
interpreter.getContext().put(contextVar, DeferredValue.instance(childBindings));
}

throw new DeferredValueException(
templateFile,
tagToken.getLineNumber(),
tagToken.getStartPosition()
);
}

public static Node parseTemplateAsNode(
Expand Down
20 changes: 17 additions & 3 deletions src/main/java/com/hubspot/jinjava/lib/tag/MacroTag.java
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,28 @@ public String interpret(TagNode tagNode, JinjavaInterpreter interpreter) {

if (StringUtils.isNotEmpty(parentName)) {
try {
Map<String, Object> macroOfParent;
if (!(interpreter.getContext().get(parentName) instanceof DeferredValue)) {
Map<String, Object> macroOfParent = (Map<String, Object>) interpreter
.getContext()
.getOrDefault(parentName, new HashMap<>());
macroOfParent =
(Map<String, Object>) interpreter
.getContext()
.getOrDefault(parentName, new HashMap<>());
macroOfParent.put(macro.getName(), macro);
if (!interpreter.getContext().containsKey(parentName)) {
interpreter.getContext().put(parentName, macroOfParent);
}
} else {
Object originalValue =
((DeferredValue) interpreter.getContext().get(parentName)).getOriginalValue();
if (originalValue instanceof Map) {
((Map<String, Object>) originalValue).put(macro.getName(), macro);
} else {
macroOfParent = new HashMap<>();
macroOfParent.put(macro.getName(), macro);
interpreter
.getContext()
.put(parentName, DeferredValue.instance(macroOfParent));
}
}
} catch (ClassCastException e) {
throw new TemplateSyntaxException(
Expand Down
30 changes: 13 additions & 17 deletions src/main/java/com/hubspot/jinjava/lib/tag/SetTag.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.hubspot.jinjava.interpret.TemplateSyntaxException;
import com.hubspot.jinjava.tree.TagNode;
import com.hubspot.jinjava.tree.parse.TagToken;
import com.hubspot.jinjava.util.DeferredValueUtils;
import java.util.List;
import org.apache.commons.lang3.StringUtils;

Expand Down Expand Up @@ -108,21 +109,9 @@ public String interpret(TagNode tagNode, JinjavaInterpreter interpreter) {
String[] varTokens = var.split(",");

try {
executeSet((TagToken) tagNode.getMaster(), interpreter, varTokens, expr);
executeSet((TagToken) tagNode.getMaster(), interpreter, varTokens, expr, false);
} catch (DeferredValueException e) {
for (String varToken : varTokens) {
String key = varToken.trim();
Object originalValue = interpreter.getContext().get(key);
if (originalValue != null) {
if (originalValue instanceof DeferredValue) {
interpreter.getContext().put(key, originalValue);
} else {
interpreter.getContext().put(key, DeferredValue.instance(originalValue));
}
} else {
interpreter.getContext().put(key, DeferredValue.instance());
}
}
DeferredValueUtils.deferVariables(varTokens, interpreter.getContext());
throw e;
}

Expand All @@ -133,7 +122,8 @@ public void executeSet(
TagToken tagToken,
JinjavaInterpreter interpreter,
String[] varTokens,
String expr
String expr,
boolean allowDeferredValueOverride
) {
if (varTokens.length > 1) {
// handle multi-variable assignment
Expand All @@ -155,7 +145,10 @@ public void executeSet(
for (int i = 0; i < varTokens.length; i++) {
String varItem = varTokens[i].trim();
if (interpreter.getContext().containsKey(varItem)) {
if (interpreter.getContext().get(varItem) instanceof DeferredValue) {
if (
!allowDeferredValueOverride &&
interpreter.getContext().get(varItem) instanceof DeferredValue
) {
throw new DeferredValueException(varItem);
}
}
Expand All @@ -164,7 +157,10 @@ public void executeSet(
} else {
// handle single variable assignment
if (interpreter.getContext().containsKey(varTokens[0])) {
if (interpreter.getContext().get(varTokens[0]) instanceof DeferredValue) {
if (
!allowDeferredValueOverride &&
interpreter.getContext().get(varTokens[0]) instanceof DeferredValue
) {
throw new DeferredValueException(varTokens[0]);
}
}
Expand Down
126 changes: 126 additions & 0 deletions src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerFromTag.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package com.hubspot.jinjava.lib.tag.eager;

import com.hubspot.jinjava.interpret.Context;
import com.hubspot.jinjava.interpret.InterpretException;
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
import com.hubspot.jinjava.lib.fn.MacroFunction;
import com.hubspot.jinjava.lib.tag.FromTag;
import com.hubspot.jinjava.tree.Node;
import com.hubspot.jinjava.tree.parse.TagToken;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

public class EagerFromTag extends EagerStateChangingTag<FromTag> {

public EagerFromTag() {
super(new FromTag());
}

@Override
public String getEagerTagImage(TagToken tagToken, JinjavaInterpreter interpreter) {
List<String> helper = FromTag.getHelpers(tagToken);

Optional<String> maybeTemplateFile = FromTag.getTemplateFile(
helper,
tagToken,
interpreter
);
if (!maybeTemplateFile.isPresent()) {
return "";
}
String templateFile = maybeTemplateFile.get();
try {
Map<String, String> imports = FromTag.getImportMap(helper);

try {
String template = interpreter.getResource(templateFile);
Node node = interpreter.parse(template);

JinjavaInterpreter child = interpreter
.getConfig()
.getInterpreterFactory()
.newInstance(interpreter);
child.getContext().put(Context.IMPORT_RESOURCE_PATH_KEY, templateFile);
JinjavaInterpreter.pushCurrent(child);
String output;
try {
output = child.render(node);
} finally {
JinjavaInterpreter.popCurrent();
}

interpreter.addAllChildErrors(templateFile, child.getErrorsCopy());

if (!child.getContext().getDeferredNodes().isEmpty()) {
FromTag.handleDeferredNodesDuringImport(
tagToken,
templateFile,
imports,
child,
interpreter
);
}

FromTag.integrateChild(imports, child, interpreter);
Map<String, String> newToOldImportNames = renameMacros(imports, interpreter)
.entrySet()
.stream()
.filter(e -> !e.getKey().equals(e.getValue()))
.collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
if (child.getContext().getEagerTokens().isEmpty() || output == null) {
output = "";
} else if (newToOldImportNames.size() > 0) {
// Set after output
output =
output +
buildSetTagForDeferredInChildContext(newToOldImportNames, interpreter, true);
}
return output;
} catch (IOException e) {
throw new InterpretException(
e.getMessage(),
e,
tagToken.getLineNumber(),
tagToken.getStartPosition()
);
}
} finally {
interpreter.getContext().popFromStack();
}
}

private static Map<String, String> renameMacros(
Map<String, String> oldToNewImportNames,
JinjavaInterpreter interpreter
) {
Set<String> toRemove = new HashSet<>();
Map<String, MacroFunction> macroFunctions = oldToNewImportNames
.entrySet()
.stream()
.filter(
e ->
!e.getKey().equals(e.getValue()) &&
!interpreter.getContext().containsKey(e.getKey()) &&
interpreter.getContext().isGlobalMacro(e.getKey())
)
.peek(entry -> toRemove.add(entry.getKey()))
.collect(
Collectors.toMap(
Map.Entry::getValue,
e -> interpreter.getContext().getGlobalMacro(e.getKey())
)
);

macroFunctions.forEach(
(key, value) ->
interpreter.getContext().addGlobalMacro(new MacroFunction(value, key))
);
toRemove.forEach(oldToNewImportNames::remove);
return oldToNewImportNames;
}
}
Loading