diff --git a/src/main/java/com/hubspot/jinjava/interpret/Context.java b/src/main/java/com/hubspot/jinjava/interpret/Context.java index b085cb5c2..3f9725f0a 100644 --- a/src/main/java/com/hubspot/jinjava/interpret/Context.java +++ b/src/main/java/com/hubspot/jinjava/interpret/Context.java @@ -50,7 +50,7 @@ public class Context extends ScopeMap { 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 dependencies = HashMultimap.create(); private Map> disabled; @@ -326,16 +326,11 @@ public Set getDeferredNodes() { public void handleEagerToken(EagerToken eagerToken) { eagerTokens.add(eagerToken); - Set 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); } } @@ -512,6 +507,10 @@ public void setExpressionStrategy(ExpressionStrategy expressionStrategy) { this.expressionStrategy = expressionStrategy; } + public Optional getImportResourceAlias() { + return Optional.ofNullable(get(IMPORT_RESOURCE_ALIAS_KEY)).map(Object::toString); + } + public CallStack getExtendPathStack() { return extendPathStack; } diff --git a/src/main/java/com/hubspot/jinjava/lib/tag/ImportTag.java b/src/main/java/com/hubspot/jinjava/lib/tag/ImportTag.java index b088f1057..be7af5a22 100644 --- a/src/main/java/com/hubspot/jinjava/lib/tag/ImportTag.java +++ b/src/main/java/com/hubspot/jinjava/lib/tag/ImportTag.java @@ -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); @@ -159,10 +162,8 @@ public static void integrateChild( } public static void handleDeferredNodesDuringImport( - TagToken tagToken, Node node, String contextVar, - String templateFile, Map childBindings, JinjavaInterpreter child, JinjavaInterpreter interpreter @@ -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( diff --git a/src/main/java/com/hubspot/jinjava/lib/tag/MacroTag.java b/src/main/java/com/hubspot/jinjava/lib/tag/MacroTag.java index ceb7a01e6..1ab05e95b 100644 --- a/src/main/java/com/hubspot/jinjava/lib/tag/MacroTag.java +++ b/src/main/java/com/hubspot/jinjava/lib/tag/MacroTag.java @@ -129,14 +129,28 @@ public String interpret(TagNode tagNode, JinjavaInterpreter interpreter) { if (StringUtils.isNotEmpty(parentName)) { try { + Map macroOfParent; if (!(interpreter.getContext().get(parentName) instanceof DeferredValue)) { - Map macroOfParent = (Map) interpreter - .getContext() - .getOrDefault(parentName, new HashMap<>()); + macroOfParent = + (Map) 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) 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( diff --git a/src/main/java/com/hubspot/jinjava/lib/tag/SetTag.java b/src/main/java/com/hubspot/jinjava/lib/tag/SetTag.java index 5c406a409..1e7891fee 100644 --- a/src/main/java/com/hubspot/jinjava/lib/tag/SetTag.java +++ b/src/main/java/com/hubspot/jinjava/lib/tag/SetTag.java @@ -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; @@ -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; } @@ -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 @@ -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); } } @@ -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]); } } diff --git a/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerFromTag.java b/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerFromTag.java new file mode 100644 index 000000000..aab7d1424 --- /dev/null +++ b/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerFromTag.java @@ -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 { + + public EagerFromTag() { + super(new FromTag()); + } + + @Override + public String getEagerTagImage(TagToken tagToken, JinjavaInterpreter interpreter) { + List helper = FromTag.getHelpers(tagToken); + + Optional maybeTemplateFile = FromTag.getTemplateFile( + helper, + tagToken, + interpreter + ); + if (!maybeTemplateFile.isPresent()) { + return ""; + } + String templateFile = maybeTemplateFile.get(); + try { + Map 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 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 renameMacros( + Map oldToNewImportNames, + JinjavaInterpreter interpreter + ) { + Set toRemove = new HashSet<>(); + Map 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; + } +} diff --git a/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerImportTag.java b/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerImportTag.java new file mode 100644 index 000000000..6de990dce --- /dev/null +++ b/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerImportTag.java @@ -0,0 +1,268 @@ +package com.hubspot.jinjava.lib.tag.eager; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Strings; +import com.hubspot.jinjava.interpret.Context; +import com.hubspot.jinjava.interpret.DeferredValue; +import com.hubspot.jinjava.interpret.DeferredValueException; +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.ImportTag; +import com.hubspot.jinjava.objects.collections.PyMap; +import com.hubspot.jinjava.tree.Node; +import com.hubspot.jinjava.tree.parse.TagToken; +import com.hubspot.jinjava.util.ChunkResolver; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.StringJoiner; +import org.apache.commons.lang3.StringUtils; + +public class EagerImportTag extends EagerStateChangingTag { + + public EagerImportTag() { + super(new ImportTag()); + } + + @Override + public String getEagerTagImage(TagToken tagToken, JinjavaInterpreter interpreter) { + List helper = ImportTag.getHelpers(tagToken); + + String currentImportAlias = ImportTag.getContextVar(helper); + + Optional maybeTemplateFile = ImportTag.getTemplateFile( + helper, + tagToken, + interpreter + ); + if (!maybeTemplateFile.isPresent()) { + return ""; + } + String templateFile = maybeTemplateFile.get(); + try { + Node node = ImportTag.parseTemplateAsNode(interpreter, templateFile); + + JinjavaInterpreter child = interpreter + .getConfig() + .getInterpreterFactory() + .newInstance(interpreter); + child.getContext().put(Context.IMPORT_RESOURCE_PATH_KEY, templateFile); + JinjavaInterpreter.pushCurrent(child); + setupImportAlias(currentImportAlias, child, interpreter); + + String output; + try { + output = child.render(node); + } finally { + JinjavaInterpreter.popCurrent(); + } + interpreter.addAllChildErrors(templateFile, child.getErrorsCopy()); + Map childBindings = child.getContext().getSessionBindings(); + + // 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()) { + ImportTag.handleDeferredNodesDuringImport( + node, + currentImportAlias, + childBindings, + child, + interpreter + ); + throw new DeferredValueException( + templateFile, + tagToken.getLineNumber(), + tagToken.getStartPosition() + ); + } + integrateChild(currentImportAlias, childBindings, child, interpreter); + if (child.getContext().getEagerTokens().isEmpty() || output == null) { + output = ""; + } else if (!Strings.isNullOrEmpty(currentImportAlias)) { + // Since some values got deferred, output a DoTag that will load the currentImportAlias on the context. + return output + getDoTagToPreserve(interpreter, currentImportAlias); + } + return output; + } catch (IOException e) { + throw new InterpretException( + e.getMessage(), + e, + tagToken.getLineNumber(), + tagToken.getStartPosition() + ); + } finally { + interpreter.getContext().getCurrentPathStack().pop(); + } + } + + @SuppressWarnings("unchecked") + private static String getDoTagToPreserve( + JinjavaInterpreter interpreter, + String currentImportAlias + ) + throws JsonProcessingException { + StringJoiner keyValueJoiner = new StringJoiner(","); + Object currentAliasMap = interpreter.getContext().get(currentImportAlias); + if ((!(currentAliasMap instanceof DeferredValue))) { + // Make sure that the map is deferred. + if (!(currentAliasMap instanceof Map)) { + currentAliasMap = new PyMap(new HashMap<>()); + } + interpreter + .getContext() + .put(currentImportAlias, DeferredValue.instance(currentAliasMap)); + } + for (Map.Entry entry : ( + (Map) ( + (DeferredValue) interpreter.getContext().get(currentImportAlias) + ).getOriginalValue() + ).entrySet()) { + if (entry.getValue() instanceof DeferredValue) { + keyValueJoiner.add(String.format("'%s': %s", entry.getKey(), entry.getKey())); + } else if (!(entry.getValue() instanceof MacroFunction)) { + keyValueJoiner.add( + String.format( + "'%s': %s", + entry.getKey(), + ChunkResolver.getValueAsJinjavaString(entry.getValue()) + ) + ); + } + } + if (keyValueJoiner.length() > 0) { + return buildDoUpdateTag(currentImportAlias, keyValueJoiner.toString(), interpreter); + } + return ""; + } + + @VisibleForTesting + public static void setupImportAlias( + String currentImportAlias, + JinjavaInterpreter child, + JinjavaInterpreter parent + ) { + if (!Strings.isNullOrEmpty(currentImportAlias)) { + Optional maybeParentImportAlias = parent + .getContext() + .getImportResourceAlias(); + if (maybeParentImportAlias.isPresent()) { + child + .getContext() + .getScope() + .put( + Context.IMPORT_RESOURCE_ALIAS_KEY, + String.format("%s.%s", maybeParentImportAlias.get(), currentImportAlias) + ); + } else { + child + .getContext() + .getScope() + .put(Context.IMPORT_RESOURCE_ALIAS_KEY, currentImportAlias); + } + constructFullAliasPathMap(currentImportAlias, child); + getMapForCurrentContextAlias(currentImportAlias, child); + } + } + + @SuppressWarnings("unchecked") + private static void constructFullAliasPathMap( + String currentImportAlias, + JinjavaInterpreter child + ) { + String fullImportAlias = child + .getContext() + .getImportResourceAlias() + .orElse(currentImportAlias); + String[] allAliases = fullImportAlias.split("\\."); + Map currentMap = child.getContext().getParent(); + for (int i = 0; i < allAliases.length - 1; i++) { + Object maybeNextMap = currentMap.get(allAliases[i]); + if (maybeNextMap instanceof Map) { + currentMap = (Map) maybeNextMap; + } else if ( + maybeNextMap instanceof DeferredValue && + ((DeferredValue) maybeNextMap).getOriginalValue() instanceof Map + ) { + currentMap = + (Map) ((DeferredValue) maybeNextMap).getOriginalValue(); + } else { + throw new InterpretException("Encountered a problem with import alias maps"); + } + } + currentMap.put(allAliases[allAliases.length - 1], new PyMap(new HashMap<>())); + } + + @SuppressWarnings("unchecked") + private static Map getMapForCurrentContextAlias( + String currentImportAlias, + JinjavaInterpreter child + ) { + Object parentValueForChild = child.getContext().getParent().get(currentImportAlias); + if (parentValueForChild instanceof Map) { + return (Map) parentValueForChild; + } else if (parentValueForChild instanceof DeferredValue) { + if (((DeferredValue) parentValueForChild).getOriginalValue() instanceof Map) { + return (Map) ( + (DeferredValue) parentValueForChild + ).getOriginalValue(); + } + Map newMap = new PyMap(new HashMap<>()); + child + .getContext() + .getParent() + .put(currentImportAlias, DeferredValue.instance(newMap)); + return newMap; + } else { + Map newMap = new PyMap(new HashMap<>()); + child.getContext().getParent().put(currentImportAlias, newMap); + return newMap; + } + } + + @VisibleForTesting + public static void integrateChild( + String currentImportAlias, + Map childBindings, + JinjavaInterpreter child, + JinjavaInterpreter parent + ) { + if (StringUtils.isBlank(currentImportAlias)) { + for (MacroFunction macro : child.getContext().getGlobalMacros().values()) { + parent.getContext().addGlobalMacro(macro); + } + childBindings.remove(Context.GLOBAL_MACROS_SCOPE_KEY); + childBindings.remove(Context.IMPORT_RESOURCE_PATH_KEY); + childBindings.remove(Context.IMPORT_RESOURCE_ALIAS_KEY); + parent.getContext().putAll(childBindings); + } else { + Map globalMacros = child.getContext().getGlobalMacros(); + for (Map.Entry macro : globalMacros.entrySet()) { + childBindings.put(macro.getKey(), macro.getValue()); + } + Map mapForCurrentContextAlias = getMapForCurrentContextAlias( + currentImportAlias, + child + ); + // Remove layers from self down to original import alias to prevent reference loops + Arrays + .stream( + child + .getContext() + .getImportResourceAlias() + .orElse(currentImportAlias) + .split("\\.") + ) + .forEach(childBindings::remove); + // Remove meta keys + childBindings.remove(Context.GLOBAL_MACROS_SCOPE_KEY); + childBindings.remove(Context.IMPORT_RESOURCE_PATH_KEY); + childBindings.remove(Context.IMPORT_RESOURCE_ALIAS_KEY); + mapForCurrentContextAlias.putAll(childBindings); + } + } +} diff --git a/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerSetTag.java b/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerSetTag.java index f10c381d6..3f99a3044 100644 --- a/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerSetTag.java +++ b/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerSetTag.java @@ -1,16 +1,15 @@ package com.hubspot.jinjava.lib.tag.eager; -import com.hubspot.jinjava.interpret.Context; import com.hubspot.jinjava.interpret.DeferredValueException; import com.hubspot.jinjava.interpret.JinjavaInterpreter; import com.hubspot.jinjava.interpret.TemplateSyntaxException; -import com.hubspot.jinjava.lib.tag.DoTag; import com.hubspot.jinjava.lib.tag.SetTag; import com.hubspot.jinjava.tree.parse.TagToken; import com.hubspot.jinjava.util.ChunkResolver; import com.hubspot.jinjava.util.LengthLimitingStringJoiner; import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.StringJoiner; import java.util.stream.Collectors; @@ -39,11 +38,7 @@ public String getEagerTagImage(TagToken tagToken, JinjavaInterpreter interpreter String variables = tagToken.getHelpers().substring(0, eqPos).trim(); String expression = tagToken.getHelpers().substring(eqPos + 1); - if (interpreter.getContext().containsKey(Context.IMPORT_RESOURCE_ALIAS)) { - return interpreter.render( - convertSetToUpdate(variables, expression, tagToken, interpreter) - ); - } + ChunkResolver chunkResolver = new ChunkResolver(expression, tagToken, interpreter); EagerStringResult resolvedExpression = executeInChildContext( eagerInterpreter -> chunkResolver.resolveChunks(), @@ -74,7 +69,13 @@ public String getEagerTagImage(TagToken tagToken, JinjavaInterpreter interpreter ) { try { getTag() - .executeSet(tagToken, interpreter, varTokens, resolvedExpression.getResult()); + .executeSet( + tagToken, + interpreter, + varTokens, + resolvedExpression.getResult(), + true + ); return ""; } catch (DeferredValueException ignored) {} } @@ -96,45 +97,38 @@ public String getEagerTagImage(TagToken tagToken, JinjavaInterpreter interpreter Arrays.stream(varTokens).map(String::trim).collect(Collectors.toSet()) ) ); - // Possible macro/set tag in front of this one. + + StringBuilder suffixToPreserveState = new StringBuilder(); + Optional maybeFullImportAlias = interpreter + .getContext() + .getImportResourceAlias(); + if (maybeFullImportAlias.isPresent()) { + String currentImportAlias = maybeFullImportAlias + .get() + .substring(maybeFullImportAlias.get().lastIndexOf(".") + 1); + String updateString = getUpdateString(variables); + suffixToPreserveState.append( + interpreter.render( + buildDoUpdateTag(currentImportAlias, updateString, interpreter) + ) + ); + } return wrapInAutoEscapeIfNeeded( - prefixToPreserveState.toString() + joiner.toString(), + prefixToPreserveState.toString() + + joiner.toString() + + suffixToPreserveState.toString(), interpreter ); } - private static String convertSetToUpdate( - String variables, - String expression, - TagToken tagToken, - JinjavaInterpreter interpreter - ) { - LengthLimitingStringJoiner joiner = new LengthLimitingStringJoiner( - interpreter.getConfig().getMaxOutputSize(), - " " - ) - .add(interpreter.getConfig().getTokenScannerSymbols().getExpressionStartWithTag()) - .add(DoTag.TAG_NAME); + private static String getUpdateString(String variables) { List varList = Arrays .stream(variables.split(",")) .map(String::trim) .collect(Collectors.toList()); - ChunkResolver chunkResolver = new ChunkResolver(expression, tagToken, interpreter); - List expressionList = chunkResolver.splitChunks(); StringJoiner updateString = new StringJoiner(","); - for (int i = 0; i < varList.size() && i < expressionList.size(); i++) { - updateString.add(String.format("'%s': %s", varList.get(i), expressionList.get(i))); - } - joiner.add( - String.format( - "%s.update({%s})", - interpreter.getContext().get(Context.IMPORT_RESOURCE_ALIAS), - updateString.toString() - ) - ); - joiner.add( - interpreter.getConfig().getTokenScannerSymbols().getExpressionEndWithTag() - ); - return joiner.toString(); + // Update the alias map to the value of the set variable. + varList.forEach(var -> updateString.add(String.format("'%s': %s", var, var))); + return updateString.toString(); } } diff --git a/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerTagDecorator.java b/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerTagDecorator.java index 6e1c6fd84..e355e38d2 100644 --- a/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerTagDecorator.java +++ b/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerTagDecorator.java @@ -17,6 +17,7 @@ import com.hubspot.jinjava.lib.fn.MacroFunction; import com.hubspot.jinjava.lib.fn.eager.EagerMacroFunction; import com.hubspot.jinjava.lib.tag.AutoEscapeTag; +import com.hubspot.jinjava.lib.tag.DoTag; import com.hubspot.jinjava.lib.tag.MacroTag; import com.hubspot.jinjava.lib.tag.RawTag; import com.hubspot.jinjava.lib.tag.SetTag; @@ -404,6 +405,19 @@ public static String buildSetTagForDeferredInChildContext( return image; } + public static String buildDoUpdateTag( + String currentImportAlias, + String updateString, + JinjavaInterpreter interpreter + ) { + return new LengthLimitingStringJoiner(interpreter.getConfig().getMaxOutputSize(), " ") + .add(interpreter.getConfig().getTokenScannerSymbols().getExpressionStartWithTag()) + .add(DoTag.TAG_NAME) + .add(String.format("%s.update({%s})", currentImportAlias, updateString)) + .add(interpreter.getConfig().getTokenScannerSymbols().getExpressionEndWithTag()) + .toString(); + } + /** * Casts token to TagToken if possible to get the eager image of the token. * @see #getEagerTagImage(TagToken, JinjavaInterpreter) diff --git a/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerTagFactory.java b/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerTagFactory.java index 4e4d62a9c..1bc77913e 100644 --- a/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerTagFactory.java +++ b/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerTagFactory.java @@ -10,7 +10,9 @@ import com.hubspot.jinjava.lib.tag.ElseTag; import com.hubspot.jinjava.lib.tag.EndTag; import com.hubspot.jinjava.lib.tag.ForTag; +import com.hubspot.jinjava.lib.tag.FromTag; import com.hubspot.jinjava.lib.tag.IfTag; +import com.hubspot.jinjava.lib.tag.ImportTag; import com.hubspot.jinjava.lib.tag.PrintTag; import com.hubspot.jinjava.lib.tag.RawTag; import com.hubspot.jinjava.lib.tag.SetTag; @@ -26,6 +28,8 @@ public class EagerTagFactory { .put(SetTag.class, EagerSetTag.class) .put(DoTag.class, EagerDoTag.class) .put(PrintTag.class, EagerPrintTag.class) + .put(FromTag.class, EagerFromTag.class) + .put(ImportTag.class, EagerImportTag.class) .put(ForTag.class, EagerForTag.class) .put(CycleTag.class, EagerCycleTag.class) .put(IfTag.class, EagerIfTag.class) diff --git a/src/main/java/com/hubspot/jinjava/util/DeferredValueUtils.java b/src/main/java/com/hubspot/jinjava/util/DeferredValueUtils.java index 79bb834f1..b55f16aae 100644 --- a/src/main/java/com/hubspot/jinjava/util/DeferredValueUtils.java +++ b/src/main/java/com/hubspot/jinjava/util/DeferredValueUtils.java @@ -61,6 +61,22 @@ public static HashMap getDeferredContextWithOriginalValues( return deferredContext; } + public static void deferVariables(String[] varTokens, Map context) { + for (String varToken : varTokens) { + String key = varToken.trim(); + Object originalValue = context.get(key); + if (originalValue != null) { + if (originalValue instanceof DeferredValue) { + context.put(key, originalValue); + } else { + context.put(key, DeferredValue.instance(originalValue)); + } + } else { + context.put(key, DeferredValue.instance()); + } + } + } + public static Set findAndMarkDeferredProperties(Context context) { String templateSource = rebuildTemplateForNodes(context.getDeferredNodes()); Set deferredProps = getPropertiesUsedInDeferredNodes(context, templateSource); diff --git a/src/test/java/com/hubspot/jinjava/EagerTest.java b/src/test/java/com/hubspot/jinjava/EagerTest.java index 35ff857c8..8e2e1471f 100644 --- a/src/test/java/com/hubspot/jinjava/EagerTest.java +++ b/src/test/java/com/hubspot/jinjava/EagerTest.java @@ -725,7 +725,6 @@ public void itWrapsCertainOutputInRaw() { } @Test - @Ignore public void itHandlesDeferredImportVars() { expectedTemplateInterpreter.assertExpectedOutput("handles-deferred-import-vars"); } @@ -739,7 +738,14 @@ public void itHandlesDeferredImportVarsSecondPass() { } @Test - @Ignore + public void itHandlesNonDeferredImportVars() { + expectedTemplateInterpreter.assertExpectedNonEagerOutput( + "handles-non-deferred-import-vars" + ); + expectedTemplateInterpreter.assertExpectedOutput("handles-non-deferred-import-vars"); + } + + @Test public void itHandlesDeferredFromImportAs() { expectedTemplateInterpreter.assertExpectedOutput("handles-deferred-from-import-as"); } diff --git a/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerFromTagTest.java b/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerFromTagTest.java new file mode 100644 index 000000000..c78ba6abf --- /dev/null +++ b/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerFromTagTest.java @@ -0,0 +1,77 @@ +package com.hubspot.jinjava.lib.tag.eager; + +import com.google.common.io.Resources; +import com.hubspot.jinjava.JinjavaConfig; +import com.hubspot.jinjava.interpret.DeferredValue; +import com.hubspot.jinjava.interpret.JinjavaInterpreter; +import com.hubspot.jinjava.lib.tag.FromTag; +import com.hubspot.jinjava.lib.tag.FromTagTest; +import com.hubspot.jinjava.lib.tag.Tag; +import com.hubspot.jinjava.loader.LocationResolver; +import com.hubspot.jinjava.loader.RelativePathResolver; +import com.hubspot.jinjava.loader.ResourceLocator; +import com.hubspot.jinjava.mode.EagerExecutionMode; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Optional; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +public class EagerFromTagTest extends FromTagTest { + + @Before + public void eagerSetup() { + jinjava.setResourceLocator( + new ResourceLocator() { + private RelativePathResolver relativePathResolver = new RelativePathResolver(); + + @Override + public String getString( + String fullName, + Charset encoding, + JinjavaInterpreter interpreter + ) + throws IOException { + return Resources.toString( + Resources.getResource(String.format("tags/macrotag/%s", fullName)), + StandardCharsets.UTF_8 + ); + } + + @Override + public Optional getLocationResolver() { + return Optional.of(relativePathResolver); + } + } + ); + context.put("padding", 42); + interpreter = + new JinjavaInterpreter( + jinjava, + context, + JinjavaConfig + .newBuilder() + .withExecutionMode(EagerExecutionMode.instance()) + .build() + ); + Tag tag = EagerTagFactory + .getEagerTagDecorator(FromTag.class) + .orElseThrow(RuntimeException::new); + context.registerTag(tag); + context.put("deferred", DeferredValue.instance()); + JinjavaInterpreter.pushCurrent(interpreter); + } + + @After + public void teardown() { + JinjavaInterpreter.popCurrent(); + } + + @Test + @Ignore + @Override + public void itDefersImport() {} +} diff --git a/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerImportTagTest.java b/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerImportTagTest.java new file mode 100644 index 000000000..39f0f8694 --- /dev/null +++ b/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerImportTagTest.java @@ -0,0 +1,521 @@ +package com.hubspot.jinjava.lib.tag.eager; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.google.common.io.Resources; +import com.hubspot.jinjava.JinjavaConfig; +import com.hubspot.jinjava.interpret.Context; +import com.hubspot.jinjava.interpret.DeferredValue; +import com.hubspot.jinjava.interpret.JinjavaInterpreter; +import com.hubspot.jinjava.lib.tag.FromTag; +import com.hubspot.jinjava.lib.tag.ImportTagTest; +import com.hubspot.jinjava.lib.tag.Tag; +import com.hubspot.jinjava.loader.LocationResolver; +import com.hubspot.jinjava.loader.RelativePathResolver; +import com.hubspot.jinjava.loader.ResourceLocator; +import com.hubspot.jinjava.mode.EagerExecutionMode; +import com.hubspot.jinjava.objects.collections.PyMap; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.Optional; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +public class EagerImportTagTest extends ImportTagTest { + private static final String CONTEXT_VAR = "context_var"; + private static final String TEMPLATE_FILE = "template.jinja"; + + @Before + public void eagerSetup() { + context.put("padding", 42); + interpreter = + new JinjavaInterpreter( + jinjava, + context, + JinjavaConfig + .newBuilder() + .withExecutionMode(EagerExecutionMode.instance()) + .build() + ); + Tag tag = EagerTagFactory + .getEagerTagDecorator(FromTag.class) + .orElseThrow(RuntimeException::new); + context.registerTag(tag); + context.put("deferred", DeferredValue.instance()); + JinjavaInterpreter.pushCurrent(interpreter); + } + + @After + public void teardown() { + JinjavaInterpreter.popCurrent(); + } + + @Test + public void itRemovesKeysFromChildBindings() { + JinjavaInterpreter child = getChildInterpreter(interpreter, CONTEXT_VAR); + Map childBindings = child.getContext().getSessionBindings(); + assertThat(childBindings.get(Context.IMPORT_RESOURCE_ALIAS_KEY)) + .isEqualTo(CONTEXT_VAR); + EagerImportTag.integrateChild(CONTEXT_VAR, childBindings, child, interpreter); + assertThat(interpreter.getContext().get(CONTEXT_VAR)).isInstanceOf(Map.class); + assertThat(((Map) interpreter.getContext().get(CONTEXT_VAR)).keySet()) + .doesNotContain(Context.IMPORT_RESOURCE_ALIAS_KEY); + } + + @Test + @SuppressWarnings("unchecked") + public void itHandlesMultiLayer() { + JinjavaInterpreter child = getChildInterpreter(interpreter, ""); + JinjavaInterpreter child2 = getChildInterpreter(child, ""); + child2.getContext().put("foo", "foo val"); + child.getContext().put("bar", "bar val"); + EagerImportTag.integrateChild( + "", + child2.getContext().getSessionBindings(), + child2, + child + ); + EagerImportTag.integrateChild( + "", + child.getContext().getSessionBindings(), + child, + interpreter + ); + assertThat(interpreter.getContext().get("foo")).isEqualTo("foo val"); + assertThat(interpreter.getContext().get("bar")).isEqualTo("bar val"); + } + + @Test + @SuppressWarnings("unchecked") + public void itHandlesMultiLayerAliased() { + String child2Alias = "double_child"; + JinjavaInterpreter child = getChildInterpreter(interpreter, CONTEXT_VAR); + JinjavaInterpreter child2 = getChildInterpreter(child, child2Alias); + + child2.render("{% set foo = 'foo val' %}"); + child.render("{% set bar = 'bar val' %}"); + + EagerImportTag.integrateChild( + child2Alias, + child2.getContext().getSessionBindings(), + child2, + child + ); + EagerImportTag.integrateChild( + CONTEXT_VAR, + child.getContext().getSessionBindings(), + child, + interpreter + ); + assertThat(interpreter.getContext().get(CONTEXT_VAR)).isInstanceOf(Map.class); + assertThat( + ((Map) interpreter.getContext().get(CONTEXT_VAR)).get(child2Alias) + ) + .isInstanceOf(Map.class); + assertThat( + ( + (Map) ( + (Map) interpreter.getContext().get(CONTEXT_VAR) + ).get(child2Alias) + ).get("foo") + ) + .isEqualTo("foo val"); + + assertThat( + ((Map) interpreter.getContext().get(CONTEXT_VAR)).get("bar") + ) + .isEqualTo("bar val"); + } + + @Test + @SuppressWarnings("unchecked") + public void itHandlesMultiLayerAliasedAndDeferred() { + String child2Alias = "double_child"; + JinjavaInterpreter child = getChildInterpreter(interpreter, CONTEXT_VAR); + JinjavaInterpreter child2 = getChildInterpreter(child, child2Alias); + + child2.render("{% set foo = 'foo val' %}"); + child.render("{% set bar = 'bar val' %}"); + child2.render("{% set foo_d = deferred %}"); + + EagerImportTag.integrateChild( + child2Alias, + child2.getContext().getSessionBindings(), + child2, + child + ); + EagerImportTag.integrateChild( + CONTEXT_VAR, + child.getContext().getSessionBindings(), + child, + interpreter + ); + assertThat(interpreter.getContext().get(CONTEXT_VAR)).isInstanceOf(PyMap.class); + assertThat( + ((Map) interpreter.getContext().get(CONTEXT_VAR)).get(child2Alias) + ) + .isInstanceOf(DeferredValue.class); + assertThat( + ( + ( + (Map) ( + (DeferredValue) ( + (Map) (interpreter.getContext().get(CONTEXT_VAR)) + ).get(child2Alias) + ).getOriginalValue() + ).get("foo") + ) + ) + .isEqualTo("foo val"); + + assertThat( + (((Map) interpreter.getContext().get(CONTEXT_VAR)).get("bar")) + ) + .isEqualTo("bar val"); + } + + @Test + @SuppressWarnings("unchecked") + public void itHandlesMultiLayerAliasedAndNullDeferred() { + String child2Alias = "double_child"; + JinjavaInterpreter child = getChildInterpreter(interpreter, CONTEXT_VAR); + JinjavaInterpreter child2 = getChildInterpreter(child, child2Alias); + + child2.render("{% set foo = 'foo val' %}"); + child.render("{% set bar = 'bar val' %}"); + child2.render("{% set foo_d = deferred %}"); + + EagerImportTag.integrateChild( + child2Alias, + child2.getContext().getSessionBindings(), + child2, + child + ); + EagerImportTag.integrateChild( + CONTEXT_VAR, + child.getContext().getSessionBindings(), + child, + interpreter + ); + assertThat(interpreter.getContext().get(CONTEXT_VAR)).isInstanceOf(PyMap.class); + assertThat( + ((Map) interpreter.getContext().get(CONTEXT_VAR)).get(child2Alias) + ) + .isInstanceOf(DeferredValue.class); + assertThat( + ( + ( + (Map) ( + (DeferredValue) ( + (Map) interpreter.getContext().get(CONTEXT_VAR) + ).get(child2Alias) + ).getOriginalValue() + ).get("foo") + ) + ) + .isEqualTo("foo val"); + + assertThat( + (((Map) interpreter.getContext().get(CONTEXT_VAR)).get("bar")) + ) + .isEqualTo("bar val"); + } + + @Test + @SuppressWarnings("unchecked") + public void itHandlesMultiLayerDeferred() { + JinjavaInterpreter child = getChildInterpreter(interpreter, ""); + JinjavaInterpreter child2 = getChildInterpreter(child, ""); + child2.getContext().put("foo", DeferredValue.instance("foo val")); + child.getContext().put("bar", DeferredValue.instance("bar val")); + + EagerImportTag.integrateChild( + "", + child2.getContext().getSessionBindings(), + child2, + child + ); + EagerImportTag.integrateChild( + "", + child.getContext().getSessionBindings(), + child, + interpreter + ); + assertThat(interpreter.getContext().get("foo")).isInstanceOf(DeferredValue.class); + assertThat( + (((DeferredValue) (interpreter.getContext().get("foo"))).getOriginalValue()) + ) + .isEqualTo("foo val"); + + assertThat(interpreter.getContext().get("bar")).isInstanceOf(DeferredValue.class); + assertThat( + (((DeferredValue) (interpreter.getContext().get("bar"))).getOriginalValue()) + ) + .isEqualTo("bar val"); + } + + @Test + @SuppressWarnings("unchecked") + public void itHandlesMultiLayerSomeAliased() { + String child2Alias = ""; + String child3Alias = "triple_child"; + JinjavaInterpreter child = getChildInterpreter(interpreter, CONTEXT_VAR); + JinjavaInterpreter child2 = getChildInterpreter(child, child2Alias); + JinjavaInterpreter child3 = getChildInterpreter(child2, child3Alias); + + child2.render("{% set foo = 'foo val' %}"); + child.render("{% set bar = 'bar val' %}"); + child3.render("{% set foobar = 'foobar val' %}"); + + EagerImportTag.integrateChild( + child3Alias, + child3.getContext().getSessionBindings(), + child3, + child2 + ); + EagerImportTag.integrateChild( + child2Alias, + child2.getContext().getSessionBindings(), + child2, + child + ); + EagerImportTag.integrateChild( + CONTEXT_VAR, + child.getContext().getSessionBindings(), + child, + interpreter + ); + assertThat(interpreter.getContext().get(CONTEXT_VAR)).isInstanceOf(Map.class); + assertThat( + ((Map) interpreter.getContext().get(CONTEXT_VAR)).get(child3Alias) + ) + .isInstanceOf(Map.class); + assertThat( + ( + (Map) ( + (Map) interpreter.getContext().get(CONTEXT_VAR) + ).get(child3Alias) + ).get("foobar") + ) + .isEqualTo("foobar val"); + + assertThat( + ((Map) interpreter.getContext().get(CONTEXT_VAR)).get("bar") + ) + .isEqualTo("bar val"); + assertThat( + ((Map) interpreter.getContext().get(CONTEXT_VAR)).get("foo") + ) + .isEqualTo("foo val"); + } + + @Test + @SuppressWarnings("unchecked") + public void itHandlesMultiLayerAliasedAndParallel() { + String child2Alias = "double_child"; + String child2BAlias = "double_child_b"; + + JinjavaInterpreter child = getChildInterpreter(interpreter, CONTEXT_VAR); + JinjavaInterpreter child2 = getChildInterpreter(child, child2Alias); + JinjavaInterpreter child2B = getChildInterpreter(child, child2BAlias); + + child2.render("{% set foo = 'foo val' %}"); + child.render("{% set bar = 'bar val' %}"); + child2B.render("{% set foo_b = 'foo_b val' %}"); + + EagerImportTag.integrateChild( + child2Alias, + child2.getContext().getSessionBindings(), + child2, + child + ); + EagerImportTag.integrateChild( + child2BAlias, + child2B.getContext().getSessionBindings(), + child2B, + child + ); + EagerImportTag.integrateChild( + CONTEXT_VAR, + child.getContext().getSessionBindings(), + child, + interpreter + ); + assertThat(interpreter.getContext().get(CONTEXT_VAR)).isInstanceOf(Map.class); + assertThat( + ((Map) interpreter.getContext().get(CONTEXT_VAR)).get(child2Alias) + ) + .isInstanceOf(Map.class); + assertThat( + ((Map) interpreter.getContext().get(CONTEXT_VAR)).get( + child2BAlias + ) + ) + .isInstanceOf(Map.class); + assertThat( + ( + (Map) ( + (Map) interpreter.getContext().get(CONTEXT_VAR) + ).get(child2Alias) + ).get("foo") + ) + .isEqualTo("foo val"); + assertThat( + ( + (Map) ( + (Map) interpreter.getContext().get(CONTEXT_VAR) + ).get(child2BAlias) + ).get("foo_b") + ) + .isEqualTo("foo_b val"); + + assertThat( + ((Map) interpreter.getContext().get(CONTEXT_VAR)).get("bar") + ) + .isEqualTo("bar val"); + } + + @Test + public void itHandlesTripleLayer() { + setupResourceLocator(); + context.put("a_val", "a"); + context.put("b_val", "b"); + context.put("c_val", "c"); + interpreter.render("{% import 'import-tree-c.jinja' as c %}"); + assertThat(interpreter.render("{{ c.b.a.foo_a }}")).isEqualTo("a"); + assertThat(interpreter.render("{{ c.b.foo_b }}")).isEqualTo("ba"); + assertThat(interpreter.render("{{ c.foo_c }}")).isEqualTo("cbaa"); + } + + @Test + public void itDefersTripleLayer() { + setupResourceLocator(); + context.put("a_val", DeferredValue.instance("a")); + context.put("b_val", "b"); + context.put("c_val", "c"); + String result = interpreter.render("{% import 'import-tree-c.jinja' as c %}{{ c }}"); + assertThat(interpreter.render("{{ c.b.a.foo_a }}")).isEqualTo("{{ c.b.a.foo_a }}"); + assertThat(interpreter.render("{{ c.b.foo_b }}")).isEqualTo("{{ c.b.foo_b }}"); + assertThat(interpreter.render("{{ c.foo_c }}")).isEqualTo("{{ c.foo_c }}"); + context.put("a_val", "a"); + // There are some extras due to deferred values copying up the context stack. + assertThat(interpreter.render(result).trim()) + .isEqualTo( + "{'b':{'foo_b':'ba','a':{'foo_a':'a','something':'somn'},'foo_a':'a'}" + + ",'foo_c':'cbaa','a':{'foo_a':'a','something':'somn'},'foo_b':'ba','foo_a':'a'}" + ); + } + + @Test + public void itHandlesQuadLayer() { + setupResourceLocator(); + context.put("a_val", "a"); + context.put("b_val", "b"); + context.put("c_val", "c"); + interpreter.render("{% import 'import-tree-d.jinja' as d %}"); + assertThat(interpreter.render("{{ d.foo_d }}")).isEqualTo("cbaabaa"); + assertThat(interpreter.render("{{ d.resolvable }}")).isEqualTo("12345"); + assertThat(interpreter.render("{{ d.bar }}")).isEqualTo("cbaabaaba"); + } + + @Test + public void itDefersQuadLayer() { + setupResourceLocator(); + context.put("a_val", DeferredValue.instance("a")); + context.put("b_val", "b"); + context.put("c_val", "c"); + String result = interpreter.render( + "{% import 'import-tree-d.jinja' as d %}{{ d.resolvable }} {{ d.bar }}" + ); + context.put("a_val", "a"); + assertThat(interpreter.render(result).trim()).isEqualTo("12345 cbaabaaba"); + } + + private static JinjavaInterpreter getChildInterpreter( + JinjavaInterpreter interpreter, + String alias + ) { + JinjavaInterpreter child = interpreter + .getConfig() + .getInterpreterFactory() + .newInstance(interpreter); + child.getContext().put(Context.IMPORT_RESOURCE_PATH_KEY, TEMPLATE_FILE); + EagerImportTag.setupImportAlias(alias, child, interpreter); + return child; + } + + private void setupResourceLocator() { + jinjava.setResourceLocator( + new ResourceLocator() { + private RelativePathResolver relativePathResolver = new RelativePathResolver(); + + @Override + public String getString( + String fullName, + Charset encoding, + JinjavaInterpreter interpreter + ) + throws IOException { + return Resources.toString( + Resources.getResource(String.format("tags/eager/importtag/%s", fullName)), + StandardCharsets.UTF_8 + ); + } + + @Override + public Optional getLocationResolver() { + return Optional.of(relativePathResolver); + } + } + ); + } + + @Test + @Ignore + @Override + public void itReconstructsDeferredImportTag() {} + + @Test + @Ignore + @Override + public void itDoesNotRenderTagsDependingOnDeferredImport() {} + + @Test + @Ignore + @Override + public void itAddsAllDeferredNodesOfImport() {} + + @Test + @Ignore + @Override + public void itAddsAllDeferredNodesOfGlobalImport() {} + + @Test + @Ignore + @Override + public void itSetsErrorLineNumbersCorrectly() {} + + @Test + @Ignore + @Override + public void itSetsErrorLineNumbersCorrectlyForImportedMacros() {} + + @Test + @Ignore + @Override + public void itDefersImportedVariableKey() {} + + @Test + @Ignore + @Override + public void itDoesNotRenderTagsDependingOnDeferredGlobalImport() {} + + @Test + @Ignore + @Override + public void itSetsErrorLineNumbersCorrectlyThroughIncludeTag() {} +} diff --git a/src/test/resources/eager/handles-deferred-import-vars.expected.jinja b/src/test/resources/eager/handles-deferred-import-vars.expected.jinja index 747182b7e..b192ec5c4 100644 --- a/src/test/resources/eager/handles-deferred-import-vars.expected.jinja +++ b/src/test/resources/eager/handles-deferred-import-vars.expected.jinja @@ -4,9 +4,9 @@ foo: {% macro foo() %}Hello {{ myname }}{% endmacro %}{{ foo() }} bar: {{ bar }} ----{% set myname = deferred + 7 %}{% set simple = {} %} -{% do simple.update({'bar': myname + 19}) %} +---{% set myname = deferred + 7 %} +{% set bar = myname + 19 %}{% set simple = {} %}{% do simple.update({'bar': bar}) %} {% macro foo() %}Hello {{ myname }}{% endmacro %}{{ foo() }} simple.foo: {% macro simple.foo() %}Hello {{ myname }}{% endmacro %}{{ simple.foo() }} -simple.bar: {{ simple.bar }} +simple.bar: {{ simple.bar }} \ No newline at end of file diff --git a/src/test/resources/eager/handles-non-deferred-import-vars.expected.jinja b/src/test/resources/eager/handles-non-deferred-import-vars.expected.jinja new file mode 100644 index 000000000..2d8a8c116 --- /dev/null +++ b/src/test/resources/eager/handles-non-deferred-import-vars.expected.jinja @@ -0,0 +1,5 @@ +foo: Hello 3 +bar: 22 +--- +simple.foo: Hello 7 +simple.bar: 26 diff --git a/src/test/resources/eager/handles-non-deferred-import-vars.jinja b/src/test/resources/eager/handles-non-deferred-import-vars.jinja new file mode 100644 index 000000000..bf42a2f8e --- /dev/null +++ b/src/test/resources/eager/handles-non-deferred-import-vars.jinja @@ -0,0 +1,9 @@ +{%- set myname = (1 + 2) -%} +{%- from "macro-and-set.jinja" import foo, bar -%} +foo: {{ foo() }} +bar: {{ bar }} +--- +{%- set myname = (3 + 4) -%} +{%- import "macro-and-set.jinja" as simple -%} +simple.foo: {{ simple.foo() }} +simple.bar: {{ simple.bar }} diff --git a/src/test/resources/tags/eager/importtag/import-tree-a.jinja b/src/test/resources/tags/eager/importtag/import-tree-a.jinja new file mode 100644 index 000000000..16096b64c --- /dev/null +++ b/src/test/resources/tags/eager/importtag/import-tree-a.jinja @@ -0,0 +1,2 @@ +{% set something = 'somn' %} +{% set foo_a = a_val %} diff --git a/src/test/resources/tags/eager/importtag/import-tree-b.jinja b/src/test/resources/tags/eager/importtag/import-tree-b.jinja new file mode 100644 index 000000000..454ee0e45 --- /dev/null +++ b/src/test/resources/tags/eager/importtag/import-tree-b.jinja @@ -0,0 +1,2 @@ +{% import 'import-tree-a.jinja' as a %} +{% set foo_b = b_val + a.foo_a %} diff --git a/src/test/resources/tags/eager/importtag/import-tree-c.jinja b/src/test/resources/tags/eager/importtag/import-tree-c.jinja new file mode 100644 index 000000000..e7096603a --- /dev/null +++ b/src/test/resources/tags/eager/importtag/import-tree-c.jinja @@ -0,0 +1,2 @@ +{% import 'import-tree-b.jinja' as b %} +{% set foo_c = c_val + b.foo_b + b.a.foo_a %} diff --git a/src/test/resources/tags/eager/importtag/import-tree-d.jinja b/src/test/resources/tags/eager/importtag/import-tree-d.jinja new file mode 100644 index 000000000..ec951a109 --- /dev/null +++ b/src/test/resources/tags/eager/importtag/import-tree-d.jinja @@ -0,0 +1,5 @@ +{% import 'import-tree-c.jinja' as c %} +{% set foo_d = c.foo_c + c.b.foo_b + c.b.a.foo_a %} +{% import 'import-tree-b.jinja' as b2 %} +{% set resolvable = 12345 %} +{% set bar = foo_d + b2.foo_b %}