Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix rendering of custom tags with more than one parameter #301

Merged
merged 2 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions ruby/cases_render_tag.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env ruby

require 'pp'
require_relative '_helpers.rb'

# pp render({"val" => "here"}, "{{ val }}")

if isJekyll
context = jekyllContext("includes_dir" => "cases_variable_inside_import/_includes")
# Tag 'render' is not defined in Jekyll. Use 'include' or 'include_relative'
assertRaise do
assertEqual("TEST", render({}, "{% render color.liquid %}", context))
end
else
Liquid::Template.file_system = Liquid::LocalFileSystem.new("cases_variable_inside_import/_includes", "%s.liquid")

# liquid template name must be a quoted string
assertRaise do
assertEqual("there", render({"var" => "there", "tmpl" => "include_read_var"}, "{% render tmpl %}"))
end
# render variables
assertEqual("", render({"var" => "there"}, "{% render 'include_read_var' %}"))
assertEqual("here", render({"var" => "there"}, "{% render 'include_read_var', var: 'here' %}"))
assertEqual("there", render({"var" => "there"}, "{% render 'include_read_var', var: var %}"))
assertEqual("color: ''", render({"color" => "red"}, "{% render 'color' %}"))
assertEqual("color: 'blue'", render({"color" => "red"}, "{% render 'color', color: 'blue' %}"))
assertEqual("color: 'red'", render({"color" => "red"}, "{% render 'color', color: color %}"))
# render with
assertEqual("color: 'red'", render({"var" => "there"}, "{% render 'color' with 'red' %}"))
assertEqual("color: 'red'", render({"var" => "there"}, "{% render 'color' with 'red', color: 'blue' %}"))
assertEqual("color: 'blue'", render({"clr" => "blue"}, "{% render 'color' with clr %}"))
assertEqual("color: 'blue'", render({"clr" => "blue"}, "{% render 'color' with clr, color: 'green' %}"))
# render for
assertEqual("color: 'r'color: 'g'color: 'b'", render({"colors" => ["r", "g", "b"]}, "{% render 'color' for colors %}"))
assertEqual("rgb", render({"colors" => ["r", "g", "b"]}, "{% render 'include_read_var' for colors as var %}"))
assertEqual("foofoofoo", render({"colors" => ["r", "g", "b"]}, "{% render 'include_read_var' for colors as clr, var: 'foo' %}"))
end

38 changes: 25 additions & 13 deletions src/main/antlr4/liquid/parser/v4/LiquidLexer.g4
Original file line number Diff line number Diff line change
@@ -1,23 +1,33 @@
lexer grammar LiquidLexer;

@lexer::members {
private boolean liquidStyleInclude = true;
private boolean stripSpacesAroundTags = false;
private boolean stripSingleLine = false;
private java.util.LinkedList<Token> tokens = new java.util.LinkedList<>();
private java.util.Set<String> blocks = new java.util.HashSet<String>();
private java.util.Set<String> tags = new java.util.HashSet<String>();
private java.util.Stack<String> customBlockState = new java.util.Stack<String>();

public LiquidLexer(CharStream charStream, boolean stripSpacesAroundTags, java.util.Set<String> blocks, java.util.Set<String> tags) {
this(charStream, stripSpacesAroundTags, false, blocks, tags);
private boolean isLiquidStyleInclude(){
return liquidStyleInclude;
}

public LiquidLexer(CharStream charStream, boolean stripSpacesAroundTags) {
this(charStream, stripSpacesAroundTags, false, new java.util.HashSet<String>(), new java.util.HashSet<String>());
private boolean isJekyllStyleInclude(){
return !liquidStyleInclude;
}

public LiquidLexer(CharStream charStream, boolean stripSpacesAroundTags, boolean stripSingleLine, java.util.Set<String> blocks, java.util.Set<String> tags) {
public LiquidLexer(CharStream charStream, boolean isLiquidStyleInclude, boolean stripSpacesAroundTags, java.util.Set<String> blocks, java.util.Set<String> tags) {
this(charStream, isLiquidStyleInclude, stripSpacesAroundTags, false, blocks, tags);
}

public LiquidLexer(CharStream charStream, boolean isLiquidStyleInclude, boolean stripSpacesAroundTags) {
this(charStream, isLiquidStyleInclude, stripSpacesAroundTags, false, new java.util.HashSet<String>(), new java.util.HashSet<String>());
}

public LiquidLexer(CharStream charStream, boolean isLiquidStyleInclude, boolean stripSpacesAroundTags, boolean stripSingleLine, java.util.Set<String> blocks, java.util.Set<String> tags) {
this(charStream);
this.liquidStyleInclude = isLiquidStyleInclude;
this.stripSpacesAroundTags = stripSpacesAroundTags;
this.stripSingleLine = stripSingleLine;
this.blocks = blocks;
Expand Down Expand Up @@ -206,14 +216,16 @@ mode IN_TAG_ID;
// otherwise it is an invalid tag
// BUT it also can be programed manually, so even if not supported flavour, we must respect
// user-defined tags OR blocks
String text = getText();
if (blocks.contains(text)) {
setType(BlockId);
customBlockState.push(text);
} else if (tags.contains(text)) {
setType(SimpleTagId);
} else {
setType(InvalidTagId);
if (isLiquidStyleInclude()) {
String text = getText();
if (blocks.contains(text)) {
setType(BlockId);
customBlockState.push(text);
} else if (tags.contains(text)) {
setType(SimpleTagId);
} else {
setType(InvalidTagId);
}
}
} -> popMode;

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/liqp/Template.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public class Template {
Set<String> tagNames = this.templateParser.insertions.getTagNames();

this.templateSize = stream.size();
LiquidLexer lexer = new LiquidLexer(stream, this.templateParser.isStripSpacesAroundTags(),
LiquidLexer lexer = new LiquidLexer(stream, this.templateParser.liquidStyleInclude, this.templateParser.isStripSpacesAroundTags(),
this.templateParser.isStripSingleLine(), blockNames, tagNames);
this.sourceLocation = location;
try {
Expand Down
6 changes: 4 additions & 2 deletions src/main/java/liqp/parser/v4/NodeVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import liquid.parser.v4.LiquidParser;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.misc.Interval;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;

import liqp.Insertion;
Expand Down Expand Up @@ -194,9 +195,10 @@ public LNode visitSimple_tag(Simple_tagContext ctx) {
List<LNode> expressions = new ArrayList<LNode>();

if (ctx.other_tag_parameters() != null) {
expressions.add(new AtomNode(ctx.other_tag_parameters().getText()));
for (ParseTree child : ctx.other_tag_parameters().other_than_tag_end().children) {
expressions.add(new AtomNode(child.getText()));
}
}

return new InsertionNode(insertions.get(ctx.SimpleTagId().getText()), expressions.toArray(new LNode[expressions.size()]));
}

Expand Down
18 changes: 18 additions & 0 deletions src/test/java/liqp/InsertionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,24 @@ public Object render(TemplateContext context, LNode... nodes) {
assertThat(rendered, is("20.0"));
}

@Test
public void testCustomTagParameters() throws RecognitionException {

TemplateParser parser = new TemplateParser.Builder().withTag(new Tag("multiply") {
@Override
public Object render(TemplateContext context, LNode... nodes) {
Double number1 = super.asNumber(nodes[0].render(context)).doubleValue();
Double number2 = super.asNumber(nodes[1].render(context)).doubleValue();
return number1 * number2;
}
}).build();

Template template = parser.parse("{% multiply 2 4 %}");
String rendered = String.valueOf(template.render());

assertThat(rendered, is("8.0"));
}

@Test
public void testCustomTagBlock() throws RecognitionException {
TemplateParser templateParser = new TemplateParser.Builder().withBlock(new Block("twice") {
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/liqp/TestUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public static LNode getNode(String source, String rule) throws Exception {
public static LNode getNode(String source, String rule, TemplateParser templateParser)
throws Exception {

LiquidLexer lexer = new LiquidLexer(CharStreams.fromString("{{ " + source + " }}"));
LiquidLexer lexer = new LiquidLexer(CharStreams.fromString("{{ " + source + " }}"), templateParser.liquidStyleInclude, templateParser.stripSpacesAroundTags);
LiquidParser parser = new LiquidParser(new CommonTokenStream(lexer), templateParser.liquidStyleInclude, templateParser.evaluateInOutputTag, templateParser.errorMode);

LiquidParser.OutputContext root = parser.output();
Expand Down
37 changes: 19 additions & 18 deletions src/test/java/liqp/parser/v4/LiquidLexerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,23 @@ public void testInclude() {
assertThat(tokenise("{%include").get(1).getType(), is(LiquidLexer.Include));
}

// IncludeRelative : 'include_relative' { conditional };
@Test
public void testIncludeRelative() {
assertThat("tag 'include_relative' is defined only in Jekyll style",
tokenise("{%include_relative").get(1).getType(), is(LiquidLexer.InvalidTagId));
}

@Test
public void testIncludeRelativeCustomTag() {
HashSet<String> tags = new HashSet<>();
tags.add("include_relative");
List<Token> tokens = tokenise("{%include_relative%}", new HashSet<String>(), tags);

assertThat("Custom tag or block 'include_relative' can be defined in Liquid style",
tokens.get(1).getType(), is(LiquidLexer.SimpleTagId));
}

// With : 'with';
@Test
public void testWith() {
Expand Down Expand Up @@ -598,13 +615,9 @@ private static List<Token> tokenise(String source, boolean stripSpacesAroundTags
return tokens;
}

static CommonTokenStream commonTokenStream(String source) {
return commonTokenStream(source, false);
}

static CommonTokenStream commonTokenStream(String source, boolean stripSpacesAroundTags, Set<String> blocks, Set<String> tags) {

LiquidLexer lexer = new LiquidLexer(CharStreams.fromString(source), stripSpacesAroundTags, blocks, tags);
boolean isLiquidStyleInclude = true; // No tests for Jekyll style of includes, yet
LiquidLexer lexer = new LiquidLexer(CharStreams.fromString(source), isLiquidStyleInclude, stripSpacesAroundTags, blocks, tags);

lexer.addErrorListener(new BaseErrorListener(){
@Override
Expand All @@ -616,16 +629,4 @@ public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int
return new CommonTokenStream(lexer);
}

static CommonTokenStream commonTokenStream(String source, boolean stripSpacesAroundTags) {
LiquidLexer lexer = new LiquidLexer(CharStreams.fromString(source), stripSpacesAroundTags);

lexer.addErrorListener(new BaseErrorListener(){
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) {
throw new RuntimeException(e);
}
});

return new CommonTokenStream(lexer);
}
}
13 changes: 13 additions & 0 deletions src/test/java/liqp/parser/v4/LiquidParserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,19 @@ public void testCustom_tag() {
equalTo(array("{%", "mu", "|42", "%}"))
);

assertThat(
"tag parameters are concatenated into one String by texts()",
texts("{% mu for foo as bar %}", "simple_tag", emptySet, muSet),
equalTo(array("{%", "mu", "forfooasbar", "%}"))
);

ParseTree tree = parse("{% mu for foo as bar %}", "simple_tag", emptySet, muSet);
assertThat(
"tag parameters are parsed as separate nodes",
texts(tree.getChild(2).getChild(0)),
equalTo(array("for", "foo", "as", "bar"))
);

assertThat(
texts("{% mu %} . {% endmu %}", "other_tag", muSet, emptySet),
equalTo(array("{%", "mu", "%}", " . ", "{%", "endmu", "%}"))
Expand Down
Loading