Skip to content

Commit 0261709

Browse files
committed
Merge branch 'master' of github.com:HubSpot/jinjava into construct-eager-macro-functions
2 parents 62bf49b + 131dbb1 commit 0261709

File tree

64 files changed

+865
-354
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+865
-354
lines changed

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# Jinjava Releases #
2+
### 2024-12-06 Verision 2.7.4 ([Maven Central](https://search.maven.org/artifact/com.hubspot.jinjava/jinjava/2.7.4/jar)) ###
3+
* [Implement jinja2.ext.loopcontrols extensions (break and continue)](https://github.com/HubSpot/jinjava/pull/1219)
4+
* [Apply whitespace rules for LStrip and Trim to comment blocks](https://github.com/HubSpot/jinjava/pull/1217)
25
### 2024-09-12 Version 2.7.3 ([Maven Central](https://search.maven.org/artifact/com.hubspot.jinjava/jinjava/2.7.3/jar)) ###
36
* [Add support for numeric keys in map literal](https://github.com/HubSpot/jinjava/pull/1152)
47
* [Add feature to consider undefined variable a TemplateError](https://github.com/HubSpot/jinjava/pull/1174)

src/main/java/com/hubspot/jinjava/el/ext/eager/EvalResultHolder.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.hubspot.jinjava.el.ext.IdentifierPreservationStrategy;
66
import com.hubspot.jinjava.interpret.DeferredValueException;
77
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
8+
import com.hubspot.jinjava.interpret.MetaContextVariables;
89
import com.hubspot.jinjava.interpret.PartiallyDeferredValue;
910
import com.hubspot.jinjava.util.EagerExpressionResolver;
1011
import de.odysseus.el.tree.Bindings;
@@ -125,11 +126,12 @@ static String reconstructNode(
125126
if (astNode instanceof AstIdentifier) {
126127
String name = ((AstIdentifier) astNode).getName();
127128
if (
128-
((JinjavaInterpreter) context
129-
.getELResolver()
130-
.getValue(context, null, ExtendedParser.INTERPRETER)).getContext()
131-
.getComputedMetaContextVariables()
132-
.contains(name)
129+
MetaContextVariables.isMetaContextVariable(
130+
name,
131+
((JinjavaInterpreter) context
132+
.getELResolver()
133+
.getValue(context, null, ExtendedParser.INTERPRETER)).getContext()
134+
)
133135
) {
134136
return name;
135137
}

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,14 +338,17 @@ public void addResolvedFunction(String function) {
338338
}
339339
}
340340

341+
/**
342+
* @deprecated Use {@link MetaContextVariables#isMetaContextVariable(String, Context)}
343+
*/
341344
@Deprecated
342345
@Beta
343346
public Set<String> getMetaContextVariables() {
344347
return metaContextVariables;
345348
}
346349

347350
@Beta
348-
public Set<String> getComputedMetaContextVariables() {
351+
Set<String> getComputedMetaContextVariables() {
349352
return Sets.difference(metaContextVariables, overriddenNonMetaContextVariables);
350353
}
351354

@@ -354,6 +357,10 @@ public void addMetaContextVariables(Collection<String> variables) {
354357
metaContextVariables.addAll(variables);
355358
}
356359

360+
Set<String> getNonMetaContextVariables() {
361+
return overriddenNonMetaContextVariables;
362+
}
363+
357364
@Beta
358365
public void addNonMetaContextVariables(Collection<String> variables) {
359366
overriddenNonMetaContextVariables.addAll(
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.hubspot.jinjava.interpret;
2+
3+
import com.google.common.annotations.Beta;
4+
import java.util.Objects;
5+
6+
@Beta
7+
public class MetaContextVariables {
8+
9+
public static final String TEMPORARY_META_CONTEXT_PREFIX = "__temp_meta_";
10+
private static final String TEMPORARY_IMPORT_ALIAS_PREFIX =
11+
TEMPORARY_META_CONTEXT_PREFIX + "import_alias_";
12+
13+
private static final String TEMPORARY_IMPORT_ALIAS_FORMAT =
14+
TEMPORARY_IMPORT_ALIAS_PREFIX + "%d__";
15+
private static final String TEMP_CURRENT_PATH_PREFIX =
16+
TEMPORARY_META_CONTEXT_PREFIX + "current_path_";
17+
private static final String TEMP_CURRENT_PATH_FORMAT =
18+
TEMP_CURRENT_PATH_PREFIX + "%d__";
19+
20+
public static boolean isMetaContextVariable(String varName, Context context) {
21+
if (isTemporaryMetaContextVariable(varName)) {
22+
return true;
23+
}
24+
return (
25+
context.getMetaContextVariables().contains(varName) &&
26+
!context.getNonMetaContextVariables().contains(varName)
27+
);
28+
}
29+
30+
private static boolean isTemporaryMetaContextVariable(String varName) {
31+
return varName.startsWith(TEMPORARY_META_CONTEXT_PREFIX);
32+
}
33+
34+
public static boolean isTemporaryImportAlias(String varName) {
35+
// This is just faster than checking a regex
36+
return varName.startsWith(TEMPORARY_IMPORT_ALIAS_PREFIX);
37+
}
38+
39+
public static String getTemporaryImportAlias(String fullAlias) {
40+
return String.format(
41+
TEMPORARY_IMPORT_ALIAS_FORMAT,
42+
Math.abs(Objects.hashCode(fullAlias))
43+
);
44+
}
45+
46+
public static String getTemporaryCurrentPathVarName(String newPath) {
47+
return String.format(
48+
TEMP_CURRENT_PATH_FORMAT,
49+
Math.abs(Objects.hash(newPath, TEMPORARY_META_CONTEXT_PREFIX) >> 1)
50+
);
51+
}
52+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.hubspot.jinjava.interpret;
2+
3+
/**
4+
* Exception thrown when `continue` or `break` is called outside of a loop
5+
*/
6+
public class NotInLoopException extends InterpretException {
7+
8+
public static final String MESSAGE_PREFIX = "`";
9+
public static final String MESSAGE_SUFFIX = "` called while not in a for loop";
10+
11+
public NotInLoopException(String tagName) {
12+
super(MESSAGE_PREFIX + tagName + MESSAGE_SUFFIX);
13+
}
14+
}

src/main/java/com/hubspot/jinjava/lib/fn/MacroFunction.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@ public String getEvaluationResult(
127127
);
128128
} else {
129129
if (!alreadyDeferredInEarlierCall(scopeEntry.getKey(), interpreter)) {
130+
if (
131+
interpreter.getContext().get(scopeEntry.getKey()) == scopeEntry.getValue()
132+
) {
133+
continue; // don't override if it's the same object
134+
}
130135
interpreter.getContext().put(scopeEntry.getKey(), scopeEntry.getValue());
131136
}
132137
}

src/main/java/com/hubspot/jinjava/lib/fn/eager/EagerMacroFunction.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,12 @@ public Object doEvaluate(
130130
);
131131
throw new DeferredInvocationResolutionException(tempVarName);
132132
}
133+
if (!eagerExecutionResult.getResult().isFullyResolved()) {
134+
return EagerReconstructionUtils.wrapInChildScope(
135+
eagerExecutionResult.getResult().toString(true),
136+
interpreter
137+
);
138+
}
133139
return eagerExecutionResult.getResult().toString(true);
134140
}
135141

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.hubspot.jinjava.lib.tag;
2+
3+
import com.hubspot.jinjava.doc.annotations.JinjavaDoc;
4+
import com.hubspot.jinjava.doc.annotations.JinjavaTextMateSnippet;
5+
import com.hubspot.jinjava.interpret.DeferredValueException;
6+
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
7+
import com.hubspot.jinjava.interpret.NotInLoopException;
8+
import com.hubspot.jinjava.tree.TagNode;
9+
import com.hubspot.jinjava.util.ForLoop;
10+
11+
/**
12+
* Implements the common loopcontrol `continue`, as in the jinja2.ext.loopcontrols extension
13+
* @author ccutrer
14+
*/
15+
16+
@JinjavaDoc(
17+
value = "Stops executing the current for loop, including any further iterations"
18+
)
19+
@JinjavaTextMateSnippet(
20+
code = "{% for item in [1, 2, 3, 4] %}{% if item > 2 == 0 %}{% break %}{% endif %}{{ item }}{% endfor %}"
21+
)
22+
public class BreakTag implements Tag {
23+
24+
public static final String TAG_NAME = "break";
25+
26+
@Override
27+
public String getName() {
28+
return TAG_NAME;
29+
}
30+
31+
@Override
32+
public String interpret(TagNode tagNode, JinjavaInterpreter interpreter) {
33+
Object loop = interpreter.getContext().get(ForTag.LOOP);
34+
if (loop instanceof ForLoop) {
35+
if (interpreter.getContext().isDeferredExecutionMode()) {
36+
throw new DeferredValueException("Deferred break");
37+
}
38+
ForLoop forLoop = (ForLoop) loop;
39+
forLoop.doBreak();
40+
} else {
41+
throw new NotInLoopException(TAG_NAME);
42+
}
43+
return "";
44+
}
45+
46+
@Override
47+
public String getEndTagName() {
48+
return null;
49+
}
50+
51+
@Override
52+
public boolean isRenderedInValidationMode() {
53+
return true;
54+
}
55+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.hubspot.jinjava.lib.tag;
2+
3+
import com.hubspot.jinjava.doc.annotations.JinjavaDoc;
4+
import com.hubspot.jinjava.doc.annotations.JinjavaTextMateSnippet;
5+
import com.hubspot.jinjava.interpret.DeferredValueException;
6+
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
7+
import com.hubspot.jinjava.interpret.NotInLoopException;
8+
import com.hubspot.jinjava.tree.TagNode;
9+
import com.hubspot.jinjava.util.ForLoop;
10+
11+
/**
12+
* Implements the common loopcontrol `continue`, as in the jinja2.ext.loopcontrols extension
13+
* @author ccutrer
14+
*/
15+
16+
@JinjavaDoc(value = "Stops executing the current iteration of the current for loop")
17+
@JinjavaTextMateSnippet(
18+
code = "{% for item in [1, 2, 3, 4] %}{% if item % 2 == 0 %}{% continue %}{% endif %}{{ item }}{% endfor %}"
19+
)
20+
public class ContinueTag implements Tag {
21+
22+
public static final String TAG_NAME = "continue";
23+
24+
@Override
25+
public String getName() {
26+
return TAG_NAME;
27+
}
28+
29+
@Override
30+
public String interpret(TagNode tagNode, JinjavaInterpreter interpreter) {
31+
Object loop = interpreter.getContext().get(ForTag.LOOP);
32+
if (loop instanceof ForLoop) {
33+
if (interpreter.getContext().isDeferredExecutionMode()) {
34+
throw new DeferredValueException("Deferred continue");
35+
}
36+
ForLoop forLoop = (ForLoop) loop;
37+
forLoop.doContinue();
38+
} else {
39+
throw new NotInLoopException(TAG_NAME);
40+
}
41+
return "";
42+
}
43+
44+
@Override
45+
public String getEndTagName() {
46+
return null;
47+
}
48+
49+
@Override
50+
public boolean isRenderedInValidationMode() {
51+
return true;
52+
}
53+
}

src/main/java/com/hubspot/jinjava/lib/tag/ForTag.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,10 @@ public String renderForCollection(
277277
interpreter.addError(TemplateError.fromOutputTooBigException(e));
278278
return checkLoopVariable(interpreter, buff);
279279
}
280+
// continue in the body of the loop; ignore the rest of the body
281+
if (loop.isContinued()) {
282+
break;
283+
}
280284
}
281285
}
282286
if (
@@ -297,7 +301,7 @@ private String checkLoopVariable(
297301
JinjavaInterpreter interpreter,
298302
LengthLimitingStringBuilder buff
299303
) {
300-
if (interpreter.getContext().get("loop") instanceof DeferredValue) {
304+
if (interpreter.getContext().get(LOOP) instanceof DeferredValue) {
301305
throw new DeferredValueException(
302306
"loop variable deferred",
303307
interpreter.getLineNumber(),

0 commit comments

Comments
 (0)