-
Notifications
You must be signed in to change notification settings - Fork 170
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1118 from HubSpot/sharing-alias-name
Allow imported files to have variables sharing alias of import name
- Loading branch information
Showing
30 changed files
with
864 additions
and
651 deletions.
There are no files selected for viewing
424 changes: 18 additions & 406 deletions
424
src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerImportTag.java
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
272 changes: 272 additions & 0 deletions
272
src/main/java/com/hubspot/jinjava/lib/tag/eager/importing/AliasedEagerImportingStrategy.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,272 @@ | ||
package com.hubspot.jinjava.lib.tag.eager.importing; | ||
|
||
import com.google.common.annotations.VisibleForTesting; | ||
import com.google.common.collect.ImmutableMap; | ||
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.eager.DeferredToken; | ||
import com.hubspot.jinjava.objects.collections.PyMap; | ||
import com.hubspot.jinjava.objects.serialization.PyishObjectMapper; | ||
import com.hubspot.jinjava.util.EagerReconstructionUtils; | ||
import com.hubspot.jinjava.util.PrefixToPreserveState; | ||
import java.util.Collections; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
import java.util.Optional; | ||
import java.util.StringJoiner; | ||
import java.util.stream.Stream; | ||
|
||
public class AliasedEagerImportingStrategy implements EagerImportingStrategy { | ||
private static final String TEMPORARY_IMPORT_ALIAS_PREFIX = "__temp_import_alias_"; | ||
private static final String TEMPORARY_IMPORT_ALIAS_FORMAT = | ||
TEMPORARY_IMPORT_ALIAS_PREFIX + "%d__"; | ||
|
||
public static Optional<String> getTemporaryImportAlias(Context context) { | ||
return context | ||
.getImportResourceAlias() | ||
.map(AliasedEagerImportingStrategy::getTemporaryImportAlias); | ||
} | ||
|
||
public static boolean isTemporaryImportAlias(String varName) { | ||
// This is just faster than checking a regex | ||
return varName.startsWith(TEMPORARY_IMPORT_ALIAS_PREFIX); | ||
} | ||
|
||
private static String getTemporaryImportAlias(String fullAlias) { | ||
return String.format( | ||
TEMPORARY_IMPORT_ALIAS_FORMAT, | ||
Math.abs(Objects.hashCode(fullAlias)) | ||
); | ||
} | ||
|
||
private final ImportingData importingData; | ||
private final String currentImportAlias; | ||
private final String fullImportAlias; | ||
|
||
@VisibleForTesting | ||
public AliasedEagerImportingStrategy( | ||
ImportingData importingData, | ||
String currentImportAlias | ||
) { | ||
this.importingData = importingData; | ||
this.currentImportAlias = currentImportAlias; | ||
Optional<String> maybeParentImportAlias = importingData | ||
.getOriginalInterpreter() | ||
.getContext() | ||
.getImportResourceAlias(); | ||
if (maybeParentImportAlias.isPresent()) { | ||
fullImportAlias = | ||
String.format("%s.%s", maybeParentImportAlias.get(), currentImportAlias); | ||
} else { | ||
fullImportAlias = currentImportAlias; | ||
} | ||
} | ||
|
||
@Override | ||
public String handleDeferredTemplateFile(DeferredValueException e) { | ||
return ( | ||
importingData.getInitialPathSetter() + | ||
new PrefixToPreserveState( | ||
EagerReconstructionUtils.handleDeferredTokenAndReconstructReferences( | ||
importingData.getOriginalInterpreter(), | ||
DeferredToken | ||
.builderFromToken(importingData.getTagToken()) | ||
.addUsedDeferredWords(Stream.of(importingData.getHelpers().get(0))) | ||
.addSetDeferredWords(Stream.of(currentImportAlias)) | ||
.build() | ||
) | ||
) + | ||
importingData.getTagToken().getImage() | ||
); | ||
} | ||
|
||
@Override | ||
public void setup(JinjavaInterpreter child) { | ||
child.getContext().getScope().put(Context.IMPORT_RESOURCE_ALIAS_KEY, fullImportAlias); | ||
child.getContext().put(Context.IMPORT_RESOURCE_ALIAS_KEY, fullImportAlias); | ||
constructFullAliasPathMap(currentImportAlias, child); | ||
getMapForCurrentContextAlias(currentImportAlias, child); | ||
importingData | ||
.getOriginalInterpreter() | ||
.getContext() | ||
.put(getTemporaryImportAlias(fullImportAlias), DeferredValue.instance()); | ||
} | ||
|
||
@Override | ||
public void integrateChild(JinjavaInterpreter child) { | ||
JinjavaInterpreter parent = importingData.getOriginalInterpreter(); | ||
for (MacroFunction macro : child.getContext().getGlobalMacros().values()) { | ||
if (parent.getContext().isDeferredExecutionMode()) { | ||
macro.setDeferred(true); | ||
} | ||
} | ||
Map<String, Object> childBindings = child.getContext().getSessionBindings(); | ||
childBindings.putAll(child.getContext().getGlobalMacros()); | ||
String temporaryImportAlias = getTemporaryImportAlias(fullImportAlias); | ||
Map<String, Object> mapForCurrentContextAlias = getMapForCurrentContextAlias( | ||
currentImportAlias, | ||
child | ||
); | ||
childBindings.remove(temporaryImportAlias); | ||
importingData.getOriginalInterpreter().getContext().remove(temporaryImportAlias); | ||
// Remove meta keys | ||
childBindings.remove(Context.GLOBAL_MACROS_SCOPE_KEY); | ||
childBindings.remove(Context.IMPORT_RESOURCE_ALIAS_KEY); | ||
mapForCurrentContextAlias.putAll(childBindings); | ||
} | ||
|
||
@Override | ||
public String getFinalOutput( | ||
String newPathSetter, | ||
String output, | ||
JinjavaInterpreter child | ||
) { | ||
String temporaryImportAlias = getTemporaryImportAlias(fullImportAlias); | ||
return ( | ||
newPathSetter + | ||
EagerReconstructionUtils.buildBlockOrInlineSetTag( | ||
temporaryImportAlias, | ||
Collections.emptyMap(), | ||
importingData.getOriginalInterpreter() | ||
) + | ||
wrapInChildScope( | ||
EagerImportingStrategy.getSetTagForDeferredChildBindings( | ||
child, | ||
currentImportAlias, | ||
child.getContext() | ||
) + | ||
output, | ||
child | ||
) + | ||
EagerReconstructionUtils.buildSetTag( | ||
ImmutableMap.of(currentImportAlias, temporaryImportAlias), | ||
importingData.getOriginalInterpreter(), | ||
true | ||
) + | ||
importingData.getInitialPathSetter() | ||
); | ||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
private static void constructFullAliasPathMap( | ||
String currentImportAlias, | ||
JinjavaInterpreter child | ||
) { | ||
String fullImportAlias = child | ||
.getContext() | ||
.getImportResourceAlias() | ||
.orElse(currentImportAlias); | ||
String[] allAliases = fullImportAlias.split("\\."); | ||
Map<String, Object> 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<String, Object>) maybeNextMap; | ||
} else if ( | ||
maybeNextMap instanceof DeferredValue && | ||
((DeferredValue) maybeNextMap).getOriginalValue() instanceof Map | ||
) { | ||
currentMap = | ||
(Map<String, Object>) ((DeferredValue) maybeNextMap).getOriginalValue(); | ||
} else { | ||
throw new InterpretException("Encountered a problem with import alias maps"); | ||
} | ||
} | ||
currentMap.put( | ||
allAliases[allAliases.length - 1], | ||
child.getContext().isDeferredExecutionMode() | ||
? DeferredValue.instance(new PyMap(new HashMap<>())) | ||
: new PyMap(new HashMap<>()) | ||
); | ||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
private static Map<String, Object> getMapForCurrentContextAlias( | ||
String currentImportAlias, | ||
JinjavaInterpreter child | ||
) { | ||
Object parentValueForChild = child | ||
.getContext() | ||
.getParent() | ||
.getSessionBindings() | ||
.get(currentImportAlias); | ||
if (parentValueForChild instanceof Map) { | ||
return (Map<String, Object>) parentValueForChild; | ||
} else if (parentValueForChild instanceof DeferredValue) { | ||
if (((DeferredValue) parentValueForChild).getOriginalValue() instanceof Map) { | ||
return (Map<String, Object>) ( | ||
(DeferredValue) parentValueForChild | ||
).getOriginalValue(); | ||
} | ||
Map<String, Object> newMap = new PyMap(new HashMap<>()); | ||
child | ||
.getContext() | ||
.getParent() | ||
.put(currentImportAlias, DeferredValue.instance(newMap)); | ||
return newMap; | ||
} else { | ||
Map<String, Object> newMap = new PyMap(new HashMap<>()); | ||
child | ||
.getContext() | ||
.getParent() | ||
.put( | ||
currentImportAlias, | ||
child.getContext().isDeferredExecutionMode() | ||
? DeferredValue.instance(newMap) | ||
: newMap | ||
); | ||
return newMap; | ||
} | ||
} | ||
|
||
private String wrapInChildScope(String output, JinjavaInterpreter child) { | ||
String combined = | ||
output + getDoTagToPreserve(importingData.getOriginalInterpreter(), child); | ||
// So that any set variables other than the alias won't exist outside the child's scope | ||
return EagerReconstructionUtils.wrapInChildScope( | ||
combined, | ||
importingData.getOriginalInterpreter() | ||
); | ||
} | ||
|
||
private String getDoTagToPreserve( | ||
JinjavaInterpreter interpreter, | ||
JinjavaInterpreter child | ||
) { | ||
StringJoiner keyValueJoiner = new StringJoiner(","); | ||
String temporaryImportAlias = getTemporaryImportAlias(fullImportAlias); | ||
Map<String, Object> currentAliasMap = getMapForCurrentContextAlias( | ||
currentImportAlias, | ||
child | ||
); | ||
for (Map.Entry<String, Object> entry : currentAliasMap.entrySet()) { | ||
if (entry.getKey().equals(temporaryImportAlias)) { | ||
continue; | ||
} | ||
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(), | ||
PyishObjectMapper.getAsPyishString(entry.getValue()) | ||
) | ||
); | ||
} | ||
} | ||
if (keyValueJoiner.length() > 0) { | ||
return EagerReconstructionUtils.buildDoUpdateTag( | ||
temporaryImportAlias, | ||
"{" + keyValueJoiner.toString() + "}", | ||
interpreter | ||
); | ||
} | ||
return ""; | ||
} | ||
} |
42 changes: 42 additions & 0 deletions
42
src/main/java/com/hubspot/jinjava/lib/tag/eager/importing/EagerImportingStrategy.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package com.hubspot.jinjava.lib.tag.eager.importing; | ||
|
||
import com.hubspot.jinjava.interpret.DeferredValue; | ||
import com.hubspot.jinjava.interpret.DeferredValueException; | ||
import com.hubspot.jinjava.interpret.JinjavaInterpreter; | ||
import com.hubspot.jinjava.util.EagerReconstructionUtils; | ||
import java.util.Map; | ||
import java.util.stream.Collectors; | ||
|
||
public interface EagerImportingStrategy { | ||
String handleDeferredTemplateFile(DeferredValueException e); | ||
void setup(JinjavaInterpreter child); | ||
|
||
void integrateChild(JinjavaInterpreter child); | ||
String getFinalOutput(String newPathSetter, String output, JinjavaInterpreter child); | ||
|
||
static String getSetTagForDeferredChildBindings( | ||
JinjavaInterpreter interpreter, | ||
String currentImportAlias, | ||
Map<String, Object> childBindings | ||
) { | ||
return childBindings | ||
.entrySet() | ||
.stream() | ||
.filter( | ||
entry -> | ||
entry.getValue() instanceof DeferredValue && | ||
((DeferredValue) entry.getValue()).getOriginalValue() != null | ||
) | ||
.filter(entry -> !interpreter.getContext().containsKey(entry.getKey())) | ||
.filter(entry -> !entry.getKey().equals(currentImportAlias)) | ||
.map( | ||
entry -> | ||
EagerReconstructionUtils.buildBlockOrInlineSetTag( // don't register deferred token so that we don't defer them on higher context scopes; they only exist in the child scope | ||
entry.getKey(), | ||
((DeferredValue) entry.getValue()).getOriginalValue(), | ||
interpreter | ||
) | ||
) | ||
.collect(Collectors.joining()); | ||
} | ||
} |
Oops, something went wrong.