Skip to content

Commit 96931e5

Browse files
authored
Merge pull request #1177 from HubSpot/eager-execution-reconstruct-block-path
Reconstruct current path of blocks/extends roots
2 parents c3b3fea + 016e299 commit 96931e5

18 files changed

Lines changed: 236 additions & 29 deletions

File tree

src/main/java/com/hubspot/jinjava/interpret/JinjavaInterpreter.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import com.hubspot.jinjava.lib.tag.DoTag;
3737
import com.hubspot.jinjava.lib.tag.ExtendsTag;
3838
import com.hubspot.jinjava.lib.tag.eager.EagerGenericTag;
39+
import com.hubspot.jinjava.loader.RelativePathResolver;
3940
import com.hubspot.jinjava.objects.serialization.PyishObjectMapper;
4041
import com.hubspot.jinjava.objects.serialization.PyishSerializable;
4142
import com.hubspot.jinjava.random.ConstantZeroRandomNumberGenerator;
@@ -46,6 +47,7 @@
4647
import com.hubspot.jinjava.tree.TreeParser;
4748
import com.hubspot.jinjava.tree.output.BlockInfo;
4849
import com.hubspot.jinjava.tree.output.BlockPlaceholderOutputNode;
50+
import com.hubspot.jinjava.tree.output.DynamicRenderedOutputNode;
4951
import com.hubspot.jinjava.tree.output.OutputList;
5052
import com.hubspot.jinjava.tree.output.OutputNode;
5153
import com.hubspot.jinjava.tree.output.RenderedOutputNode;
@@ -388,7 +390,9 @@ private String render(Node root, boolean processExtendRoots, long renderLimit) {
388390
return output.getValue();
389391
}
390392
}
391-
393+
DynamicRenderedOutputNode pathSetter = new DynamicRenderedOutputNode();
394+
output.addNode(pathSetter);
395+
Optional<String> basePath = context.getCurrentPathStack().peek();
392396
StringBuilder ignoredOutput = new StringBuilder();
393397
// render all extend parents, keeping the last as the root output
394398
if (processExtendRoots) {
@@ -429,7 +433,7 @@ private String render(Node root, boolean processExtendRoots, long renderLimit) {
429433
}
430434
numDeferredTokensBefore = context.getDeferredTokens().size();
431435
output = new OutputList(config.getMaxOutputSize());
432-
436+
output.addNode(pathSetter);
433437
boolean hasNestedExtends = false;
434438
for (Node node : parentRoot.getChildren()) {
435439
lineNumber = node.getLineNumber() - 1; // The line number is off by one when rendering the extend parent
@@ -445,15 +449,24 @@ private String render(Node root, boolean processExtendRoots, long renderLimit) {
445449
return output.getValue();
446450
}
447451
}
448-
449452
Optional<String> currentExtendPath = context.getExtendPathStack().pop();
450453
extendPath =
451454
hasNestedExtends ? currentExtendPath : context.getExtendPathStack().peek();
452-
context.getCurrentPathStack().pop();
455+
basePath = context.getCurrentPathStack().pop();
453456
}
454457
}
455458

459+
int numDeferredTokensBefore = context.getDeferredTokens().size();
456460
resolveBlockStubs(output);
461+
if (context.getDeferredTokens().size() > numDeferredTokensBefore) {
462+
pathSetter.setValue(
463+
EagerReconstructionUtils.buildBlockOrInlineSetTag(
464+
RelativePathResolver.CURRENT_PATH_CONTEXT_KEY,
465+
basePath,
466+
this
467+
)
468+
);
469+
}
457470

458471
if (ignoredOutput.length() > 0) {
459472
return (
@@ -513,7 +526,10 @@ private void resolveBlockStubs(OutputList output, Stack<String> blockNames) {
513526
currentBlock = block;
514527

515528
OutputList blockValueBuilder = new OutputList(config.getMaxOutputSize());
529+
DynamicRenderedOutputNode prefix = new DynamicRenderedOutputNode();
530+
blockValueBuilder.addNode(prefix);
516531
boolean pushedParentPathOntoStack = false;
532+
int numDeferredTokensBefore = context.getDeferredTokens().size();
517533
if (
518534
block.getParentPath().isPresent() &&
519535
!getContext().getCurrentPathStack().contains(block.getParentPath().get())
@@ -534,6 +550,13 @@ private void resolveBlockStubs(OutputList output, Stack<String> blockNames) {
534550

535551
blockValueBuilder.addNode(child.render(this));
536552
}
553+
if (context.getDeferredTokens().size() > numDeferredTokensBefore) {
554+
EagerReconstructionUtils.reconstructPathAroundBlock(
555+
prefix,
556+
blockValueBuilder,
557+
this
558+
);
559+
}
537560
if (pushedParentPathOntoStack) {
538561
getContext().getCurrentPathStack().pop();
539562
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.hubspot.jinjava.tree.output;
2+
3+
import com.google.common.base.Charsets;
4+
import java.nio.charset.Charset;
5+
6+
/**
7+
* An OutputNode that can be modified after already being added to the OutputList
8+
*/
9+
public class DynamicRenderedOutputNode implements OutputNode {
10+
11+
protected String output = "";
12+
13+
public void setValue(String output) {
14+
this.output = output;
15+
}
16+
17+
@Override
18+
public String getValue() {
19+
return output;
20+
}
21+
22+
@Override
23+
public long getSize() {
24+
return output == null
25+
? 0
26+
: output.getBytes(Charset.forName(Charsets.UTF_8.name())).length;
27+
}
28+
29+
@Override
30+
public String toString() {
31+
return getValue();
32+
}
33+
}

src/main/java/com/hubspot/jinjava/util/EagerReconstructionUtils.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,16 @@
2323
import com.hubspot.jinjava.lib.tag.eager.EagerExecutionResult;
2424
import com.hubspot.jinjava.lib.tag.eager.EagerSetTagStrategy;
2525
import com.hubspot.jinjava.lib.tag.eager.importing.AliasedEagerImportingStrategy;
26+
import com.hubspot.jinjava.lib.tag.eager.importing.EagerImportingStrategyFactory;
27+
import com.hubspot.jinjava.loader.RelativePathResolver;
2628
import com.hubspot.jinjava.mode.EagerExecutionMode;
2729
import com.hubspot.jinjava.objects.serialization.PyishBlockSetSerializable;
2830
import com.hubspot.jinjava.objects.serialization.PyishObjectMapper;
2931
import com.hubspot.jinjava.objects.serialization.PyishSerializable;
3032
import com.hubspot.jinjava.tree.TagNode;
33+
import com.hubspot.jinjava.tree.output.DynamicRenderedOutputNode;
34+
import com.hubspot.jinjava.tree.output.OutputList;
35+
import com.hubspot.jinjava.tree.output.RenderedOutputNode;
3136
import com.hubspot.jinjava.tree.parse.NoteToken;
3237
import com.hubspot.jinjava.tree.parse.TagToken;
3338
import com.hubspot.jinjava.tree.parse.TokenScannerSymbols;
@@ -921,4 +926,32 @@ public static void commitSpeculativeBindings(
921926
.filter(entry -> !(entry.getValue() instanceof DeferredValueShadow))
922927
.forEach(entry -> interpreter.getContext().put(entry.getKey(), entry.getValue()));
923928
}
929+
930+
public static void reconstructPathAroundBlock(
931+
DynamicRenderedOutputNode prefix,
932+
OutputList blockValueBuilder,
933+
JinjavaInterpreter interpreter
934+
) {
935+
String blockPathSetter = EagerImportingStrategyFactory.getSetTagForCurrentPath(
936+
interpreter
937+
);
938+
String tempVarName = "temp_current_path_" + Math.abs(blockPathSetter.hashCode() >> 1);
939+
prefix.setValue(
940+
buildSetTag(
941+
ImmutableMap.of(tempVarName, RelativePathResolver.CURRENT_PATH_CONTEXT_KEY),
942+
interpreter,
943+
false
944+
) +
945+
EagerImportingStrategyFactory.getSetTagForCurrentPath(interpreter)
946+
);
947+
blockValueBuilder.addNode(
948+
new RenderedOutputNode(
949+
buildSetTag(
950+
ImmutableMap.of(RelativePathResolver.CURRENT_PATH_CONTEXT_KEY, tempVarName),
951+
interpreter,
952+
false
953+
)
954+
)
955+
);
956+
}
924957
}

src/test/java/com/hubspot/jinjava/EagerTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1540,4 +1540,36 @@ public void itReconstructsAliasedMacroSecondPass() {
15401540
"reconstructs-aliased-macro.expected"
15411541
);
15421542
}
1543+
1544+
@Test
1545+
public void itReconstructsBlockPathWhenDeferred() {
1546+
interpreter.getContext().getCurrentPathStack().push("Child path", 0, 0);
1547+
expectedTemplateInterpreter.assertExpectedOutputNonIdempotent(
1548+
"reconstructs-block-path-when-deferred/test"
1549+
);
1550+
}
1551+
1552+
@Test
1553+
public void itReconstructsBlockPathWhenDeferredSecondPass() {
1554+
interpreter.getContext().put("deferred", "resolved");
1555+
expectedTemplateInterpreter.assertExpectedOutput(
1556+
"reconstructs-block-path-when-deferred/test.expected"
1557+
);
1558+
}
1559+
1560+
@Test
1561+
public void itReconstructsBlockPathWhenDeferredNested() {
1562+
interpreter.getContext().getCurrentPathStack().push("Child path", 0, 0);
1563+
expectedTemplateInterpreter.assertExpectedOutputNonIdempotent(
1564+
"reconstructs-block-path-when-deferred-nested/test"
1565+
);
1566+
}
1567+
1568+
@Test
1569+
public void itReconstructsBlockPathWhenDeferredNestedSecondPass() {
1570+
interpreter.getContext().put("deferred", "resolved");
1571+
expectedTemplateInterpreter.assertExpectedOutput(
1572+
"reconstructs-block-path-when-deferred-nested/test.expected"
1573+
);
1574+
}
15431575
}

src/test/java/com/hubspot/jinjava/ExpectedTemplateInterpreter.java

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.google.common.base.Charsets;
66
import com.google.common.io.Resources;
77
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
8+
import com.hubspot.jinjava.interpret.JinjavaInterpreter.InterpreterScopeClosable;
89
import com.hubspot.jinjava.mode.DefaultExecutionMode;
910
import java.io.IOException;
1011
import java.nio.charset.StandardCharsets;
@@ -66,13 +67,15 @@ public String assertExpectedNonEagerOutput(String name) {
6667
);
6768
JinjavaInterpreter.pushCurrent(preserveInterpreter);
6869

69-
preserveInterpreter.getContext().putAll(interpreter.getContext());
70-
String template = getFixtureTemplate(name);
71-
output = JinjavaInterpreter.getCurrent().render(template);
72-
assertThat(JinjavaInterpreter.getCurrent().getContext().getDeferredNodes())
73-
.as("Ensure no deferred nodes were created")
74-
.isEmpty();
75-
assertThat(output.trim()).isEqualTo(expected(name).trim());
70+
try (InterpreterScopeClosable ignored = preserveInterpreter.enterScope()) {
71+
preserveInterpreter.getContext().putAll(interpreter.getContext());
72+
String template = getFixtureTemplate(name);
73+
output = JinjavaInterpreter.getCurrent().render(template);
74+
assertThat(JinjavaInterpreter.getCurrent().getContext().getDeferredNodes())
75+
.as("Ensure no deferred nodes were created")
76+
.isEmpty();
77+
assertThat(output.trim()).isEqualTo(expected(name).trim());
78+
}
7679
} finally {
7780
JinjavaInterpreter.popCurrent();
7881
}
@@ -97,11 +100,13 @@ public String assertExpectedNonEagerOutput(String name) {
97100

98101
preserveInterpreter.getContext().putAll(interpreter.getContext());
99102
String template = getFixtureTemplate(originalName);
100-
output = JinjavaInterpreter.getCurrent().render(template);
101-
assertThat(JinjavaInterpreter.getCurrent().getContext().getDeferredNodes())
102-
.as("Ensure no deferred nodes were created")
103-
.isEmpty();
104-
assertThat(output.trim()).isEqualTo(expected(name).trim());
103+
try (InterpreterScopeClosable ignored = preserveInterpreter.enterScope()) {
104+
output = JinjavaInterpreter.getCurrent().render(template);
105+
assertThat(JinjavaInterpreter.getCurrent().getContext().getDeferredNodes())
106+
.as("Ensure no deferred nodes were created")
107+
.isEmpty();
108+
assertThat(output.trim()).isEqualTo(expected(name).trim());
109+
}
105110
} finally {
106111
JinjavaInterpreter.popCurrent();
107112
}

src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerExtendsTagTest.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.hubspot.jinjava.interpret.DeferredValue;
99
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
1010
import com.hubspot.jinjava.lib.tag.ExtendsTagTest;
11+
import com.hubspot.jinjava.loader.RelativePathResolver;
1112
import com.hubspot.jinjava.mode.EagerExecutionMode;
1213
import java.io.IOException;
1314
import org.junit.After;
@@ -46,7 +47,9 @@ public void teardown() {
4647

4748
@Test
4849
public void itDefersBlockInExtendsChild() {
49-
expectedTemplateInterpreter.assertExpectedOutput("defers-block-in-extends-child");
50+
expectedTemplateInterpreter.assertExpectedOutputNonIdempotent(
51+
"defers-block-in-extends-child"
52+
);
5053
}
5154

5255
@Test
@@ -55,14 +58,17 @@ public void itDefersBlockInExtendsChildSecondPass() {
5558
expectedTemplateInterpreter.assertExpectedOutput(
5659
"defers-block-in-extends-child.expected"
5760
);
61+
context.remove(RelativePathResolver.CURRENT_PATH_CONTEXT_KEY);
5862
expectedTemplateInterpreter.assertExpectedNonEagerOutput(
5963
"defers-block-in-extends-child.expected"
6064
);
6165
}
6266

6367
@Test
6468
public void itDefersSuperBlockWithDeferred() {
65-
expectedTemplateInterpreter.assertExpectedOutput("defers-super-block-with-deferred");
69+
expectedTemplateInterpreter.assertExpectedOutputNonIdempotent(
70+
"defers-super-block-with-deferred"
71+
);
6672
}
6773

6874
@Test
@@ -71,6 +77,7 @@ public void itDefersSuperBlockWithDeferredSecondPass() {
7177
expectedTemplateInterpreter.assertExpectedOutput(
7278
"defers-super-block-with-deferred.expected"
7379
);
80+
context.remove(RelativePathResolver.CURRENT_PATH_CONTEXT_KEY);
7481
expectedTemplateInterpreter.assertExpectedNonEagerOutput(
7582
"defers-super-block-with-deferred.expected"
7683
);
@@ -90,6 +97,7 @@ public void itReconstructsDeferredOutsideBlockSecondPass() {
9097
expectedTemplateInterpreter.assertExpectedOutput(
9198
"reconstructs-deferred-outside-block.expected"
9299
);
100+
context.remove(RelativePathResolver.CURRENT_PATH_CONTEXT_KEY);
93101
expectedTemplateInterpreter.assertExpectedNonEagerOutput(
94102
"reconstructs-deferred-outside-block.expected"
95103
);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{% set prefix = deferred ? "current" : "current" %}
2+
Parent's current path is: {{ '{{' + prefix + '_path }}' }}
3+
-----Pre-First-----
4+
{% block first -%}
5+
{%- endblock %}
6+
-----Post-First-----
7+
-----Pre-Second-----
8+
{% block second -%}
9+
{%- endblock %}
10+
-----Post-Second-----
11+
Parent's current path is: {{ '{{' + prefix + '_path }}' }}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{% extends '../../eager/reconstructs-block-path-when-deferred-nested/base.jinja' %}
2+
{% block first %}
3+
{%- set prefix = deferred ? "current" : "current" -%}
4+
Middle's first current path is: {{ '{{' + prefix + '_path }}' }}
5+
{% endblock %}
6+
7+
{% block second %}
8+
{%- set prefix = deferred ? "current" : "current" -%}
9+
Middle's second current path is: {{ '{{' + prefix + '_path }}' }}
10+
{% endblock %}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Parent's current path is: ../../eager/reconstructs-block-path-when-deferred-nested/base.jinja
2+
-----Pre-First-----
3+
Child's first current path is: Child path
4+
5+
-----Post-First-----
6+
-----Pre-Second-----
7+
Middle's second current path is: ../../eager/reconstructs-block-path-when-deferred-nested/middle.jinja
8+
9+
-----Post-Second-----
10+
Parent's current path is: ../../eager/reconstructs-block-path-when-deferred-nested/base.jinja
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{% set current_path = '../../eager/reconstructs-block-path-when-deferred-nested/base.jinja' %}{% set prefix = deferred ? 'current' : 'current' %}
2+
Parent's current path is: {{ '{{' + prefix + '_path }}' }}
3+
-----Pre-First-----
4+
{% set temp_current_path_586961858 = current_path %}{% set current_path = 'Child path' %}{% set prefix = deferred ? 'current' : 'current' %}Child's first current path is: {{ '{{' + prefix + '_path }}' }}
5+
{% set current_path = temp_current_path_586961858 %}
6+
-----Post-First-----
7+
-----Pre-Second-----
8+
{% set temp_current_path_435835221 = current_path %}{% set current_path = '../../eager/reconstructs-block-path-when-deferred-nested/middle.jinja' %}{% set prefix = deferred ? 'current' : 'current' %}Middle's second current path is: {{ '{{' + prefix + '_path }}' }}
9+
{% set current_path = temp_current_path_435835221 %}
10+
-----Post-Second-----
11+
Parent's current path is: {{ '{{' + prefix + '_path }}' }}

0 commit comments

Comments
 (0)