Skip to content

Commit 1963975

Browse files
authored
Merge pull request #1118 from HubSpot/sharing-alias-name
Allow imported files to have variables sharing alias of import name
2 parents 963ad10 + c9a1165 commit 1963975

30 files changed

Lines changed: 864 additions & 651 deletions

src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerImportTag.java

Lines changed: 18 additions & 406 deletions
Large diffs are not rendered by default.

src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerIncludeTag.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.google.common.annotations.Beta;
44
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
55
import com.hubspot.jinjava.lib.tag.IncludeTag;
6+
import com.hubspot.jinjava.lib.tag.eager.importing.EagerImportingStrategyFactory;
67
import com.hubspot.jinjava.loader.RelativePathResolver;
78
import com.hubspot.jinjava.tree.TagNode;
89
import com.hubspot.jinjava.util.EagerReconstructionUtils;
@@ -29,7 +30,7 @@ public String innerInterpret(TagNode tagNode, JinjavaInterpreter interpreter) {
2930
tagNode.getStartPosition()
3031
);
3132
templateFile = interpreter.resolveResourceLocation(templateFile);
32-
final String initialPathSetter = EagerImportTag.getSetTagForCurrentPath(
33+
final String initialPathSetter = EagerImportingStrategyFactory.getSetTagForCurrentPath(
3334
interpreter
3435
);
3536
final String newPathSetter = EagerReconstructionUtils.buildBlockOrInlineSetTag(

src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerSetTagStrategy.java

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.google.common.annotations.Beta;
44
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
55
import com.hubspot.jinjava.lib.tag.SetTag;
6+
import com.hubspot.jinjava.lib.tag.eager.importing.AliasedEagerImportingStrategy;
67
import com.hubspot.jinjava.tree.TagNode;
78
import com.hubspot.jinjava.util.EagerReconstructionUtils;
89
import com.hubspot.jinjava.util.PrefixToPreserveState;
@@ -149,35 +150,34 @@ protected PrefixToPreserveState getPrefixToPreserveState(
149150
return prefixToPreserveState;
150151
}
151152

152-
protected String getSuffixToPreserveState(
153+
public static String getSuffixToPreserveState(
153154
String variables,
154155
JinjavaInterpreter interpreter
155156
) {
157+
if (variables.isEmpty()) {
158+
return "";
159+
}
156160
StringBuilder suffixToPreserveState = new StringBuilder();
157-
Optional<String> maybeFullImportAlias = interpreter
158-
.getContext()
159-
.getImportResourceAlias();
160-
if (maybeFullImportAlias.isPresent()) {
161-
String currentImportAlias = maybeFullImportAlias
162-
.get()
163-
.substring(maybeFullImportAlias.get().lastIndexOf(".") + 1);
164-
String filteredVariables = Arrays
165-
.stream(variables.split(","))
166-
.filter(var -> !var.equals(currentImportAlias))
167-
.collect(Collectors.joining(","));
168-
if (!filteredVariables.isEmpty()) {
169-
String updateString = getUpdateString(filteredVariables);
170-
suffixToPreserveState.append(
171-
interpreter.render(
172-
EagerReconstructionUtils.buildDoUpdateTag(
173-
currentImportAlias,
174-
updateString,
175-
interpreter
176-
)
177-
)
178-
);
179-
}
161+
Optional<String> maybeTemporaryImportAlias = AliasedEagerImportingStrategy.getTemporaryImportAlias(
162+
interpreter.getContext()
163+
);
164+
if (
165+
maybeTemporaryImportAlias.isPresent() &&
166+
!AliasedEagerImportingStrategy.isTemporaryImportAlias(variables) &&
167+
!interpreter.getContext().getMetaContextVariables().contains(variables)
168+
) {
169+
String updateString = getUpdateString(variables);
170+
171+
// Don't need to render because the temporary import alias's value is always deferred, and rendering will do nothing
172+
suffixToPreserveState.append(
173+
EagerReconstructionUtils.buildDoUpdateTag(
174+
maybeTemporaryImportAlias.get(),
175+
updateString,
176+
interpreter
177+
)
178+
);
180179
}
180+
181181
return suffixToPreserveState.toString();
182182
}
183183

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
package com.hubspot.jinjava.lib.tag.eager.importing;
2+
3+
import com.google.common.annotations.VisibleForTesting;
4+
import com.google.common.collect.ImmutableMap;
5+
import com.hubspot.jinjava.interpret.Context;
6+
import com.hubspot.jinjava.interpret.DeferredValue;
7+
import com.hubspot.jinjava.interpret.DeferredValueException;
8+
import com.hubspot.jinjava.interpret.InterpretException;
9+
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
10+
import com.hubspot.jinjava.lib.fn.MacroFunction;
11+
import com.hubspot.jinjava.lib.tag.eager.DeferredToken;
12+
import com.hubspot.jinjava.objects.collections.PyMap;
13+
import com.hubspot.jinjava.objects.serialization.PyishObjectMapper;
14+
import com.hubspot.jinjava.util.EagerReconstructionUtils;
15+
import com.hubspot.jinjava.util.PrefixToPreserveState;
16+
import java.util.Collections;
17+
import java.util.HashMap;
18+
import java.util.Map;
19+
import java.util.Objects;
20+
import java.util.Optional;
21+
import java.util.StringJoiner;
22+
import java.util.stream.Stream;
23+
24+
public class AliasedEagerImportingStrategy implements EagerImportingStrategy {
25+
private static final String TEMPORARY_IMPORT_ALIAS_PREFIX = "__temp_import_alias_";
26+
private static final String TEMPORARY_IMPORT_ALIAS_FORMAT =
27+
TEMPORARY_IMPORT_ALIAS_PREFIX + "%d__";
28+
29+
public static Optional<String> getTemporaryImportAlias(Context context) {
30+
return context
31+
.getImportResourceAlias()
32+
.map(AliasedEagerImportingStrategy::getTemporaryImportAlias);
33+
}
34+
35+
public static boolean isTemporaryImportAlias(String varName) {
36+
// This is just faster than checking a regex
37+
return varName.startsWith(TEMPORARY_IMPORT_ALIAS_PREFIX);
38+
}
39+
40+
private static String getTemporaryImportAlias(String fullAlias) {
41+
return String.format(
42+
TEMPORARY_IMPORT_ALIAS_FORMAT,
43+
Math.abs(Objects.hashCode(fullAlias))
44+
);
45+
}
46+
47+
private final ImportingData importingData;
48+
private final String currentImportAlias;
49+
private final String fullImportAlias;
50+
51+
@VisibleForTesting
52+
public AliasedEagerImportingStrategy(
53+
ImportingData importingData,
54+
String currentImportAlias
55+
) {
56+
this.importingData = importingData;
57+
this.currentImportAlias = currentImportAlias;
58+
Optional<String> maybeParentImportAlias = importingData
59+
.getOriginalInterpreter()
60+
.getContext()
61+
.getImportResourceAlias();
62+
if (maybeParentImportAlias.isPresent()) {
63+
fullImportAlias =
64+
String.format("%s.%s", maybeParentImportAlias.get(), currentImportAlias);
65+
} else {
66+
fullImportAlias = currentImportAlias;
67+
}
68+
}
69+
70+
@Override
71+
public String handleDeferredTemplateFile(DeferredValueException e) {
72+
return (
73+
importingData.getInitialPathSetter() +
74+
new PrefixToPreserveState(
75+
EagerReconstructionUtils.handleDeferredTokenAndReconstructReferences(
76+
importingData.getOriginalInterpreter(),
77+
DeferredToken
78+
.builderFromToken(importingData.getTagToken())
79+
.addUsedDeferredWords(Stream.of(importingData.getHelpers().get(0)))
80+
.addSetDeferredWords(Stream.of(currentImportAlias))
81+
.build()
82+
)
83+
) +
84+
importingData.getTagToken().getImage()
85+
);
86+
}
87+
88+
@Override
89+
public void setup(JinjavaInterpreter child) {
90+
child.getContext().getScope().put(Context.IMPORT_RESOURCE_ALIAS_KEY, fullImportAlias);
91+
child.getContext().put(Context.IMPORT_RESOURCE_ALIAS_KEY, fullImportAlias);
92+
constructFullAliasPathMap(currentImportAlias, child);
93+
getMapForCurrentContextAlias(currentImportAlias, child);
94+
importingData
95+
.getOriginalInterpreter()
96+
.getContext()
97+
.put(getTemporaryImportAlias(fullImportAlias), DeferredValue.instance());
98+
}
99+
100+
@Override
101+
public void integrateChild(JinjavaInterpreter child) {
102+
JinjavaInterpreter parent = importingData.getOriginalInterpreter();
103+
for (MacroFunction macro : child.getContext().getGlobalMacros().values()) {
104+
if (parent.getContext().isDeferredExecutionMode()) {
105+
macro.setDeferred(true);
106+
}
107+
}
108+
Map<String, Object> childBindings = child.getContext().getSessionBindings();
109+
childBindings.putAll(child.getContext().getGlobalMacros());
110+
String temporaryImportAlias = getTemporaryImportAlias(fullImportAlias);
111+
Map<String, Object> mapForCurrentContextAlias = getMapForCurrentContextAlias(
112+
currentImportAlias,
113+
child
114+
);
115+
childBindings.remove(temporaryImportAlias);
116+
importingData.getOriginalInterpreter().getContext().remove(temporaryImportAlias);
117+
// Remove meta keys
118+
childBindings.remove(Context.GLOBAL_MACROS_SCOPE_KEY);
119+
childBindings.remove(Context.IMPORT_RESOURCE_ALIAS_KEY);
120+
mapForCurrentContextAlias.putAll(childBindings);
121+
}
122+
123+
@Override
124+
public String getFinalOutput(
125+
String newPathSetter,
126+
String output,
127+
JinjavaInterpreter child
128+
) {
129+
String temporaryImportAlias = getTemporaryImportAlias(fullImportAlias);
130+
return (
131+
newPathSetter +
132+
EagerReconstructionUtils.buildBlockOrInlineSetTag(
133+
temporaryImportAlias,
134+
Collections.emptyMap(),
135+
importingData.getOriginalInterpreter()
136+
) +
137+
wrapInChildScope(
138+
EagerImportingStrategy.getSetTagForDeferredChildBindings(
139+
child,
140+
currentImportAlias,
141+
child.getContext()
142+
) +
143+
output,
144+
child
145+
) +
146+
EagerReconstructionUtils.buildSetTag(
147+
ImmutableMap.of(currentImportAlias, temporaryImportAlias),
148+
importingData.getOriginalInterpreter(),
149+
true
150+
) +
151+
importingData.getInitialPathSetter()
152+
);
153+
}
154+
155+
@SuppressWarnings("unchecked")
156+
private static void constructFullAliasPathMap(
157+
String currentImportAlias,
158+
JinjavaInterpreter child
159+
) {
160+
String fullImportAlias = child
161+
.getContext()
162+
.getImportResourceAlias()
163+
.orElse(currentImportAlias);
164+
String[] allAliases = fullImportAlias.split("\\.");
165+
Map<String, Object> currentMap = child.getContext().getParent();
166+
for (int i = 0; i < allAliases.length - 1; i++) {
167+
Object maybeNextMap = currentMap.get(allAliases[i]);
168+
if (maybeNextMap instanceof Map) {
169+
currentMap = (Map<String, Object>) maybeNextMap;
170+
} else if (
171+
maybeNextMap instanceof DeferredValue &&
172+
((DeferredValue) maybeNextMap).getOriginalValue() instanceof Map
173+
) {
174+
currentMap =
175+
(Map<String, Object>) ((DeferredValue) maybeNextMap).getOriginalValue();
176+
} else {
177+
throw new InterpretException("Encountered a problem with import alias maps");
178+
}
179+
}
180+
currentMap.put(
181+
allAliases[allAliases.length - 1],
182+
child.getContext().isDeferredExecutionMode()
183+
? DeferredValue.instance(new PyMap(new HashMap<>()))
184+
: new PyMap(new HashMap<>())
185+
);
186+
}
187+
188+
@SuppressWarnings("unchecked")
189+
private static Map<String, Object> getMapForCurrentContextAlias(
190+
String currentImportAlias,
191+
JinjavaInterpreter child
192+
) {
193+
Object parentValueForChild = child
194+
.getContext()
195+
.getParent()
196+
.getSessionBindings()
197+
.get(currentImportAlias);
198+
if (parentValueForChild instanceof Map) {
199+
return (Map<String, Object>) parentValueForChild;
200+
} else if (parentValueForChild instanceof DeferredValue) {
201+
if (((DeferredValue) parentValueForChild).getOriginalValue() instanceof Map) {
202+
return (Map<String, Object>) (
203+
(DeferredValue) parentValueForChild
204+
).getOriginalValue();
205+
}
206+
Map<String, Object> newMap = new PyMap(new HashMap<>());
207+
child
208+
.getContext()
209+
.getParent()
210+
.put(currentImportAlias, DeferredValue.instance(newMap));
211+
return newMap;
212+
} else {
213+
Map<String, Object> newMap = new PyMap(new HashMap<>());
214+
child
215+
.getContext()
216+
.getParent()
217+
.put(
218+
currentImportAlias,
219+
child.getContext().isDeferredExecutionMode()
220+
? DeferredValue.instance(newMap)
221+
: newMap
222+
);
223+
return newMap;
224+
}
225+
}
226+
227+
private String wrapInChildScope(String output, JinjavaInterpreter child) {
228+
String combined =
229+
output + getDoTagToPreserve(importingData.getOriginalInterpreter(), child);
230+
// So that any set variables other than the alias won't exist outside the child's scope
231+
return EagerReconstructionUtils.wrapInChildScope(
232+
combined,
233+
importingData.getOriginalInterpreter()
234+
);
235+
}
236+
237+
private String getDoTagToPreserve(
238+
JinjavaInterpreter interpreter,
239+
JinjavaInterpreter child
240+
) {
241+
StringJoiner keyValueJoiner = new StringJoiner(",");
242+
String temporaryImportAlias = getTemporaryImportAlias(fullImportAlias);
243+
Map<String, Object> currentAliasMap = getMapForCurrentContextAlias(
244+
currentImportAlias,
245+
child
246+
);
247+
for (Map.Entry<String, Object> entry : currentAliasMap.entrySet()) {
248+
if (entry.getKey().equals(temporaryImportAlias)) {
249+
continue;
250+
}
251+
if (entry.getValue() instanceof DeferredValue) {
252+
keyValueJoiner.add(String.format("'%s': %s", entry.getKey(), entry.getKey()));
253+
} else if (!(entry.getValue() instanceof MacroFunction)) {
254+
keyValueJoiner.add(
255+
String.format(
256+
"'%s': %s",
257+
entry.getKey(),
258+
PyishObjectMapper.getAsPyishString(entry.getValue())
259+
)
260+
);
261+
}
262+
}
263+
if (keyValueJoiner.length() > 0) {
264+
return EagerReconstructionUtils.buildDoUpdateTag(
265+
temporaryImportAlias,
266+
"{" + keyValueJoiner.toString() + "}",
267+
interpreter
268+
);
269+
}
270+
return "";
271+
}
272+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.hubspot.jinjava.lib.tag.eager.importing;
2+
3+
import com.hubspot.jinjava.interpret.DeferredValue;
4+
import com.hubspot.jinjava.interpret.DeferredValueException;
5+
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
6+
import com.hubspot.jinjava.util.EagerReconstructionUtils;
7+
import java.util.Map;
8+
import java.util.stream.Collectors;
9+
10+
public interface EagerImportingStrategy {
11+
String handleDeferredTemplateFile(DeferredValueException e);
12+
void setup(JinjavaInterpreter child);
13+
14+
void integrateChild(JinjavaInterpreter child);
15+
String getFinalOutput(String newPathSetter, String output, JinjavaInterpreter child);
16+
17+
static String getSetTagForDeferredChildBindings(
18+
JinjavaInterpreter interpreter,
19+
String currentImportAlias,
20+
Map<String, Object> childBindings
21+
) {
22+
return childBindings
23+
.entrySet()
24+
.stream()
25+
.filter(
26+
entry ->
27+
entry.getValue() instanceof DeferredValue &&
28+
((DeferredValue) entry.getValue()).getOriginalValue() != null
29+
)
30+
.filter(entry -> !interpreter.getContext().containsKey(entry.getKey()))
31+
.filter(entry -> !entry.getKey().equals(currentImportAlias))
32+
.map(
33+
entry ->
34+
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
35+
entry.getKey(),
36+
((DeferredValue) entry.getValue()).getOriginalValue(),
37+
interpreter
38+
)
39+
)
40+
.collect(Collectors.joining());
41+
}
42+
}

0 commit comments

Comments
 (0)